From 1634438d5b6fe577ca7cd1f7b306749c6efef2f1 Mon Sep 17 00:00:00 2001 From: areteruhiro Date: Fri, 1 Nov 2024 00:39:09 +0900 Subject: [PATCH] =?UTF-8?q?=E8=87=AA=E5=8B=95=E3=83=90=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=82=A2=E3=83=83=E3=83=97=E6=A9=9F=E8=83=BD(24H=E9=96=93?= =?UTF-8?q?=E9=9A=94)=E2=86=90=E9=96=93=E9=9A=94=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=81=AF=E3=81=BE=E3=81=A0=E3=81=A7=E3=81=8D=E3=81=BE=E3=81=9B?= =?UTF-8?q?=E3=82=93=20=E6=97=A2=E8=AA=AD=E3=81=AB=E3=81=A4=E3=81=84?= =?UTF-8?q?=E3=81=A6=E3=81=AE=E6=A9=9F=E8=83=BD=E6=94=B9=E5=96=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 2 +- .../java/io/github/hiro/lime/LimeOptions.java | 4 + .../main/java/io/github/hiro/lime/Main.java | 2 + .../hiro/lime/hooks/AutomaticBackup.java | 108 +++++++++++ .../github/hiro/lime/hooks/EmbedOptions.java | 28 ++- .../io/github/hiro/lime/hooks/KeepUnread.java | 3 - .../github/hiro/lime/hooks/ReadChecker.java | 181 ++++++++++-------- app/src/main/res/values/strings.xml | 1 + 8 files changed, 226 insertions(+), 103 deletions(-) create mode 100644 app/src/main/java/io/github/hiro/lime/hooks/AutomaticBackup.java diff --git a/app/build.gradle b/app/build.gradle index 0f43692c..c8a64b6b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ android { minSdk 28 targetSdk 34 versionCode 15 - versionName "1.12.2" + versionName "1.12.3" multiDexEnabled false proguardFiles += 'proguard-rules.pro' buildConfigField 'String', 'HOOK_TARGET_VERSION', '"141700420"' diff --git a/app/src/main/java/io/github/hiro/lime/LimeOptions.java b/app/src/main/java/io/github/hiro/lime/LimeOptions.java index 003e904e..50ea97f9 100644 --- a/app/src/main/java/io/github/hiro/lime/LimeOptions.java +++ b/app/src/main/java/io/github/hiro/lime/LimeOptions.java @@ -41,6 +41,9 @@ public Option(String name, int id, boolean checked) { public Option ReadChecker = new Option("ReadChecker", R.string.ReadChecker, false); public Option RemoveNotification = new Option("RemoveNotification", R.string.removeNotification, false); public Option NaviColor = new Option("NaviColor", R.string.NaviColor, false); + public Option AutomaticBackup = new Option("AutomaticBackup", R.string.AutomaticBackup, false); + + public Option[] options = { @@ -71,6 +74,7 @@ public Option(String name, int id, boolean checked) { outputCommunication, calltone, NaviColor, + AutomaticBackup }; } diff --git a/app/src/main/java/io/github/hiro/lime/Main.java b/app/src/main/java/io/github/hiro/lime/Main.java index 5ee3591e..d3e07075 100644 --- a/app/src/main/java/io/github/hiro/lime/Main.java +++ b/app/src/main/java/io/github/hiro/lime/Main.java @@ -15,6 +15,7 @@ import de.robv.android.xposed.callbacks.XC_LayoutInflated; import de.robv.android.xposed.callbacks.XC_LoadPackage; import io.github.hiro.lime.hooks.AddRegistrationOptions; +import io.github.hiro.lime.hooks.AutomaticBackup; import io.github.hiro.lime.hooks.BlockTracking; import io.github.hiro.lime.hooks.CheckHookTargetVersion; import io.github.hiro.lime.hooks.Constants; @@ -79,6 +80,7 @@ public class Main implements IXposedHookLoadPackage, IXposedHookInitPackageResou new ReadChecker(), new NaviColor(), new KeepUnreadLSpatch(), + new AutomaticBackup() }; diff --git a/app/src/main/java/io/github/hiro/lime/hooks/AutomaticBackup.java b/app/src/main/java/io/github/hiro/lime/hooks/AutomaticBackup.java new file mode 100644 index 00000000..45329b90 --- /dev/null +++ b/app/src/main/java/io/github/hiro/lime/hooks/AutomaticBackup.java @@ -0,0 +1,108 @@ +package io.github.hiro.lime.hooks; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.os.Environment; +import android.util.Log; +import android.widget.Toast; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.channels.FileChannel; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XposedBridge; +import de.robv.android.xposed.callbacks.XC_LoadPackage; +import io.github.hiro.lime.LimeOptions; +import io.github.hiro.lime.hooks.IHook; + +public class AutomaticBackup implements IHook { + + private static final String TAG = "AutomaticBackup"; + private ScheduledExecutorService scheduler; + private static final String PREFS_NAME = "BackupPrefs"; + public static final String KEY_BACKUP_INTERVAL = "backup_interval_in_millis"; + private static final long DAILY_INTERVAL = 24 * 60 * 60 * 1000; + + @Override + public void hook(LimeOptions limeOptions, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { + if (!limeOptions.AutomaticBackup.checked) return; + + XposedBridge.hookAllMethods(Activity.class, "onCreate", new XC_MethodHook() { + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + Activity activity = (Activity) param.thisObject; + Context appContext = activity.getApplicationContext(); + if (scheduler == null || scheduler.isShutdown()) { + startScheduledBackup(appContext); + } + } + }); + } + + private void startScheduledBackup(Context appContext) { + // 1日ごとにバックアップを行う + if (scheduler == null) { + scheduler = Executors.newScheduledThreadPool(1); + scheduler.scheduleAtFixedRate(() -> checkAndBackupChatHistory(appContext), 0, DAILY_INTERVAL, TimeUnit.MILLISECONDS); + } + } + + private void checkAndBackupChatHistory(Context appContext) { + File backupDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "LimeBackup"); + File[] backupFiles = backupDir.listFiles((dir, name) -> name.startsWith("naver_line_backup_")); + + long currentTime = System.currentTimeMillis(); + long lastBackupTime = 0; + + if (backupFiles != null && backupFiles.length > 0) { + File latestBackupFile = backupFiles[0]; + for (File file : backupFiles) { + if (file.lastModified() > latestBackupFile.lastModified()) { + latestBackupFile = file; + } + } + lastBackupTime = latestBackupFile.lastModified(); + } + + // 最後のバックアップから指定された間隔が経過しているか確認 + if (currentTime - lastBackupTime >= DAILY_INTERVAL) { + backupChatHistory(appContext); + + } + } + + private void backupChatHistory(Context appContext) { + File originalDbFile = appContext.getDatabasePath("naver_line"); + File backupDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "LimeBackup"); + if (!backupDir.exists() && !backupDir.mkdirs()) { + return; + } + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); + File backupFileWithTimestamp = new File(backupDir, "naver_line_backup_" + timeStamp + ".db"); + + try (FileChannel source = new FileInputStream(originalDbFile).getChannel()) { + try (FileChannel destinationWithTimestamp = new FileOutputStream(backupFileWithTimestamp).getChannel()) { + destinationWithTimestamp.transferFrom(source, 0, source.size()); + } + showToast(appContext, "自動バックアップが成功しました"); // トーストをUIスレッドで表示 + + } catch (IOException e) { + showToast(appContext, "自動バックアップ中にエラーが発生しました: " + e.getMessage()); + } + } + + // トーストをUIスレッドで表示するヘルパーメソッド + private void showToast(final Context context, final String message) { + new android.os.Handler(context.getMainLooper()).post(() -> + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + ); + } +} \ No newline at end of file diff --git a/app/src/main/java/io/github/hiro/lime/hooks/EmbedOptions.java b/app/src/main/java/io/github/hiro/lime/hooks/EmbedOptions.java index 44808d48..2dba3e3f 100644 --- a/app/src/main/java/io/github/hiro/lime/hooks/EmbedOptions.java +++ b/app/src/main/java/io/github/hiro/lime/hooks/EmbedOptions.java @@ -2,6 +2,8 @@ import static android.content.ContentValues.TAG; +import static io.github.hiro.lime.hooks.AutomaticBackup.KEY_BACKUP_INTERVAL; + import android.app.AlertDialog; import android.content.ClipData; import android.content.ClipboardManager; @@ -22,12 +24,15 @@ import android.view.Gravity; import android.view.View; import android.view.ViewGroup; +import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.LinearLayout; import android.widget.ScrollView; +import android.widget.Spinner; import android.widget.Switch; +import android.widget.TextView; import android.widget.Toast; import java.io.File; @@ -227,6 +232,7 @@ public void onClick(View v) { layout.addView(restorefolderButton); + builder.setPositiveButton(R.string.positive_button, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { @@ -463,39 +469,31 @@ public void onClick(View view) { ); } - - private void addButton(LinearLayout layout, String buttonText, View.OnClickListener listener) { - Button button = new Button(layout.getContext()); - button.setText(buttonText); - button.setOnClickListener(listener); - layout.addView(button); - } - private void backupChatHistory(Context appContext) { File originalDbFile = appContext.getDatabasePath("naver_line"); - + File backupDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "LimeBackup"); - + if (!backupDir.exists()) { if (!backupDir.mkdirs()) { Log.e(TAG, "Failed to create backup directory: " + backupDir.getAbsolutePath()); return; } } - + String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); String backupFileNameWithTimestamp = "naver_line_backup_" + timeStamp + ".db"; String backupFileNameFixed = "naver_line_backup.db"; - + File backupFileWithTimestamp = new File(backupDir, backupFileNameWithTimestamp); File backupFileFixed = new File(backupDir, backupFileNameFixed); - + try (FileChannel source = new FileInputStream(originalDbFile).getChannel()) { try (FileChannel destinationWithTimestamp = new FileOutputStream(backupFileWithTimestamp).getChannel()) { destinationWithTimestamp.transferFrom(source, 0, source.size()); } - - source.position(0); + + source.position(0); try (FileChannel destinationFixed = new FileOutputStream(backupFileFixed).getChannel()) { destinationFixed.transferFrom(source, 0, source.size()); } diff --git a/app/src/main/java/io/github/hiro/lime/hooks/KeepUnread.java b/app/src/main/java/io/github/hiro/lime/hooks/KeepUnread.java index 0ecb288d..0f600d7e 100644 --- a/app/src/main/java/io/github/hiro/lime/hooks/KeepUnread.java +++ b/app/src/main/java/io/github/hiro/lime/hooks/KeepUnread.java @@ -133,9 +133,6 @@ private Drawable scaleDrawable(Drawable drawable, int width, int height) { return new BitmapDrawable(scaledBitmap); } - - - private void saveStateToFile(Context context, boolean state) { String filename = "keep_unread_state.txt"; try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) { diff --git a/app/src/main/java/io/github/hiro/lime/hooks/ReadChecker.java b/app/src/main/java/io/github/hiro/lime/hooks/ReadChecker.java index 1b2689a2..cbc4f580 100644 --- a/app/src/main/java/io/github/hiro/lime/hooks/ReadChecker.java +++ b/app/src/main/java/io/github/hiro/lime/hooks/ReadChecker.java @@ -1,12 +1,12 @@ package io.github.hiro.lime.hooks; + import android.app.Activity; import android.app.AlertDialog; import android.app.Application; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteReadOnlyDatabaseException; import android.os.Bundle; import android.view.Gravity; import android.view.View; @@ -15,8 +15,6 @@ import android.widget.FrameLayout; import android.widget.ScrollView; import android.widget.TextView; - - import java.io.File; import java.text.SimpleDateFormat; import java.util.ArrayList; @@ -29,13 +27,13 @@ import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; - 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.hiro.lime.LimeOptions; + public class ReadChecker implements IHook { private SQLiteDatabase limeDatabase; private SQLiteDatabase db3 = null; // クラスフィールドとして宣言 @@ -43,59 +41,56 @@ public class ReadChecker implements IHook { private boolean shouldHookOnCreate = false; private String currentGroupId = null; + @Override public void hook(LimeOptions limeOptions, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { - if (!limeOptions.ReadChecker.checked) return; XposedHelpers.findAndHookMethod(Application.class, "onCreate", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Application appContext = (Application) param.thisObject; + if (appContext == null) { return; } + File dbFile3 = appContext.getDatabasePath("naver_line"); File dbFile4 = appContext.getDatabasePath("contact"); - File dbFile5 = appContext.getDatabasePath("lime_data.db"); // lime_data.dbのパス + if (dbFile3.exists() && dbFile4.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(); + db3 = SQLiteDatabase.openDatabase(dbFile3, dbParams1); // フィールドに代入 db4 = SQLiteDatabase.openDatabase(dbFile4, dbParams2); // フィールドに代入 - // lime_data.dbが存在する場合に開く - if (dbFile5.exists()) { - SQLiteDatabase.OpenParams.Builder builder3 = new SQLiteDatabase.OpenParams.Builder(); - builder3.addOpenFlags(SQLiteDatabase.OPEN_READWRITE); - SQLiteDatabase.OpenParams dbParams3 = builder3.build(); - - - } // データベースの初期化 initializeLimeDatabase(appContext); + // データの取得 Catcha(loadPackageParam, db3, db4); // ここでフィールドを使って呼び出す } } }); - // ChatHistoryRequestクラスをフックしてgroupIdを取得 + Class chatHistoryRequestClass = XposedHelpers.findClass("com.linecorp.line.chat.request.ChatHistoryRequest", loadPackageParam.classLoader); XposedHelpers.findAndHookMethod(chatHistoryRequestClass, "getChatId", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { String chatId = (String) param.getResult(); - // XposedBridge.log(chatId); + //XposedBridge.log(chatId); if (isGroupExists(chatId)) { shouldHookOnCreate = true; currentGroupId = chatId; // groupIdを保存 @@ -106,12 +101,15 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable { } }); + Class chatHistoryActivityClass = XposedHelpers.findClass("jp.naver.line.android.activity.chathistory.ChatHistoryActivity", loadPackageParam.classLoader); XposedHelpers.findAndHookMethod(chatHistoryActivityClass, "onCreate", Bundle.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { + + if (shouldHookOnCreate && currentGroupId != null) { Activity activity = (Activity) param.thisObject; addButton(activity); @@ -120,38 +118,36 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable { }); + + } + + private boolean isGroupExists(String groupId) { if (limeDatabase == null) { - // XposedBridge.log("Database is not initialized."); + XposedBridge.log("Database is not initialized."); return false; } + String query = "SELECT 1 FROM group_messages WHERE group_id = ?"; Cursor cursor = limeDatabase.rawQuery(query, new String[]{groupId}); boolean exists = cursor.moveToFirst(); cursor.close(); + return exists; } + // ボタンを追加するメソッド private void addButton(Activity activity) { - if (currentGroupId == null || limeDatabase == null) { - return; - } - - // group_nameがnullの場合はボタンを作成しない - String groupName = queryDatabase(limeDatabase, "SELECT group_name FROM group_messages WHERE group_id=?", currentGroupId); - if (groupName == null) { - return; // group_nameがnullならボタンを作成しない - } - Button button = new Button(activity); button.setText("既読データ表示"); + FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams( FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT @@ -160,6 +156,7 @@ private void addButton(Activity activity) { frameParams.topMargin = 150; button.setLayoutParams(frameParams); + button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { @@ -169,13 +166,13 @@ public void onClick(View v) { } }); + ViewGroup layout = activity.findViewById(android.R.id.content); layout.addView(button); } private void showDataForGroupId(Activity activity, String groupId) { if (limeDatabase == null) { - XposedBridge.log("Database is not initialized."); return; } @@ -183,44 +180,49 @@ private void showDataForGroupId(Activity activity, String groupId) { String query = "SELECT server_id, content, created_time FROM group_messages WHERE group_id=? ORDER BY created_time ASC"; Cursor cursor = limeDatabase.rawQuery(query, new String[]{groupId}); - Map contentMap = new HashMap<>(); + // server_idごとのデータを保持するマップ + Map dataItemMap = new HashMap<>(); + // データをマップに格納 while (cursor.moveToNext()) { String serverId = cursor.getString(0); String content = cursor.getString(1); String createdTime = cursor.getString(2); + // 既読者リストを取得 List talkNameList = getTalkNamesForServerId(serverId); - if (contentMap.containsKey(content)) { - DataItem existingItem = contentMap.get(content); - Set uniqueTalkNames = new HashSet<>(existingItem.talkNames); - uniqueTalkNames.addAll(talkNameList); // 既読者リストをマージ - existingItem.talkNames = new ArrayList<>(uniqueTalkNames); // SetからArrayListに戻す + // 既存のデータがある場合は、既読者を追加しないように重複を排除 + if (dataItemMap.containsKey(serverId)) { + DataItem existingItem = dataItemMap.get(serverId); + existingItem.talkNames.addAll(talkNameList); } else { - contentMap.put(content, new DataItem(serverId, content, createdTime, new ArrayList<>(talkNameList))); + // 新しいDataItemを作成 + DataItem dataItem = new DataItem(serverId, content, createdTime); + dataItem.talkNames.addAll(talkNameList); + dataItemMap.put(serverId, dataItem); } } cursor.close(); + // 結果を表示 StringBuilder resultBuilder = new StringBuilder(); - for (DataItem item : contentMap.values()) { + for (DataItem item : dataItemMap.values()) { resultBuilder.append("Content: ").append(item.content != null ? item.content : "Media").append("\n"); - resultBuilder.append("Time: ").append(item.createdTime).append("\n"); + resultBuilder.append("Created Time: ").append(item.createdTime).append("\n"); - List talkNames = item.talkNames; - if (!talkNames.isEmpty()) { - resultBuilder.append("既読者 (").append(talkNames.size()).append("):\n"); - for (String talkName : talkNames) { - resultBuilder.append("- ").append(talkName).append("\n"); // リスト形式で表示 + if (!item.talkNames.isEmpty()) { + resultBuilder.append("既読者 (").append(item.talkNames.size()).append("):\n"); + for (String talkName : item.talkNames) { + resultBuilder.append("- ").append(talkName).append("\n"); } } else { - resultBuilder.append("既読者: なし\n"); + resultBuilder.append("No talk names found.\n"); } - resultBuilder.append("\n"); // 次のcontentごとに改行 + resultBuilder.append("\n"); } - // スクロール可能なレイアウトを作成 + // ScrollViewを使って結果を表示 TextView textView = new TextView(activity); textView.setText(resultBuilder.toString()); textView.setPadding(20, 20, 20, 20); @@ -230,36 +232,31 @@ private void showDataForGroupId(Activity activity, String groupId) { AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle("Group Data"); - builder.setView(scrollView); // ScrollViewを設定 + builder.setView(scrollView); builder.setPositiveButton("OK", null); builder.show(); } - - // データ項目を保持するクラス private static class DataItem { String serverId; String content; String createdTime; - List talkNames; + Set talkNames; - DataItem(String serverId, String content, String createdTime, List talkNames) { + DataItem(String serverId, String content, String createdTime) { this.serverId = serverId; this.content = content; this.createdTime = createdTime; - this.talkNames = talkNames; + this.talkNames = new HashSet<>(); } } - - private List getTalkNamesForServerId(String serverId) { List talkNames = new ArrayList<>(); if (limeDatabase == null) { - //XposedBridge.log("Database is not initialized."); return talkNames; } - String query = "SELECT talk_name FROM group_messages WHERE server_id=?"; + String query = "SELECT DISTINCT talk_name FROM group_messages WHERE server_id=?"; Cursor cursor = limeDatabase.rawQuery(query, new String[]{serverId}); while (cursor.moveToNext()) { @@ -272,6 +269,7 @@ private List getTalkNamesForServerId(String serverId) { return talkNames; } + private void Catcha(XC_LoadPackage.LoadPackageParam loadPackageParam, SQLiteDatabase db3, SQLiteDatabase db4) { try { XposedBridge.hookAllMethods( @@ -281,12 +279,13 @@ private void Catcha(XC_LoadPackage.LoadPackageParam loadPackageParam, SQLiteData @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { String paramValue = param.args[1].toString(); - // XposedBridge.log(paramValue); + XposedBridge.log(paramValue); + if (paramValue.contains("type:NOTIFIED_READ_MESSAGE")) { - // XposedBridge.log(paramValue); - // データを取得してデータベースに保存 - fetchDataAndSave(db3, db4, paramValue); // db5も渡す + XposedBridge.log(paramValue); + // Fetch data and save it to the database + fetchDataAndSave(db3, db4, paramValue); // db3とdb4を渡す } } } @@ -296,27 +295,29 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable { } } + private void fetchDataAndSave(SQLiteDatabase db3, SQLiteDatabase db4, String paramValue) { - String groupId = extractGroupId(paramValue); + // param1, param2, param3をそれぞれ抽出 + + String serverId = extractServerId(paramValue); String checkedUser = extractCheckedUser(paramValue); - if (serverId == null || groupId == null || checkedUser == null) { + + if (serverId == null || checkedUser == null) { + // XposedBridge.log("Missing parameters: serverId=" + serverId + ", groupId=" + groupId + ", checkedUser=" + checkedUser); return; } + + String groupId = queryDatabase(db3, "SELECT chat_id FROM chat_history WHERE server_id=?", serverId); String content = queryDatabase(db3, "SELECT content FROM chat_history WHERE server_id=?", serverId); String groupName = queryDatabase(db3, "SELECT name FROM groups WHERE id=?", groupId); - - if (groupName == null) { - return; - } - String talkName = queryDatabase(db4, "SELECT profile_name FROM contacts WHERE mid=?", checkedUser); String timeEpochStr = queryDatabase(db3, "SELECT created_time FROM chat_history WHERE server_id=?", serverId); String timeFormatted = formatMessageTime(timeEpochStr); - // データベースに保存 + saveData(groupId, serverId, checkedUser, groupName, content, talkName, timeFormatted); } @@ -328,8 +329,9 @@ private String formatMessageTime(String timeEpochStr) { return sdf.format(new Date(timeEpoch)); } + private String extractGroupId(String paramValue) { - Pattern pattern = Pattern.compile("to:([a-zA-Z0-9]+)"); + Pattern pattern = Pattern.compile("param1:([a-zA-Z0-9]+)"); Matcher matcher = pattern.matcher(paramValue); return matcher.find() ? matcher.group(1) : null; } @@ -341,6 +343,7 @@ private String extractServerId(String paramValue) { return matcher.find() ? matcher.group(1) : null; } + private String extractCheckedUser(String paramValue) { Pattern pattern = Pattern.compile("param2:([a-zA-Z0-9]+)"); Matcher matcher = pattern.matcher(paramValue); @@ -349,9 +352,10 @@ private String extractCheckedUser(String paramValue) { + private String queryDatabase(SQLiteDatabase db, String query, String... selectionArgs) { if (db == null) { - // XposedBridge.log("Database is not initialized."); + XposedBridge.log("Database is not initialized."); return null; } Cursor cursor = db.rawQuery(query, selectionArgs); @@ -363,20 +367,18 @@ private String queryDatabase(SQLiteDatabase db, String query, String... selectio return result; } + private void initializeLimeDatabase(Context context) { File dbFile = new File(context.getFilesDir(), "lime_data.db"); + limeDatabase = SQLiteDatabase.openOrCreateDatabase(dbFile, null); - // データベースを読み書き可能で開く。例外が発生する場合は、書き込みモードで再試行 - try { - limeDatabase = SQLiteDatabase.openOrCreateDatabase(dbFile, null); - // XposedBridge.log("Database opened successfully."); - } catch (SQLiteReadOnlyDatabaseException e) { - // XposedBridge.log("Read-only database exception occurred, retrying in writable mode."); - // 書き込み可能なモードで開き直す - limeDatabase = SQLiteDatabase.openDatabase(dbFile.getPath(), null, SQLiteDatabase.OPEN_READWRITE); - } - // group_messages テーブルの作成または更新 + // 既存のテーブルがあれば削除する(デモ用に行いますが、実際のアプリでは注意が必要です) + String dropTableIfExists = "DROP TABLE IF EXISTS group_messages;"; + limeDatabase.execSQL(dropTableIfExists); + + + // 新しいテーブルを作成 String createGroupTable = "CREATE TABLE IF NOT EXISTS group_messages (" + "group_id TEXT NOT NULL," + "server_id TEXT NOT NULL," + @@ -384,42 +386,53 @@ private void initializeLimeDatabase(Context context) { "group_name TEXT," + "content TEXT," + "talk_name TEXT," + - "created_time TEXT," + // created_time カラムを追加 + "created_time TEXT," + // ここで created_time カラムを追加 "PRIMARY KEY (group_id, server_id, checked_user)" + ");"; + limeDatabase.execSQL(createGroupTable); - // XposedBridge.log("Database initialized and group_messages table created."); + XposedBridge.log("Database initialized and group_messages table created."); } + + private void saveData(String groupId, String serverId, String checkedUser, String groupName, String content, String talkName, String createdTime) { if (limeDatabase == null) { + return; } + + String checkQuery = "SELECT COUNT(*) FROM group_messages WHERE server_id=? AND checked_user=?"; Cursor cursor = limeDatabase.rawQuery(checkQuery, new String[]{serverId, checkedUser}); cursor.moveToFirst(); int count = cursor.getInt(0); cursor.close(); + // 既存のデータがない場合のみ書き込む if (count > 0) { - //XposedBridge.log("Data already exists for Server_Id: " + serverId + ", Checked_user: " + checkedUser + ". Skipping save."); + //XposedBridge.log("Data already exists for Server_Id: " + serverId + ", Checked_user: " + checkedUser + ". Skipping save."); return; } + String insertOrUpdateQuery = "INSERT INTO group_messages (group_id, server_id, checked_user, group_name, content, talk_name, created_time) " + "VALUES (?, ?, ?, ?, ?, ?, ?);"; // created_timeを追加 + limeDatabase.execSQL(insertOrUpdateQuery, new Object[]{groupId, serverId, checkedUser, groupName, content, talkName, createdTime}); // created_timeも追加 - // XposedBridge.log("Saved to DB: Group_Id: " + groupId + ", Server_id: " + serverId + ", Checked_user: " + checkedUser + - // ", Group_Name: " + groupName + ", Content: " + content + ", Talk_Name: " + talkName + ", Created_Time: " + createdTime); + + XposedBridge.log("Saved to DB: Group_Id: " + groupId + ", Server_id: " + serverId + ", Checked_user: " + checkedUser + + ", Group_Name: " + groupName + ", Content: " + content + ", Talk_Name: " + talkName + ", Created_Time: " + createdTime); } } + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ee8887c9..b8cd57e7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -94,4 +94,5 @@ 更新されたプロフィールを削除\n ナビゲーションのボタンの色を黒にする LsPatch用「未読のまま閲覧」スイッチを表示 + 定期的にバックアップ\n