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 619f9b4b..b74d40fb 100644 --- a/app/src/main/java/io/github/chipppppppppp/lime/LimeOptions.java +++ b/app/src/main/java/io/github/chipppppppppp/lime/LimeOptions.java @@ -23,7 +23,7 @@ public Option(String name, int id, boolean checked) { public Option removeRecommendation = new Option("remove_recommendation", R.string.switch_remove_recommendation, true); public Option removePremiumRecommendation = new Option("remove_premium_recommendation", R.string.switch_remove_premium_recommendation, true); public Option removeServiceLabels = new Option("remove_service_labels", R.string.switch_remove_service_labels, false); - public Option removeAllServices = new Option("remove_services", R.string.switch_remove_service, false); + public Option removeAllServices = new Option("remove_services", R.string.RemoveService, false); public Option removeReplyMute = new Option("remove_reply_mute", R.string.switch_remove_reply_mute, true); public Option redirectWebView = new Option("redirect_webview", R.string.switch_redirect_webview, true); public Option openInBrowser = new Option("open_in_browser", R.string.switch_open_in_browser, false); @@ -35,7 +35,9 @@ public Option(String name, int id, boolean checked) { 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 callTone = new Option("call_tone", R.string.call_tone, false); + public Option call_tone = new Option("call_tone", R.string.call_tone, false); + public Option PhotoAddNotification = new Option("PhotoAddNotification", R.string.PhotoAddNotification, false); + public Option[] options = { removeVoom, @@ -60,6 +62,7 @@ public Option(String name, int id, boolean checked) { blockTracking, stopVersionCheck, outputCommunication, - callTone + call_tone, + PhotoAddNotification }; } 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 86113d5a..c2d2b94e 100644 --- a/app/src/main/java/io/github/chipppppppppp/lime/Main.java +++ b/app/src/main/java/io/github/chipppppppppp/lime/Main.java @@ -22,6 +22,7 @@ import io.github.chipppppppppp.lime.hooks.ModifyResponse; import io.github.chipppppppppp.lime.hooks.OutputRequest; import io.github.chipppppppppp.lime.hooks.OutputResponse; +import io.github.chipppppppppp.lime.hooks.PhotoAddNotification; import io.github.chipppppppppp.lime.hooks.PreventMarkAsRead; import io.github.chipppppppppp.lime.hooks.PreventUnsendMessage; import io.github.chipppppppppp.lime.hooks.RedirectWebView; @@ -30,7 +31,7 @@ import io.github.chipppppppppp.lime.hooks.RemoveIconLabels; import io.github.chipppppppppp.lime.hooks.RemoveIcons; import io.github.chipppppppppp.lime.hooks.RemoveReplyMute; -import io.github.chipppppppppp.lime.hooks.Ringtone; +import io.github.chipppppppppp.lime.hooks.RingTone; import io.github.chipppppppppp.lime.hooks.SendMuteMessage; import io.github.chipppppppppp.lime.hooks.SpoofAndroidId; import io.github.chipppppppppp.lime.hooks.SpoofUserAgent; @@ -66,8 +67,9 @@ public class Main implements IXposedHookLoadPackage, IXposedHookInitPackageResou new ModifyResponse(), new OutputRequest(), new Archived(), - new Ringtone(), - new UnsentCap() + new RingTone(), + new UnsentCap(), + new PhotoAddNotification() }; public void handleLoadPackage(@NonNull XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { diff --git a/app/src/main/java/io/github/chipppppppppp/lime/hooks/PhotoAddNotification.java b/app/src/main/java/io/github/chipppppppppp/lime/hooks/PhotoAddNotification.java new file mode 100644 index 00000000..0d87c61b --- /dev/null +++ b/app/src/main/java/io/github/chipppppppppp/lime/hooks/PhotoAddNotification.java @@ -0,0 +1,207 @@ +package io.github.chipppppppppp.lime.hooks; + +import android.app.Application; +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.database.Cursor; +import android.database.sqlite.SQLiteDatabase; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Environment; + +import java.io.File; +import java.util.Arrays; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XposedHelpers; +import de.robv.android.xposed.callbacks.XC_LoadPackage; +import io.github.chipppppppppp.lime.LimeOptions; + +public class PhotoAddNotification implements IHook { + @Override + public void hook(LimeOptions limeOptions, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { + if (!limeOptions.PhotoAddNotification.checked) return; + + XposedHelpers.findAndHookMethod(Application.class, "onCreate", new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + Application appContext = (Application) param.thisObject; + File dbFile1 = appContext.getDatabasePath("naver_line"); + File dbFile2 = appContext.getDatabasePath("contact"); + + if (dbFile1.exists() && dbFile2.exists()) { + SQLiteDatabase.OpenParams.Builder builder1 = new SQLiteDatabase.OpenParams.Builder(); + builder1.addOpenFlags(SQLiteDatabase.OPEN_READWRITE); + SQLiteDatabase.OpenParams dbParams1 = builder1.build(); + + SQLiteDatabase.OpenParams.Builder builder2 = new SQLiteDatabase.OpenParams.Builder(); + builder2.addOpenFlags(SQLiteDatabase.OPEN_READWRITE); + SQLiteDatabase.OpenParams dbParams2 = builder2.build(); + + SQLiteDatabase db1 = SQLiteDatabase.openDatabase(dbFile1, dbParams1); + SQLiteDatabase db2 = SQLiteDatabase.openDatabase(dbFile2, dbParams2); + + hookNotificationMethods(loadPackageParam, appContext, db1, db2); + } + } + }); + } + private void hookNotificationMethods(XC_LoadPackage.LoadPackageParam loadPackageParam, + Context context, SQLiteDatabase dbGroups, SQLiteDatabase dbContacts) { + XposedHelpers.findAndHookMethod(NotificationManager.class, "notify", + int.class, Notification.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + handleNotificationHook(context, dbGroups, dbContacts, param, false); + } + }); + + XposedHelpers.findAndHookMethod(NotificationManager.class, "notify", + String.class, int.class, Notification.class, new XC_MethodHook() { + @Override + protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + handleNotificationHook(context, dbGroups, dbContacts, param, true); + } + }); + } + + private void handleNotificationHook(Context context, SQLiteDatabase dbGroups, SQLiteDatabase dbContacts, XC_MethodHook.MethodHookParam param, boolean hasTag) { + Notification notification = hasTag ? (Notification) param.args[2] : (Notification) param.args[1]; + String title = getNotificationTitle(notification); + + if (title == null) { + // XposedBridge.log("Notification title is null. Skipping."); + return; + } + + // 通知の本文を取得 + String originalText = getNotificationText(notification); + if (originalText != null && (originalText.contains("写真を送信しました") || originalText.contains("sent a photo"))) { // "写真を送信しました" または "sent a photo" が含まれている場合のみ処理 + // XposedBridge.log("Target notification detected: " + originalText); + + String chatId = resolveChatId(dbGroups, dbContacts, notification); + if (chatId == null) { + // XposedBridge.log("Chat ID not found for title: " + title); + return; + } + + File latestFile = getLatestMessageFile(chatId); + if (latestFile == null) { + // XposedBridge.log("No latest message file found for chat ID: " + chatId); + return; + } + + Bitmap bitmap = loadBitmapFromFile(latestFile); + if (bitmap == null) { + // XposedBridge.log("Failed to load bitmap from file: " + latestFile.getAbsolutePath()); + return; + } + + Notification newNotification = createNotificationWithImageFromFile(context, notification, latestFile, originalText); + + if (hasTag) { + param.args[2] = newNotification; + } else { + param.args[1] = newNotification; + } + + logNotificationDetails("Modified NotificationManager.notify", hasTag ? (int) param.args[1] : -1, newNotification, hasTag ? (String) param.args[0] : null); + } + } + + + private String resolveChatId(SQLiteDatabase dbGroups, SQLiteDatabase dbContacts, Notification notification) { + String subText = notification.extras.getString(Notification.EXTRA_SUB_TEXT); + if (subText != null) { + String groupId = queryDatabase(dbGroups, "SELECT id FROM groups WHERE name =?", subText); + if (groupId != null) { + return groupId; + } + String talkId = queryDatabase(dbContacts, "SELECT mid FROM contacts WHERE profile_name =?", subText); + return talkId; + } + return null; + } + + private File getLatestMessageFile(String chatId) { + File messagesDir = new File(Environment.getExternalStorageDirectory(), + "/Android/data/jp.naver.line.android/files/chats/" + chatId + "/messages"); + if (!messagesDir.exists() || !messagesDir.isDirectory()) { + // XposedBridge.log("Messages directory does not exist: " + messagesDir.getAbsolutePath()); + return null; + } + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + // XposedBridge.log("Sleep interrupted: " + e.getMessage()); + } + + File[] files = messagesDir.listFiles((dir, name) -> !name.endsWith(".thumb") && !name.endsWith(".downloading")); + if (files == null || files.length == 0) { + // XposedBridge.log("No files found in messages directory: " + messagesDir.getAbsolutePath()); + return null; + } + + Arrays.sort(files, (f1, f2) -> Long.compare(f2.lastModified(), f1.lastModified())); + return files[0]; + } + + + private String queryDatabase(SQLiteDatabase db, String query, String... selectionArgs) { + Cursor cursor = db.rawQuery(query, selectionArgs); + String result = null; + if (cursor.moveToFirst()) { + result = cursor.getString(0); + } + cursor.close(); + return result; + } + + private String getNotificationTitle(Notification notification) { + if (notification.extras != null) { + return notification.extras.getString(Notification.EXTRA_TITLE); + } + return null; + } + + private String getNotificationText(Notification notification) { + if (notification.extras != null) { + return notification.extras.getString(Notification.EXTRA_TEXT); + } + return null; + } + + private Bitmap loadBitmapFromFile(File file) { + if (!file.exists()) { + return null; + } + return BitmapFactory.decodeFile(file.getAbsolutePath()); + } + + private Notification createNotificationWithImageFromFile(Context context, Notification original, File imageFile, String originalText) { + Bitmap bitmap = loadBitmapFromFile(imageFile); + if (bitmap == null) { + return original; + } + + Notification.Builder builder = Notification.Builder.recoverBuilder(context, original) + .setStyle(new Notification.BigPictureStyle() + .bigPicture(bitmap) + .setSummaryText(originalText)); + return builder.build(); + } + + private void logNotificationDetails(String method, int id, Notification notification, String tag) { + // XposedBridge.log(method + " called. ID: " + id + (tag != null ? ", Tag: " + tag : "")); + if (notification.extras != null) { + String title = notification.extras.getString(Notification.EXTRA_TITLE); + String text = notification.extras.getString(Notification.EXTRA_TEXT); + // XposedBridge.log("Notification Title: " + (title != null ? title : "No Title")); + // XposedBridge.log("Notification Text: " + (text != null ? text : "No Text")); + } else { + // XposedBridge.log("Notification has no extras."); + } + } +} diff --git a/app/src/main/java/io/github/chipppppppppp/lime/hooks/Ringtone.java b/app/src/main/java/io/github/chipppppppppp/lime/hooks/RingTone.java similarity index 96% rename from app/src/main/java/io/github/chipppppppppp/lime/hooks/Ringtone.java rename to app/src/main/java/io/github/chipppppppppp/lime/hooks/RingTone.java index bfd24ae8..662417ba 100644 --- a/app/src/main/java/io/github/chipppppppppp/lime/hooks/Ringtone.java +++ b/app/src/main/java/io/github/chipppppppppp/lime/hooks/RingTone.java @@ -10,14 +10,14 @@ import de.robv.android.xposed.callbacks.XC_LoadPackage; import io.github.chipppppppppp.lime.LimeOptions; -public class Ringtone implements IHook { +public class RingTone implements IHook { private android.media.Ringtone ringtone = null; private boolean isPlaying = false; @Override public void hook(LimeOptions limeOptions, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { - if (!limeOptions.callTone.checked) return; + if (!limeOptions.call_tone.checked) return; XposedBridge.hookAllMethods( loadPackageParam.classLoader.loadClass(Constants.RESPONSE_HOOK.className), diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index cf7e3245..517e7711 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -3,16 +3,10 @@ OK キャンセル - OK - はい - いいえ - キャンセル - スタンプ - 動画 - 写真 - 再取得 + LINE をクリーンに。 + エラー モジュールが有効化されていません! @@ -23,7 +17,6 @@ Android ID の偽装は自己責任です バージョン不適合のため一部機能が利用できません 最大バージョンに偽装 - 非表示にしたチャットの再表示を無効化 設定を LINE に埋め込まない (設定は同期されません) VOOM アイコンを削除 ウォレットアイコンを削除 @@ -35,41 +28,106 @@ おすすめを削除 LYP プレミアムのおすすめを削除 サービスのラベルを削除 - サービスの項目を削除 通知から \"通知をオフ\" アクションを削除 - WebView をデフォルトのブラウザにリダイレクト + WebView を既定のブラウザにリダイレクト ブラウザアプリで開く 常に既読をつけない 常にミュートメッセージとして送信 送信取り消しを拒否 - トーク画面右上のメニューにある「未読のまま閲覧」スイッチを削除 - トラッキング通信をブロック + 「未読のまま閲覧」スイッチを削除 + + トラッキング通信をブロック(この機能はクラッシュを引き起こす可能性があります) LINE バージョンの確認を停止 通信内容をログに出力 (開発者用) リクエストを改変 レスポンスを改変 コピー - 貼り付け - 着信音を鳴らす(LSPatch用) + ペースト + + + LINEバージョンの偽装は自己責任です + OS 名 + OS バージョン + LINEバージョン + + デバイス名 未読のまま閲覧 - + + + + + + バックアップ バックアップの作成に失敗しました - 確認 + 確認 クラスが見つかりません + 確認済みのメッセージ メッセージを削除 削除されたメッセージ 内容がバックアップファイルに移動されました + ファイルの内容が削除されました ファイルを作成できませんでした ファイルの削除に失敗しました ファイルの移動に失敗しました ファイルの保存に失敗しました ファイルが見つかりません - %s の読み取りに失敗しました - 本当に削除しますか? + BackUpFile.txtの読み取りに失敗しました + 本当に削除しますか + + 何もバックアップされていません - 正しく取得できませんでした。\nアプリを再起動してください - \ No newline at end of file + 正しく取得できませんでした。アプリを再起動してください + ok + + はい + いいえ + キャンセル + スタンプ + 動画 + 写真 + 再取得 + 非表示にしたチャットの再表示を無効化 + サービスの項目を削除 + 送信したメッセージの既読者の確認 + LsPatch用「未読のまま閲覧」スイッチを表示 + ミュートメッセージの無効化 + 登録しているグループ名の通知をオフにする + ナビゲーションのボタンの色を黒にする、ダークテーマをピュアダークにする + プロフィールの更新を通知しない + 画像通知の際画像を添付します⚠3秒受信が遅延します + トーク履歴のバックアップを開始 + トーク画像フォルダのバックアップを開始 + バックアップを開始 + 自動バックアップが成功しました + 自動バックアップ中にエラーが発生しました + トーク画像フォルダのバックアップが成功しました + トーク画像フォルダのバックアップ中にエラーが発生しました + バックアップ + リストア + トーク画像のリストア + ミュート中のグループ + バックアップが成功しました + バックアップ中にエラーが発生しました + バックアップファイルが見つかりません + リストアが成功しました + リストア中にエラーが発生しました + chatテーブルのリストアが成功しました + chatテーブルのリストアが失敗しました + トーク画像フォルダのバックアップが成功しました + トーク画像フォルダのバックアップ中にエラーが発生しました + トーク画像フォルダのバックアップフォルダが見つかりません + トーク画像フォルダのフォルダの作成に失敗しました + トーク画像フォルダの復元が成功しました + トーク画像フォルダの復元中にエラーが発生しました + 既読者 + 削除 + 既読データが削除されました。 + LsPatch用 着信音を鳴らす + 更新されたプロフィールを削除 + 定期的にバックアップ + Set ID + diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a2048acf..d272a0c2 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -1,10 +1,11 @@ - - + 確定 取消 + 整潔的 LINE + 錯誤 模組未啟用! @@ -40,6 +41,87 @@ 修改回應 複製 貼上 + + 欺騙 LINE 版本的風險由您自行承擔 + 作業系統名稱 + 作業系統版本 + 應用程式版本 + 設備名稱 保持未讀 - \ No newline at end of file + + + 停用靜音訊息 + 開始備份通話記錄 + 開始備份談話圖片資料夾 + 開始備份 + 自動備份成功 + 自動備份時發生錯誤 + 談話圖片資料夾已成功備份 + 備份談話圖片資料夾時出錯 + 備份 + 恢復 + 恢復談話影像 + 群組靜音 + 備份成功 + 備份時發生錯誤 + 找不到備份文件 + 恢復成功 + 恢復期間發生錯誤 + 聊天表恢復成功 + 聊天表恢復失敗 + 談話圖片資料夾備份成功 + 備份談話圖片資料夾時出錯 + 找不到談話圖片資料夾的備份資料夾 + 無法為談話圖片資料夾建立資料夾 + 恢復談話圖片資料夾成功 + 恢復談話圖片資料夾時出錯 + 讀者 + 刪除 + 讀取資料已被刪除 + 將您訂閱的群組的通知靜音 + + 發送圖片通知時附上圖片⚠接收圖片通知會有1~3秒的延遲 + + + Backup + 建立備份失敗 + 確認 + 找不到類別 + 已確認訊息 + 已刪除的訊息 + 已刪除的訊息 + 內容移至備份文件 + 文件內容已刪除 + 無法建立文件 + 刪除檔案失敗 + 移動文件失敗 + 儲存檔案失敗 + 找不到文件 + 無法讀取 BackUpFile.txt + 您確定要刪除嗎 + 沒有任何備份 + 無法正常獲取。請重新啟動應用程式 + 好的 + 設定ID + 是的 + + 取消 + 貼紙 + 影片 + 照片 + 重新獲得 + + 停用取消隱藏隱藏聊天 + + 不通知個人資料更新 + + 對於 LsPatch 鈴聲 + 刪除服務項目 + 檢查已發送訊息的讀者人數 + 刪除更新的設定文件 + 使導航按鈕顏色為黑色,深色主題純深色 + 顯示 LsPatch 的保留未讀開關 + 將您訂閱的群組的通知靜音 + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dc44ca9f..fd3cc7c2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,19 +1,12 @@ - - - LIME + + LIMEs OK Cancel - OK - Yes - No - Cancel - Stickers - Videos - Photos - Reacquisition + Clean LINE. + Error Module not enabled! @@ -24,7 +17,6 @@ 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 @@ -35,29 +27,44 @@ Remove ads Remove recommendations Remove LYP Premium recommendations - Remove tbe service labels - Remove services item - Remove the \"Mute chat\" action from notifications + Remove the 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 + 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) + Do not notify of profile updates + + For LsPatch Ringtones + Remove service item + Check the readership of sent messages + Remove updated profiles\n + Make navigation button color black, dark theme pure dark + Show "Keep Unread" switch for LsPatch + Recurring backups\n + + Modify requests Modify responses Copy Paste - Notify ringtone of LSPatch + + Disable mute messages + Mute notifications for your subscribed groups + + Attach an image when sending an image notification ⚠There will be a 1~3 second delay in receiving image notifications + Keep unread - + Backup Failed to create backup - Confirm + Confirm Class not found Confirmed messages Deleted messages @@ -69,8 +76,61 @@ Failed to move file Failed to save file File not found - Failed to read %s - Really want to delete it? + Failed to read BackUpFile.txt + Are you sure you want to delete Nothing backed up - Could not get properly.\nPlease restart the app - \ No newline at end of file + Could not get properly. Please restart the app + OK + Set ID + Yes + No + Cancel + Stickers + Videos + Photos + Reacquire + + Disable unhiding hidden chats + + + + Device Name + Spoofing LINE Version is at your own risk + OS name + OS version + App version + + + + + + Start backup of talk history + Start backup of talk picture folder + Start backup + Error occurred while backing up the talk picture folder + Backup + Restore + Restore talk image + Group on mute + Backup was successful + Error occurred during backup + Backup file not found + Restore succeeded + Error occurred during restore + Chat table restored successfully + Chat table restoration failed + Backup of talk picture folder succeeded + Error occurred while backing up the talk picture folder + The backup folder for the talk picture folder was not found + Failure to create folder for talk picture folder + Restore talk picture folder succeeded + Error occurred while restoring the talk picture folder + + + Reader + delete + Read data has been deleted + Automatic backup succeeded + Error occurred during automatic backup + The talk picture folder was successfully backed up + diff --git a/settings.gradle b/settings.gradle index 83518587..12b4681a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,7 +4,7 @@ pluginManagement { mavenCentral() } plugins { - id 'com.android.application' version '8.7.2' apply false + id 'com.android.application' version '8.6.0' apply false } } dependencyResolutionManagement {