diff --git a/app/src/main/java/io/github/chipppppppppp/lime/LimeOptions.java b/app/src/main/java/io/github/chipppppppppp/lime/LimeOptions.java index 547b0541..a6747fbf 100644 --- a/app/src/main/java/io/github/chipppppppppp/lime/LimeOptions.java +++ b/app/src/main/java/io/github/chipppppppppp/lime/LimeOptions.java @@ -33,7 +33,8 @@ public Option(String name, int id, boolean checked) { public Option blockTracking = new Option("block_tracking", R.string.switch_block_tracking, false); public Option stopVersionCheck = new Option("stop_version_check", R.string.switch_stop_version_check, false); public Option outputCommunication = new Option("output_communication", R.string.switch_output_communication, false); - + public Option Archived = new Option("Archived_message", R.string.switch_archived, false); + public Option[] options = { removeVoom, removeWallet, @@ -50,6 +51,7 @@ public Option(String name, int id, boolean checked) { openInBrowser, preventMarkAsRead, preventUnsendMessage, + Archived, sendMuteMessage, removeKeepUnread, blockTracking, diff --git a/app/src/main/java/io/github/chipppppppppp/lime/Main.java b/app/src/main/java/io/github/chipppppppppp/lime/Main.java index 4f913893..48c0a3cb 100644 --- a/app/src/main/java/io/github/chipppppppppp/lime/Main.java +++ b/app/src/main/java/io/github/chipppppppppp/lime/Main.java @@ -11,6 +11,7 @@ import de.robv.android.xposed.callbacks.XC_LayoutInflated; import de.robv.android.xposed.callbacks.XC_LoadPackage; import io.github.chipppppppppp.lime.hooks.AddRegistrationOptions; +import io.github.chipppppppppp.lime.hooks.Archived; import io.github.chipppppppppp.lime.hooks.BlockTracking; import io.github.chipppppppppp.lime.hooks.CheckHookTargetVersion; import io.github.chipppppppppp.lime.hooks.Constants; @@ -61,7 +62,8 @@ public class Main implements IXposedHookLoadPackage, IXposedHookInitPackageResou new KeepUnread(), new BlockTracking(), new ModifyResponse(), - new OutputRequest() + new OutputRequest(), + new Archived() }; public void handleLoadPackage(@NonNull XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { diff --git a/app/src/main/java/io/github/chipppppppppp/lime/hooks/Archived.java b/app/src/main/java/io/github/chipppppppppp/lime/hooks/Archived.java new file mode 100644 index 00000000..7e99b824 --- /dev/null +++ b/app/src/main/java/io/github/chipppppppppp/lime/hooks/Archived.java @@ -0,0 +1,229 @@ +package io.github.chipppppppppp.lime.hooks; + +import static io.github.chipppppppppp.lime.Main.limeOptions; + +import android.app.AndroidAppHelper; +import android.app.Application; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XposedBridge; +import de.robv.android.xposed.XposedHelpers; +import de.robv.android.xposed.callbacks.XC_LoadPackage; +import io.github.chipppppppppp.lime.LimeOptions; + +public class Archived implements IHook { + + @Override + public void hook(LimeOptions limeOptions, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { + if (!limeOptions.Archived.checked) return; + + XposedBridge.hookAllMethods(Application.class, "onCreate", new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + Application appContext = (Application) param.thisObject; + if (appContext == null) return; + + File dbFile = appContext.getDatabasePath("naver_line"); + if (!dbFile.exists()) return; + + SQLiteDatabase.OpenParams dbParams = new SQLiteDatabase.OpenParams.Builder() + .addOpenFlags(SQLiteDatabase.OPEN_READWRITE) + .build(); + SQLiteDatabase db = SQLiteDatabase.openDatabase(dbFile, dbParams); + + hookSAMethod(loadPackageParam, db, appContext); + hookMessageDeletion(loadPackageParam, appContext, db, appContext); + } + }); + } + + private void hookMessageDeletion(XC_LoadPackage.LoadPackageParam loadPackageParam, Context context, SQLiteDatabase db, Context moduleContext) { + if (!limeOptions.Archived.checked) return; + + try { + XposedBridge.hookAllMethods( + loadPackageParam.classLoader.loadClass(Constants.REQUEST_HOOK.className), + Constants.REQUEST_HOOK.methodName, + new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + String paramValue = param.args[1].toString(); + String talkId = extractTalkId(paramValue); + if (talkId == null) return; + + if (paramValue.contains("hidden:true")) { + saveTalkIdToFile(talkId, context); + } else if (paramValue.contains("hidden:false")) { + deleteTalkIdFromFile(talkId, context); + } + + updateArchivedChatsFromFile(db, context, moduleContext); + } + }); + } catch (ClassNotFoundException ignored) { + } + } + + private void deleteTalkIdFromFile(String talkId, Context moduleContext) { + File file = new File(moduleContext.getFilesDir(), "hidelist.txt"); + if (!file.exists()) return; + + try { + List lines = new ArrayList<>(); + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + if (!line.trim().equals(talkId)) { + lines.add(line); + } + } + } + + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + for (String remainingLine : lines) { + writer.write(remainingLine); + writer.newLine(); + } + } + } catch (IOException ignored) { + } + } + + private void hookSAMethod(XC_LoadPackage.LoadPackageParam loadPackageParam, SQLiteDatabase db, Context context) { + Class targetClass = XposedHelpers.findClass("SA.Q", loadPackageParam.classLoader); + + XposedBridge.hookAllMethods(targetClass, "invokeSuspend", new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + Context appContext = AndroidAppHelper.currentApplication(); + if (appContext == null) return; + + File dbFile = appContext.getDatabasePath("naver_line"); + if (!dbFile.exists()) return; + + SQLiteDatabase.OpenParams dbParams = new SQLiteDatabase.OpenParams.Builder() + .addOpenFlags(SQLiteDatabase.OPEN_READWRITE) + .build(); + SQLiteDatabase db = SQLiteDatabase.openDatabase(dbFile, dbParams); + + List chatIds = readChatIdsFromFile(appContext, context); + for (String chatId : chatIds) { + if (!chatId.isEmpty()) { + updateIsArchived(db, chatId); + } + } + + if (db != null) db.close(); + } + }); + } + + private List readChatIdsFromFile(Context context, Context moduleContext) { + List chatIds = new ArrayList<>(); + File file = new File(moduleContext.getFilesDir(), "hidelist.txt"); + + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + chatIds.add(line.trim()); + } + } catch (IOException ignored) { + } + + return chatIds; + } + + private void saveTalkIdToFile(String talkId, Context moduleContext) { + File file = new File(moduleContext.getFilesDir(), "hidelist.txt"); + + try { + if (!file.exists()) file.createNewFile(); + + List existingIds = new ArrayList<>(); + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + existingIds.add(line.trim()); + } + } + + if (!existingIds.contains(talkId.trim())) { + try (FileWriter writer = new FileWriter(file, true)) { + writer.write(talkId + "\n"); + } + } + } catch (IOException ignored) { + } + } + + private void updateArchivedChatsFromFile(SQLiteDatabase db, Context context, Context moduleContext) { + File file = new File(moduleContext.getFilesDir(), "hidelist.txt"); + if (!file.exists()) return; + + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String chatId; + while ((chatId = reader.readLine()) != null) { + chatId = chatId.trim(); + if (!chatId.isEmpty()) { + updateIsArchived(db, chatId); + } + } + } catch (IOException ignored) { + } + } + + private String extractTalkId(String paramValue) { + String requestPrefix = "setChatHiddenStatusRequest:SetChatHiddenStatusRequest(reqSeq:0, chatMid:"; + int startIndex = paramValue.indexOf(requestPrefix); + if (startIndex == -1) return null; + + int chatMidStartIndex = startIndex + requestPrefix.length(); + int endIndex = paramValue.indexOf(",", chatMidStartIndex); + if (endIndex == -1) endIndex = paramValue.indexOf(")", chatMidStartIndex); + + return endIndex != -1 ? paramValue.substring(chatMidStartIndex, endIndex).trim() : null; + } + + private String queryDatabase(SQLiteDatabase db, String query, String... selectionArgs) { + if (db == null) return null; + + try (Cursor cursor = db.rawQuery(query, selectionArgs)) { + if (cursor.moveToFirst()) return cursor.getString(0); + } catch (Exception ignored) { + } + return null; + } + + private void updateDatabase(SQLiteDatabase db, String query, Object... bindArgs) { + if (db == null) return; + + try { + db.beginTransaction(); + db.execSQL(query, bindArgs); + db.setTransactionSuccessful(); + } catch (Exception ignored) { + } finally { + db.endTransaction(); + } + } + + private void updateIsArchived(SQLiteDatabase db, String chatId) { + String updateQuery = "UPDATE chat SET is_archived = 1 WHERE chat_id = ?"; + updateDatabase(db, updateQuery, chatId); + + String selectQuery = "SELECT is_archived FROM chat WHERE chat_id = ?"; + queryDatabase(db, selectQuery, chatId); + } +} diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 87713198..298dfb06 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -17,6 +17,7 @@ Android ID の偽装は自己責任です バージョン不適合のため一部機能が利用できません 最大バージョンに偽装 + 非表示にしたチャットの再表示を無効化 設定を LINE に埋め込まない (設定は同期されません) VOOM アイコンを削除 ウォレットアイコンを削除 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5e406085..abe055ca 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,6 +17,7 @@ Spoofing Android ID is at your own risk Some functions are not available due to version incompatibility Disguise as the highest version + Disable unhiding hidden chats Do not embed options in LINE (options will not be synchronized) Remove the VOOM icon Remove the wallet icon