结合 AndServer,实现抖音 X-Gorgon 算法,设备 id 生成接口

2020-04-07 10:14:52 +08:00
 louislivi

结合 AndServer 实现接口开发

implementation 'com.yanzhenjie.andserver:api:2.0.5'
annotationProcessor 'com.yanzhenjie.andserver:processor:2.0.5'
implementation 'com.alibaba:fastjson:1.1.71.android'
package com.yf.douyintool.controller;

import android.text.TextUtils;
import android.util.Log;

import com.alibaba.fastjson.JSONObject;
import com.bytedance.frameworks.core.encrypt.TTEncryptUtils;
import com.google.gson.Gson;
import com.ss.sys.ces.a;
import com.yanzhenjie.andserver.annotation.GetMapping;
import com.yanzhenjie.andserver.annotation.PostMapping;
import com.yanzhenjie.andserver.annotation.RequestBody;
import com.yanzhenjie.andserver.annotation.RestController;
import com.yanzhenjie.andserver.util.MediaType;
import com.yf.douyintool.DeviceUtil;
import com.yf.douyintool.bean.DeviceBean;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TimeZone;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.zip.GZIPOutputStream;

import okhttp3.ConnectionPool;
import okhttp3.FormBody;
import okhttp3.Interceptor;
import okhttp3.OkHttpClient;
import okhttp3.Protocol;
import okhttp3.Request;
import okhttp3.Response;

@RestController
public class RequestController {
    private static final String NULL_MD5_STRING = "00000000000000000000000000000000";
    public String sessionid = "";
    public String xtttoken = "";

    @PostMapping(value = "/request", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public String test(@RequestBody String requestBodyString) {
        JSONObject jsonObject = new JSONObject();
        JSONObject requestBody = JSONObject.parseObject(requestBodyString);
        String ck = requestBody.getString("cookie");
        String url = requestBody.getString("url");
        String postData = requestBody.getString("postData");
        if (ck == null || url == null) {
            jsonObject.put("status_code", 0);
            jsonObject.put("status_msg", "缺少参数!");
            return jsonObject.toJSONString();
        }
        if (url.contains("douplus/order/create")) {
            // 如果是投放订单每次生产不同的 DeviceData
            JSONObject deviceData = getNewDeviceData();
            url = replaceUrlParam(url, "device_id", deviceData.getString("device_id"));
            url = replaceUrlParam(url, "iid", deviceData.getString("install_id"));
//            Log.i("orderCreate", "替换 device_id 成功!");
        }
//        String _ricket = System.currentTimeMillis() + "";
        long time = System.currentTimeMillis() / 1000;
        String p = url.substring(url.indexOf("?") + 1, url.length());
        boolean isPost = postData != null && !postData.equals("");
        String result;
        if (isPost) {
            FormBody.Builder formBody = new FormBody.Builder();
            String STUB = encryption(postData);
            Map<String, String> map = new HashMap<>();
            String[] ks = postData.split("&");
            for (int i = 0; i < ks.length; i++) {
                String[] ur = ks[i].split("=");
                if (ur.length == 1) {
                    map.put(ur[0], "");
                } else {
                    map.put(ur[0], ur[1]);

                }
            }
            for (Map.Entry<String, String> m : map.entrySet()) {
                formBody.add(m.getKey(), m.getValue());
            }
            String s = getXGon(p, STUB, ck, sessionid);
            String XGon = ByteToStr(a.leviathan((int) time, StrToByte(s)));
            result = doPostNet(url, formBody.build(), time, XGon, STUB, ck);
        } else {
            String s = getXGon(p, "", ck, sessionid);
            String XGon = ByteToStr(a.leviathan((int) time, StrToByte(s)));
            result = doGetNet(url, time, XGon, ck);
        }
        jsonObject = JSONObject.parseObject(result);
        if (jsonObject == null) {
            jsonObject = new JSONObject();
            jsonObject.put("status_code", -2);
            jsonObject.put("status_msg", "未知错误!");
        }
        return jsonObject.toJSONString();
    }


    @GetMapping(value = "/getDeviceData", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public String getDeviceData() {
        return getNewDeviceData().toJSONString();
    }

    @GetMapping(value = "/getQuery", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
    public String getQuery() {
        String uuid = DeviceUtil.getRanInt(15);  //设备 id
        String openudid = DeviceUtil.getRanInt(16);  //android_id
        String _rticket = System.currentTimeMillis() + "";   //获取当前时间
        String url = "https://log.snssdk.com/service/2/device_register/?mcc_mnc=46000&ac=wifi&channel=aweGW&aid=1128&app_name=aweme&version_code=550&version_name=5.5.0&device_platform=android&ssmix=a&device_type=SM-G925F&device_brand=samsung&language=zh&os_api=22&os_version=5.1.1&uuid=" + uuid + "&openudid=" + openudid + "&manifest_version_code=550&resolution=720*1280&dpi=192&update_version_code=5502&_rticket=" + _rticket + "&tt_data=a&config_retry=b";
        String stb = url.substring(url.indexOf("?") + 1, url.length());
        String STUB = encryption(stb).toUpperCase();
        String ck = "odin_tt=9c1e0ebae55f3c2d9f71ab2aadce63126022e8960819bace07d441d977ad60eff6312161f546ebfe747528d03d53a161728250938c4287a588d86aa599c284b3; qh[360]=1; install_id=66715314288; ttreq=1$0b4589453328800ed93e002538883aa52da3e1d5";
        int time = (int) (System.currentTimeMillis() / 1000);
        String s = getXGon(url, STUB, ck, null);
        String XGon = ByteToStr(a.leviathan(time, StrToByte(s)));
        String device = getDevice(openudid, uuid);
        JSONObject deviceJson = JSONObject.parseObject(device);
        final okhttp3.RequestBody formBody = okhttp3.RequestBody.create(okhttp3.MediaType.parse("application/octet-stream;tt-data=a"), this.toGzip(device));
        String result = doPostNet(url, formBody, time, XGon, "", ck);
        JSONObject deviceResult = JSONObject.parseObject(result);
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("status_code", 0);
        JSONObject headerJson = deviceJson.getJSONObject("header");
        String data = String.format("os_api=22&device_type=SM-G925F&ssmix=a&manifest_version_code=911&dpi=320&uuid=%s&app_name=aweme&version_name=9.1.1&ts=%d&app_type=normal&ac=wifi&update_version_code=9104&channel=huawei_1&_rticket=%s&device_platform=android&iid=%s&version_code=911&cdid=%s&openudid=%s&device_id=%s&resolution=720*1280&os_version=5.1.1&language=zh&device_brand=OPPO&aid=1128&mcc_mnc=46007",
                uuid, time, _rticket, deviceResult.getString("install_id_str"), headerJson.getString("clientudid"), openudid, deviceResult.getString("device_id_str"));
        jsonObject.put("data", data);
        Log.i("data", data);
        return jsonObject.toJSONString();
    }

    public JSONObject getNewDeviceData() {
        String uuid = DeviceUtil.getRanInt(15);  //设备 id
        String openudid = DeviceUtil.getRanInt(16);  //android_id
        String _rticket = System.currentTimeMillis() + "";   //获取当前时间
        String url = "https://log.snssdk.com/service/2/device_register/?mcc_mnc=46000&ac=wifi&channel=aweGW&aid=1128&app_name=aweme&version_code=550&version_name=5.5.0&device_platform=android&ssmix=a&device_type=SM-G925F&device_brand=samsung&language=zh&os_api=22&os_version=5.1.1&uuid=" + uuid + "&openudid=" + openudid + "&manifest_version_code=550&resolution=720*1280&dpi=192&update_version_code=5502&_rticket=" + _rticket + "&tt_data=a&config_retry=b";
        String stb = url.substring(url.indexOf("?") + 1, url.length());
        String STUB = encryption(stb).toUpperCase();
        String ck = "odin_tt=9c1e0ebae55f3c2d9f71ab2a12ce63c46022e8912819bace07d441d977ad60eff6301161f546ebfe747528d03d53a161728250938c4287a588d86aa599c284b3; qh[360]=1; install_id=66715314288; ttreq=1$0b4589453328800ed93e002538883aa52da3e1d5";
        int time = (int) (System.currentTimeMillis() / 1000);
        String s = getXGon(url, STUB, ck, null);
        String XGon = ByteToStr(a.leviathan(time, StrToByte(s)));
        final okhttp3.RequestBody formBody = okhttp3.RequestBody.create(okhttp3.MediaType.parse("application/octet-stream;tt-data=a"), this.toGzip(this.getDevice(openudid, uuid)));
        String result = doPostNet(url, formBody, time, XGon, "", ck);
        JSONObject jsonObject = JSONObject.parseObject(result);
        if (jsonObject == null) {
            jsonObject = new JSONObject();
            jsonObject.put("status_code", -2);
            jsonObject.put("status_msg", "未知错误!");
        }
        return jsonObject;
    }

    public String getDevice(String openudid, String udid) {
        //DeviceBean
        String Serial_number = DeviceUtil.getRanInt(8);
        DeviceBean deviceBean = new DeviceBean();
        deviceBean.set_gen_time(System.currentTimeMillis() + "");
        deviceBean.setMagic_tag("ss_app_log");
        //HeaderBean
        DeviceBean.HeaderBean headerBean = new DeviceBean.HeaderBean();
        headerBean.setDisplay_name("抖音短视频");
        headerBean.setUpdate_version_code(5502);
        headerBean.setManifest_version_code(550);
        headerBean.setAid(1128);
        headerBean.setChannel("aweGW");
        headerBean.setAppkey("59bfa27c67e59e7d920028d9"); //appkey
        headerBean.setPackageX("com.ss.android.ugc.aweme");
        headerBean.setApp_version("5.5.0");
        headerBean.setVersion_code(550);
        headerBean.setSdk_version("2.5.5.8");
        headerBean.setOs("Android");
        headerBean.setOs_version("5.1.1");
        headerBean.setOs_api(22);
        headerBean.setDevice_model("SM-G925F");
        headerBean.setDevice_brand("samsung");
        headerBean.setDevice_manufacturer("samsung");
        headerBean.setCpu_abi("armeabi-v7a");
        headerBean.setBuild_serial(Serial_number);  ////android.os.Build.SERIAL
        headerBean.setRelease_build("2132ca7_20190321");  // release 版本
        headerBean.setDensity_dpi(192);
        headerBean.setDisplay_density("mdpi");
        headerBean.setResolution("1280x720");
        headerBean.setLanguage("zh");
        headerBean.setMc(DeviceUtil.getMac());  //mac 地址
        headerBean.setTimezone(8);
        headerBean.setAccess("wifi");
        headerBean.setNot_request_sender(0);
        headerBean.setCarrier("China Mobile GSM");
        headerBean.setMcc_mnc("46000");
        headerBean.setRom("eng.se.infra.20181117.120021");  //Build.VERSION.INCREMENTAL
        headerBean.setRom_version("samsung-user 5.1.1 20171130.276299 release-keys");  //Build.DISPLAY
        headerBean.setSig_hash("aea615ab910015038f73c47e45d21466");  //app md5 加密  固定
        headerBean.setDevice_id("");   //获取之后的设备 id
        headerBean.setOpenudid(openudid);  //openudid
        headerBean.setUdid(udid);  //真机的 imei
        headerBean.setClientudid(UUID.randomUUID().toString());  //uuid
        headerBean.setSerial_number(Serial_number);  //android.os.Build.SERIAL
        headerBean.setRegion("CN");
        headerBean.setTz_name("Asia\\/Shanghai");  //timeZone.getID();
        headerBean.setTimezone(28800);  //String.valueOf(timeZone.getOffset(System.currentTimeMillis()) / 1000)
        headerBean.setSim_region("cn");
        List<DeviceBean.HeaderBean.SimSerialNumberBean> sim_serial_number = new ArrayList<>();
        DeviceBean.HeaderBean.SimSerialNumberBean bean = new DeviceBean.HeaderBean.SimSerialNumberBean();
        bean.setSim_serial_number(DeviceUtil.getRanInt(20));
        sim_serial_number.add(bean);
        headerBean.setSim_serial_number(sim_serial_number);
//        Log.i("deviceHeader", headerBean.toString());
        deviceBean.setHeader(headerBean);
        TimeZone timeZone = Calendar.getInstance().getTimeZone();
        timeZone.getID();
        //r
        Gson gson = new Gson();
        return gson.toJson(deviceBean);
    }

    public byte[] toGzip(String r) {
        try {
            byte[] bArr2 = r.getBytes("UTF-8");

            ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(8192);
            GZIPOutputStream gZIPOutputStream = new GZIPOutputStream(byteArrayOutputStream);
            gZIPOutputStream.write(bArr2);
            gZIPOutputStream.close();
            bArr2 = byteArrayOutputStream.toByteArray();
            bArr2 = TTEncryptUtils.a(bArr2, bArr2.length);
            return bArr2;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    public class RetryIntercepter implements Interceptor {

        public int maxRetry;//最大重试次数
        private int retryNum = 0;//假如设置为 3 次重试的话,则最大可能请求 4 次(默认 1 次+3 次重试)

        public RetryIntercepter(int maxRetry) {
            this.maxRetry = maxRetry;
        }

        @Override
        public Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
//            System.out.println("retryNum=" + retryNum);
            boolean isSuccessful;
            Response response = null;
            try {
                response = chain.proceed(request);
                isSuccessful = response.isSuccessful();
            } catch (Exception e) {
                isSuccessful = false;
            }
            while (!isSuccessful && retryNum < maxRetry) {
                retryNum++;
                System.out.println("retryNum=" + retryNum);
                response = chain.proceed(request);
            }

            return response;
        }
    }

    public String doGetNet(String url, long time, String XGon, String ck) {
//        Log.i("XGon", XGon);
//        Log.i("time", String.valueOf(time));
        Request request = new Request.Builder()
                .url(url)
                .get()
                .addHeader("X-SS-REQ-TICKET", System.currentTimeMillis() + "")
                .addHeader("X-Khronos", time + "")
                .addHeader("X-Gorgon", XGon)
                .addHeader("sdk-version", "1")
                .addHeader("Cookie", ck)
                .addHeader("X-Pods", "")
                .addHeader("Connection", "Keep-Alive")
                .addHeader("User-Agent", "okhttp/3.10.0.1")
                .addHeader("x-tt-token", xtttoken)
                .addHeader("Accept-Encoding", "identity")
                .addHeader("Connection", "Upgrade, HTTP2-Settings")
                .addHeader("Upgrade", "h2c")
                .build();
        List<Protocol> protocols = new ArrayList<>();
// protocols.add(Protocol.H2_PRIOR_KNOWLEDGE);
        protocols.add(Protocol.HTTP_2);
        protocols.add(Protocol.HTTP_1_1);
        OkHttpClient okHttpClient = new OkHttpClient.Builder()
                .retryOnConnectionFailure(true)
                .protocols(protocols)
                .connectionPool(new ConnectionPool(10, 30, TimeUnit.SECONDS))
                .addInterceptor(new RetryIntercepter(3))
                .build();
        Response response = null;
        try {
            response = okHttpClient.newCall(request).execute();
        } catch (IOException e) {
            e.printStackTrace();
        }
        //响应成功
        if (response.isSuccessful()) {
            try {
                return response.body().string();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        JSONObject jsonObject = new JSONObject();
        jsonObject.put("status_code", -2);
        jsonObject.put("status_msg", "未知错误!");
        return jsonObject.toJSONString();
    }



    ......主题内容不能超过 20000 个字符
}
package com.yf.douyintool;

import android.content.Context;
import android.util.Log;

import com.yanzhenjie.andserver.AndServer;
import com.yanzhenjie.andserver.Server;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.concurrent.TimeUnit;

/**
 * Created by Zhenjie Yan on 2018/6/9.
 */
public class ServerManager {

    private static final String TAG = "ServerManager";

    private Server mServer;

    /**
     * Create server.
     */
    public ServerManager(Context context) {
        InetAddress inetAddress = null;
        try {
            inetAddress = InetAddress.getByName("0.0.0.0");
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        mServer = AndServer.serverBuilder(context)
                .inetAddress(inetAddress)
                .port(8080)
                .timeout(10, TimeUnit.SECONDS)
                .listener(new Server.ServerListener() {
                    @Override
                    public void onStarted() {
                        // TODO The server started successfully.
                        Log.d(TAG, "onStarted: ");
                    }

                    @Override
                    public void onStopped() {
                        // TODO The server has stopped.
                        Log.d(TAG, "onStarted: ");
                    }

                    @Override
                    public void onException(Exception e) {
                        Log.e(TAG, "onException: ",e );
                        // TODO An exception occurred while the server was starting.
                    }
                })
                .build();
    }

    /**
     * Start server.
     */
    public void startServer() {
        if (mServer.isRunning()) {
            // TODO The server is already up.
        } else {
            mServer.startup();
        }
    }

    /**
     * Stop server.
     */
    public void stopServer() {
        if (mServer.isRunning()) {
            mServer.shutdown();
        } else {
            Log.w("AndServer", "The server has not started yet.");
        }
    }
}

serverManager = new ServerManager(this);
serverManager.startServer();
Log.i("address", NetUtils.getLocalIPAddress()+":8080");
application = this;
Thread.setDefaultUncaughtExceptionHandler(handler);
private Thread.UncaughtExceptionHandler handler = (t, e) -> {
        restartApp(); //发生崩溃异常时,重启应用
    };

测试接口

Cookie 获取方式

有爬取登录接口实现的,但其实有一种简单的方式就是二维码登录,但是涉及到一些隐秘性这里就不公开说明了。

沟通讨论

有问题可以加 QQ:574747417 共同讨论。

2922 次点击
所在节点    Java
1 条回复
SHOWLEE
2020-04-07 15:44:27 +08:00
点赞,加油

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/660002

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX