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..c3a3ceae 100644 --- a/app/src/main/java/io/github/chipppppppppp/lime/LimeOptions.java +++ b/app/src/main/java/io/github/chipppppppppp/lime/LimeOptions.java @@ -33,6 +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, @@ -51,9 +53,11 @@ public Option(String name, int id, boolean checked) { preventMarkAsRead, preventUnsendMessage, sendMuteMessage, + Archived, removeKeepUnread, blockTracking, stopVersionCheck, outputCommunication + }; } 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..a64e6da2 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; @@ -33,6 +34,7 @@ import io.github.chipppppppppp.lime.hooks.SpoofAndroidId; import io.github.chipppppppppp.lime.hooks.SpoofUserAgent; + public class Main implements IXposedHookLoadPackage, IXposedHookInitPackageResources, IXposedHookZygoteInit { public static String modulePath; @@ -61,7 +63,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 deleted file mode 100644 index 87713198..00000000 --- a/app/src/main/res/values-ja/strings.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - OK - キャンセル - - - LINE をクリーンに。 - - - エラー - モジュールが有効化されていません! - 設定 - アプリを再起動しています... - 強制停止とキャッシュの削除が必要です - Android ID を偽装 - Android ID の偽装は自己責任です - バージョン不適合のため一部機能が利用できません - 最大バージョンに偽装 - 設定を LINE に埋め込まない (設定は同期されません) - VOOM アイコンを削除 - ウォレットアイコンを削除 - ニュースまたは通話アイコンを削除 - ボトムバーのアイコンを均等に配置 - ボトムバーのアイコンの当たり判定を拡張する - ボトムバーのアイコンのラベルを削除 - 広告を削除 - おすすめを削除 - LYP プレミアムのおすすめを削除 - サービスのラベルを削除 - 通知から \"通知をオフ\" アクションを削除 - WebView を既定のブラウザにリダイレクト - ブラウザアプリで開く - 常に既読をつけない - 常にミュートメッセージとして送信 - 送信取り消しを拒否 - トーク画面右上のメニューにある「未読のまま閲覧」スイッチを削除 - トラッキング通信をブロック - LINE バージョンの確認を停止 - 通信内容をログに出力 (開発者用) - リクエストを改変 - レスポンスを改変 - コピー - ペースト - - - 未読のまま閲覧 - diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml deleted file mode 100644 index 5e406085..00000000 --- a/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,48 +0,0 @@ - - LIME - - OK - Cancel - - - Clean LINE. - - - Error - Module not enabled! - Options - Restarting now... - Forced stop and cache clearing are required - Spoof Android ID - Spoofing Android ID is at your own risk - Some functions are not available due to version incompatibility - Disguise as the highest version - Do not embed options in LINE (options will not be synchronized) - Remove the VOOM icon - Remove the wallet icon - Remove the news or call icon - Distribute the icons on the bottom bar evenly - Extend the clickable area of the icons on the bottom bar - Remove the icon labels on the bottom bar - Remove ads - Remove recommendations - Remove LYP Premium recommendations - Remove tbe service labels - Remove the \"Mute chat\" action from notifications - Redirect WebView to the default browser - Open in the browser app - Always prevent marking as read - Always send mute messages - Prevent messages from unsending - Remove the \"Keep unread\" switch from the menu in the top right corner of the chat tab - Block tracking communications - Stop checking LINE version - Output communication contents to log (for developers) - Modify requests - Modify responses - Copy - Paste - - - Keep unread -