diff --git a/app/build.gradle b/app/build.gradle index a66c37e..cf73647 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ android { minSdk 28 targetSdk 35 versionCode 11501 - versionName "1.15.01" + versionName "1.16.10beta" multiDexEnabled false proguardFiles += 'proguard-rules.pro' buildConfigField 'String', 'HOOK_TARGET_VERSION', '"141910383"' @@ -71,4 +71,4 @@ dependencies { tasks.withType(JavaCompile).configureEach { options.compilerArgs << "-Xlint:deprecation" << "-Xlint:unchecked" -} +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8930d88..bae01b9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,6 +1,8 @@ - + + + { - if (isChecked) switchView.setEnabled(true); - else { - switchView.setChecked(false); - switchView.setEnabled(false); - } - }); - switchView.setEnabled(limeOptions.redirectWebView.checked); - } - - layout.addView(switchView); - - - } - - { - final String script = new String(Base64.decode(prefs.getString("encoded_js_modify_request", ""), Base64.NO_WRAP)); - - LinearLayout layoutModifyRequest = new LinearLayout(context); - layoutModifyRequest.setLayoutParams(new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.MATCH_PARENT)); - layoutModifyRequest.setOrientation(LinearLayout.VERTICAL); - layoutModifyRequest.setPadding(Utils.dpToPx(20, context), Utils.dpToPx(20, context), Utils.dpToPx(20, context), Utils.dpToPx(20, context)); - - EditText editText = new EditText(context); - editText.setLayoutParams(new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT)); - editText.setTypeface(Typeface.MONOSPACE); - editText.setInputType(InputType.TYPE_CLASS_TEXT | - InputType.TYPE_TEXT_FLAG_MULTI_LINE | - InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | - InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE); - editText.setMovementMethod(new ScrollingMovementMethod()); - editText.setTextIsSelectable(true); - editText.setHorizontallyScrolling(true); - editText.setVerticalScrollBarEnabled(true); - editText.setHorizontalScrollBarEnabled(true); - editText.setText(script); - - layoutModifyRequest.addView(editText); - - LinearLayout buttonLayout = new LinearLayout(context); - LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT); - buttonParams.topMargin = Utils.dpToPx(10, context); - buttonLayout.setLayoutParams(buttonParams); - buttonLayout.setOrientation(LinearLayout.HORIZONTAL); - - Button copyButton = new Button(context); - copyButton.setText(R.string.button_copy); - copyButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("", editText.getText().toString()); - clipboard.setPrimaryClip(clip); - } - }); - - buttonLayout.addView(copyButton); - - Button pasteButton = new Button(context); - pasteButton.setText(R.string.button_paste); - pasteButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - if (clipboard != null && clipboard.hasPrimaryClip()) { - ClipData clip = clipboard.getPrimaryClip(); - if (clip != null && clip.getItemCount() > 0) { - CharSequence pasteData = clip.getItemAt(0).getText(); - editText.setText(pasteData); - } - } - } - }); - - buttonLayout.addView(pasteButton); - layoutModifyRequest.addView(buttonLayout); - ScrollView scrollView = new ScrollView(context); - scrollView.addView(layoutModifyRequest); - AlertDialog.Builder builder = new AlertDialog.Builder(context) - .setTitle(R.string.modify_request); - builder.setView(scrollView); - Button backupButton = new Button(context); - buttonParams.topMargin = Utils.dpToPx(20, context); - backupButton.setLayoutParams(buttonParams); - backupButton.setText(moduleContext.getResources().getString(R.string.Back_Up)); - backupButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - backupChatHistory(context,moduleContext); - } - }); - layout.addView(backupButton); - Button restoreButton = new Button(context); - restoreButton.setLayoutParams(buttonParams); - restoreButton.setText(moduleContext.getResources().getString(R.string.Restore)); - restoreButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - restoreChatHistory(context,moduleContext); - } - }); - layout.addView(restoreButton); - Button backupfolderButton = new Button(context); - backupfolderButton.setLayoutParams(buttonParams); - backupfolderButton.setText(moduleContext.getResources().getString(R.string.Talk_Picture_Back_up)); - backupfolderButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - backupChatsFolder(context,moduleContext); - } - }); - layout.addView(backupfolderButton); - Button restorefolderButton = new Button(context); - restorefolderButton.setLayoutParams(buttonParams); - restorefolderButton.setText(moduleContext.getResources().getString(R.string.Picure_Restore)); - restorefolderButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - restoreChatsFolder(context,moduleContext); - } - }); - layout.addView(restorefolderButton); - if (limeOptions.MuteGroup.checked) { - Button MuteGroups_Button = new Button(context); - MuteGroups_Button.setLayoutParams(buttonParams); - MuteGroups_Button.setText(moduleContext.getResources().getString(R.string.Mute_Group)); - MuteGroups_Button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - MuteGroups_Button(context,moduleContext); - } - }); - layout.addView(MuteGroups_Button); - } - - Button KeepUnread_Button = new Button(context); - KeepUnread_Button.setLayoutParams(buttonParams); - KeepUnread_Button.setText(moduleContext.getResources().getString(R.string.edit_margin_settings)); - KeepUnread_Button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - KeepUnread_Button(context,moduleContext); - } - }); - layout.addView(KeepUnread_Button); - builder.setPositiveButton(R.string.positive_button, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - String code = editText.getText().toString(); - if (!code.equals(script)) { - prefs.edit().putString("encoded_js_modify_request", Base64.encodeToString(code.getBytes(), Base64.NO_WRAP)).commit(); - Toast.makeText(context.getApplicationContext(), context.getString(R.string.restarting), Toast.LENGTH_SHORT).show(); - Process.killProcess(Process.myPid()); - context.startActivity(new Intent().setClassName(Constants.PACKAGE_NAME, "jp.naver.line.android.activity.SplashActivity")); - } - } - }); - builder.setNegativeButton(R.string.negative_button, null); + } - builder.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - editText.setText(script); - } - }); + private void Cancel_Message_Button(Context context, Context moduleContext) { + // フォルダのパスを設定 + File dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "LimeBackup/Setting"); + if (!dir.exists()) { + if (!dir.mkdirs()) { + Toast.makeText(context, "Failed to create directory", Toast.LENGTH_SHORT).show(); + return; + } + } - AlertDialog dialog = builder.create(); - Button button = new Button(context); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT); - params.topMargin = Utils.dpToPx(20, context); - button.setLayoutParams(params); - button.setText(R.string.modify_request); - button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - dialog.show(); - } - }); - layout.addView(button); - } + // ファイルのパスを設定 + File file = new File(dir, "canceled_message.txt"); - { - final String script = new String(Base64.decode(prefs.getString("encoded_js_modify_response", ""), Base64.NO_WRAP)); + // ファイルが存在しない場合、デフォルトのメッセージを設定 + if (!file.exists()) { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + String defaultMessage = moduleContext.getResources().getString(R.string.canceled_message_txt); + writer.write(defaultMessage); + } catch (IOException e) { + Toast.makeText(context, "Failed to create file: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + return; + } + } - LinearLayout layoutModifyResponse = new LinearLayout(context); - layoutModifyResponse.setLayoutParams(new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.MATCH_PARENT)); - layoutModifyResponse.setOrientation(LinearLayout.VERTICAL); - layoutModifyResponse.setPadding(Utils.dpToPx(20, context), Utils.dpToPx(20, context), Utils.dpToPx(20, context), Utils.dpToPx(20, context)); - EditText editText = new EditText(context); - editText.setLayoutParams(new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT)); - editText.setTypeface(Typeface.MONOSPACE); - editText.setInputType(InputType.TYPE_CLASS_TEXT | - InputType.TYPE_TEXT_FLAG_MULTI_LINE | - InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS | - InputType.TYPE_TEXT_FLAG_IME_MULTI_LINE); - editText.setMovementMethod(new ScrollingMovementMethod()); - editText.setTextIsSelectable(true); - editText.setHorizontallyScrolling(true); - editText.setVerticalScrollBarEnabled(true); - editText.setHorizontalScrollBarEnabled(true); - editText.setText(script); - layoutModifyResponse.addView(editText); - LinearLayout buttonLayout = new LinearLayout(context); - LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.MATCH_PARENT, - LinearLayout.LayoutParams.WRAP_CONTENT); - buttonParams.topMargin = Utils.dpToPx(10, context); - buttonLayout.setLayoutParams(buttonParams); - buttonLayout.setOrientation(LinearLayout.HORIZONTAL); - - Button copyButton = new Button(context); - copyButton.setText(R.string.button_copy); - copyButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText("", editText.getText().toString()); - clipboard.setPrimaryClip(clip); - } - }); - - buttonLayout.addView(copyButton); - - Button pasteButton = new Button(context); - pasteButton.setText(R.string.button_paste); - pasteButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - if (clipboard != null && clipboard.hasPrimaryClip()) { - ClipData clip = clipboard.getPrimaryClip(); - if (clip != null && clip.getItemCount() > 0) { - CharSequence pasteData = clip.getItemAt(0).getText(); - editText.setText(pasteData); - } - } - } - }); - - - buttonLayout.addView(pasteButton); - layoutModifyResponse.addView(buttonLayout); - ScrollView scrollView = new ScrollView(context); - scrollView.addView(layoutModifyResponse); - AlertDialog.Builder builder = new AlertDialog.Builder(context) - .setTitle(R.string.modify_response); - builder.setView(scrollView); - builder.setPositiveButton(R.string.positive_button, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - String code = editText.getText().toString(); - if (!code.equals(script)) { - prefs.edit().putString("encoded_js_modify_response", Base64.encodeToString(code.getBytes(), Base64.NO_WRAP)).commit(); - Toast.makeText(context.getApplicationContext(), context.getString(R.string.restarting), Toast.LENGTH_SHORT).show(); - Process.killProcess(Process.myPid()); - context.startActivity(new Intent().setClassName(Constants.PACKAGE_NAME, "jp.naver.line.android.activity.SplashActivity")); - } - } - }); - - builder.setNegativeButton(R.string.negative_button, null); - - builder.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - editText.setText(script); - } - }); - AlertDialog dialog = builder.create(); - Button button = new Button(context); - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( - LinearLayout.LayoutParams.WRAP_CONTENT, - LinearLayout.LayoutParams.WRAP_CONTENT); - params.topMargin = Utils.dpToPx(20, context); - button.setLayoutParams(params); - button.setText(R.string.modify_response); - button.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - dialog.show(); - } - }); - - layout.addView(button); - } - - ScrollView scrollView = new ScrollView(context); - scrollView.addView(layout); - AlertDialog.Builder builder = new AlertDialog.Builder(context) - .setTitle(R.string.options_title); - builder.setView(scrollView); - builder.setPositiveButton(R.string.positive_button, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - boolean optionChanged = false; - for (int i = 0; i < limeOptions.options.length; ++i) { - Switch switchView = (Switch) layout.getChildAt(i); - if (limeOptions.options[i].checked != switchView.isChecked()) { - optionChanged = true; - } - prefs.edit().putBoolean(limeOptions.options[i].name, switchView.isChecked()).commit(); - } - - if (optionChanged) { - Toast.makeText(context.getApplicationContext(), context.getString(R.string.restarting), Toast.LENGTH_SHORT).show(); - Process.killProcess(Process.myPid()); - context.startActivity(new Intent().setClassName(Constants.PACKAGE_NAME, "jp.naver.line.android.activity.SplashActivity")); - } - } - }); - - builder.setNegativeButton(R.string.negative_button, null); - - builder.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - for (int i = 0; i < limeOptions.options.length; ++i) { - Switch switchView = (Switch) layout.getChildAt(i); - switchView.setChecked(limeOptions.options[i].checked); - } - } - }); - AlertDialog dialog = builder.create(); - -// 新しい項目ボタンを作成 - android.view.View view = (android.view.View) param.args[0]; - android.widget.Button newButton = new android.widget.Button(context); - newButton.setText(R.string.app_name); // ボタンのテキストを設定 - -// ボタンのレイアウトパラメータを設定 - android.widget.FrameLayout.LayoutParams layoutParams = new android.widget.FrameLayout.LayoutParams( - android.widget.FrameLayout.LayoutParams.WRAP_CONTENT, // 幅 - android.widget.FrameLayout.LayoutParams.WRAP_CONTENT // 高さ - ); - layoutParams.gravity = Gravity.BOTTOM | Gravity.END; // 右下に配置 - layoutParams.bottomMargin = Utils.dpToPx(16, context); // 下マージンを16dpに設定 - layoutParams.rightMargin = Utils.dpToPx(16, context); // 右マージンを16dpに設定 - - newButton.setLayoutParams(layoutParams); - -// ボタンのクリックリスナーを設定 - newButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - dialog.show(); // ボタンクリックで AlertDialog を表示 - } - }); - -// ボタンをビューに追加 - if (view instanceof android.view.ViewGroup) { - android.view.ViewGroup viewGroup1 = (android.view.ViewGroup) view; - - // FrameLayout を作成してボタンを追加 - android.widget.FrameLayout frameLayout = new android.widget.FrameLayout(context); - frameLayout.setLayoutParams(new android.view.ViewGroup.LayoutParams( - android.view.ViewGroup.LayoutParams.MATCH_PARENT, - android.view.ViewGroup.LayoutParams.MATCH_PARENT - )); - frameLayout.addView(newButton); - - // FrameLayout を親ビューに追加 - viewGroup1.addView(frameLayout); - } - } + // ファイルの内容を読み取る + StringBuilder fileContent = new StringBuilder(); + if (file.exists()) { + try (BufferedReader reader = new BufferedReader(new FileReader(file))) { + String line; + while ((line = reader.readLine()) != null) { + fileContent.append(line).append("\n"); } + } catch (IOException e) { + Toast.makeText(context, "Failed to read file: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + return; + } + } - ); + // EditText の設定 + final EditText editText = new EditText(context); + editText.setText(fileContent.toString().trim()); // 不要な改行を削除 + editText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_MULTI_LINE); + editText.setMinLines(10); + editText.setGravity(Gravity.TOP); + // Save ボタンの設定 + LinearLayout.LayoutParams buttonParams = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); + buttonParams.setMargins(16, 16, 16, 16); + Button saveButton = new Button(context); + saveButton.setText(moduleContext.getResources().getString(R.string.options_title)); // リソースからテキストを取得 + saveButton.setLayoutParams(buttonParams); + saveButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { + writer.write(editText.getText().toString()); + Toast.makeText(context, "Saved successfully", Toast.LENGTH_SHORT).show(); + } catch (IOException e) { + Toast.makeText(context, "Failed to save file: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + } + } + }); + + // レイアウトの設定 + LinearLayout layout = new LinearLayout(context); + layout.setOrientation(LinearLayout.VERTICAL); + layout.addView(editText); + layout.addView(saveButton); + + // AlertDialog の設定 + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setTitle(moduleContext.getResources().getString(R.string.canceled_message)); // リソースからタイトルを取得 + builder.setView(layout); + builder.setNegativeButton(moduleContext.getResources().getString(R.string.cancel), null); // リソースからキャンセルボタンのテキストを取得 + builder.show(); } public int getStatusBarHeight(Context context) { int result = 0; diff --git a/app/src/main/java/io/github/hiro/lime/hooks/ModifyRequest.java b/app/src/main/java/io/github/hiro/lime/hooks/ModifyRequest.java index f2ad148..e5552cd 100644 --- a/app/src/main/java/io/github/hiro/lime/hooks/ModifyRequest.java +++ b/app/src/main/java/io/github/hiro/lime/hooks/ModifyRequest.java @@ -21,7 +21,8 @@ public class ModifyRequest implements IHook { new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - final String script = new String(Base64.decode(Main.xPrefs.getString("encoded_js_modify_request", ""), Base64.NO_WRAP)); + CustomPreferences customPreferences = new CustomPreferences(); + final String script = new String(Base64.decode(customPreferences.getSetting("encoded_js_modify_request", ""), Base64.NO_WRAP)); Context ctx = Context.enter(); ctx.setOptimizationLevel(-1); try { diff --git a/app/src/main/java/io/github/hiro/lime/hooks/ModifyResponse.java b/app/src/main/java/io/github/hiro/lime/hooks/ModifyResponse.java index d28ad02..02d6222 100644 --- a/app/src/main/java/io/github/hiro/lime/hooks/ModifyResponse.java +++ b/app/src/main/java/io/github/hiro/lime/hooks/ModifyResponse.java @@ -21,14 +21,20 @@ public class ModifyResponse implements IHook { new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { - final String script = new String(Base64.decode(Main.xPrefs.getString("encoded_js_modify_response", ""), Base64.NO_WRAP)); + CustomPreferences customPreferences = new CustomPreferences(); + + final String script = new String(Base64.decode(customPreferences.getSetting("encoded_js_modify_response", ""), Base64.NO_WRAP)); + Context ctx = Context.enter(); - ctx.setOptimizationLevel(-1); + ctx.setOptimizationLevel(-1); // 最適化レベルを無効化 try { Scriptable scope = ctx.initStandardObjects(); + Object jsData = Context.javaToJS(new Communication(Communication.Type.RESPONSE, param.args[0].toString(), param.args[1]), scope); + ScriptableObject.putProperty(scope, "data", jsData); ScriptableObject.putProperty(scope, "console", Context.javaToJS(new Console(), scope)); + ctx.evaluateString(scope, script, "Script", 1, null); } catch (Exception e) { XposedBridge.log(e.toString()); diff --git a/app/src/main/java/io/github/hiro/lime/hooks/RedirectWebView.java b/app/src/main/java/io/github/hiro/lime/hooks/RedirectWebView.java index 968bc35..91b10fe 100644 --- a/app/src/main/java/io/github/hiro/lime/hooks/RedirectWebView.java +++ b/app/src/main/java/io/github/hiro/lime/hooks/RedirectWebView.java @@ -3,11 +3,11 @@ package io.github.hiro.lime.hooks; import android.app.Activity; import android.content.Intent; import android.net.Uri; +import android.support.customtabs.CustomTabsIntent; import android.view.View; import android.view.ViewGroup; import android.webkit.WebView; -import androidx.browser.customtabs.CustomTabsIntent; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedHelpers; diff --git a/app/src/main/java/io/github/hiro/lime/hooks/SpoofAndroidId.java b/app/src/main/java/io/github/hiro/lime/hooks/SpoofAndroidId.java index 16ebd6b..b2e9db0 100644 --- a/app/src/main/java/io/github/hiro/lime/hooks/SpoofAndroidId.java +++ b/app/src/main/java/io/github/hiro/lime/hooks/SpoofAndroidId.java @@ -7,13 +7,21 @@ import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage; import io.github.hiro.lime.LimeOptions; -import io.github.hiro.lime.Main; public class SpoofAndroidId implements IHook { @Override public void hook(LimeOptions limeOptions, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { - if (!Main.xPackagePrefs.getBoolean("spoof_android_id", false)) return; + // CustomPreferences を初期化 + io.github.hiro.lime.hooks.CustomPreferences customPreferences = new io.github.hiro.lime.hooks.CustomPreferences(); + + + // 設定を確認 + if (!Boolean.parseBoolean(customPreferences.getSetting("spoof_android_id", "false"))) { + return; // 設定が無効な場合は何もしない + } + + // Settings.Secure.getString メソッドをフック XposedHelpers.findAndHookMethod( Settings.Secure.class, "getString", @@ -22,11 +30,13 @@ public class SpoofAndroidId implements IHook { new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { + // ANDROID_ID が要求された場合 if (param.args[1].toString().equals(Settings.Secure.ANDROID_ID)) { + // 偽の ANDROID_ID を返す param.setResult("0000000000000000"); } } } ); } -} +} \ No newline at end of file diff --git a/app/src/main/java/io/github/hiro/lime/hooks/SpoofUserAgent.java b/app/src/main/java/io/github/hiro/lime/hooks/SpoofUserAgent.java index 4f109a2..0049bb7 100644 --- a/app/src/main/java/io/github/hiro/lime/hooks/SpoofUserAgent.java +++ b/app/src/main/java/io/github/hiro/lime/hooks/SpoofUserAgent.java @@ -1,22 +1,28 @@ package io.github.hiro.lime.hooks; import android.content.Context; -import android.content.SharedPreferences; 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; -import io.github.hiro.lime.Main; public class SpoofUserAgent implements IHook { private boolean hasLoggedSpoofedUserAgent = false; @Override public void hook(LimeOptions limeOptions, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { - if (!Main.xPackagePrefs.getBoolean("android_secondary", false)) return; + // CustomPreferences を初期化 + io.github.hiro.lime.hooks.CustomPreferences customPreferences = new io.github.hiro.lime.hooks.CustomPreferences(); + + // 設定を確認 + if (!Boolean.parseBoolean(customPreferences.getSetting("android_secondary", "false"))) { + return; // 設定が無効な場合は何もしない + } + + // ターゲットメソッドをフック XposedHelpers.findAndHookMethod( loadPackageParam.classLoader.loadClass(Constants.USER_AGENT_HOOK.className), Constants.USER_AGENT_HOOK.methodName, @@ -24,16 +30,17 @@ public class SpoofUserAgent implements IHook { new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - SharedPreferences prefs = Main.xPackagePrefs; - - String device = prefs.getString("device_name", "ANDROID"); - String androidVersion = prefs.getString("android_version", "14.16.0"); - String osName = prefs.getString("os_name", "Android OS"); - String osVersion = prefs.getString("os_version", "14"); + // 設定から値を取得 + String device = customPreferences.getSetting("device_name", "ANDROID"); + String androidVersion = customPreferences.getSetting("android_version", "14.16.0"); + String osName = customPreferences.getSetting("os_name", "Android OS"); + String osVersion = customPreferences.getSetting("os_version", "14"); + // 偽の User-Agent を生成 String spoofedUserAgent = device + "\t" + androidVersion + "\t" + osName + "\t" + osVersion; param.setResult(spoofedUserAgent); + // ログに出力(初回のみ) if (!hasLoggedSpoofedUserAgent) { XposedBridge.log("Spoofed User-Agent: " + spoofedUserAgent); hasLoggedSpoofedUserAgent = true; @@ -42,4 +49,4 @@ public class SpoofUserAgent implements IHook { } ); } -} +} \ No newline at end of file diff --git a/app/src/main/java/io/github/hiro/lime/hooks/UnsentRec.java b/app/src/main/java/io/github/hiro/lime/hooks/UnsentRec.java index f06db6a..b008e9c 100644 --- a/app/src/main/java/io/github/hiro/lime/hooks/UnsentRec.java +++ b/app/src/main/java/io/github/hiro/lime/hooks/UnsentRec.java @@ -1,14 +1,18 @@ package io.github.hiro.lime.hooks; +import static io.github.hiro.lime.Main.limeOptions; + import android.app.AlertDialog; import android.app.AndroidAppHelper; import android.app.Application; +import android.content.ContentValues; import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.graphics.Color; +import android.os.Environment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -24,6 +28,7 @@ import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; @@ -136,7 +141,7 @@ public class UnsentRec implements IHook { writer.close(); Toast.makeText(appContext, moduleContext.getResources().getString(R.string.file_content_deleted), Toast.LENGTH_SHORT).show(); } catch (IOException ignored) { - + Toast.makeText(appContext, moduleContext.getResources().getString(R.string.file_delete_failed), Toast.LENGTH_SHORT).show(); } } else { @@ -168,7 +173,7 @@ public class UnsentRec implements IHook { try { originalFile.createNewFile(); } catch (IOException ignored) { - + Toast.makeText(context, moduleContext.getResources().getString(R.string.file_creation_failed), Toast.LENGTH_SHORT).show(); return; } @@ -298,27 +303,40 @@ public class UnsentRec implements IHook { SQLiteDatabase db2 = SQLiteDatabase.openDatabase(dbFile2, dbParams2); - hookMessageDeletion(loadPackageParam, appContext, db1, db2); + hookMessageDeletion(loadPackageParam, appContext, db1, db2,moduleContext); resolveUnresolvedIds(loadPackageParam, appContext, db1, db2, moduleContext); + + canceled_message(loadPackageParam, appContext, db1, db2, moduleContext); + } } }); } - private String queryDatabase(SQLiteDatabase db, String query, String... selectionArgs) { + + + private String queryDatabase(SQLiteDatabase db, String query, String... selectionArgs) { if (db == null) { return null; } Cursor cursor = db.rawQuery(query, selectionArgs); String result = null; - if (cursor.moveToFirst()) { - result = cursor.getString(0); + + if (cursor != null && cursor.moveToFirst()) { + do { + result = cursor.getString(0); + XposedBridge.log("Query Result: " + result); // Log each result found + } while (cursor.moveToNext()); // In case there are multiple rows + } else { + XposedBridge.log("No rows found for query: " + query); } + cursor.close(); return result; } + private int countLinesInFile(File file) { int count = 0; try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) { @@ -334,7 +352,7 @@ public class UnsentRec implements IHook { } - private void hookMessageDeletion(XC_LoadPackage.LoadPackageParam loadPackageParam, Context context, SQLiteDatabase db1, SQLiteDatabase db2) { + private void hookMessageDeletion(XC_LoadPackage.LoadPackageParam loadPackageParam, Context context, SQLiteDatabase db1, SQLiteDatabase db2,Context moduleContext) { try { XposedBridge.hookAllMethods( loadPackageParam.classLoader.loadClass(Constants.RESPONSE_HOOK.className), @@ -355,7 +373,6 @@ public class UnsentRec implements IHook { } catch (ClassNotFoundException ignored) { } } - private void processMessage(String paramValue, Context moduleContext, SQLiteDatabase db1, SQLiteDatabase db2, Context context) { String unresolvedFilePath = context.getFilesDir() + "/UnresolvedIds.txt"; @@ -398,6 +415,9 @@ public class UnsentRec implements IHook { } if (serverId != null && talkId != null) { + // 新しいメソッドを呼び出して、メッセージを取り消し済みとして更新 + updateMessageAsCanceled(db1, serverId,context,moduleContext); + String content = queryDatabase(db1, "SELECT content FROM chat_history WHERE server_id=?", serverId); String imageCheck = queryDatabase(db1, "SELECT attachement_image FROM chat_history WHERE server_id=?", serverId); String timeEpochStr = queryDatabase(db1, "SELECT created_time FROM chat_history WHERE server_id=?", serverId); @@ -460,10 +480,163 @@ public class UnsentRec implements IHook { } catch (IOException ignored) { } } - } } + private void canceled_message(XC_LoadPackage.LoadPackageParam loadPackageParam, Context context, SQLiteDatabase db1, SQLiteDatabase db2, Context moduleContext) { + 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(); + + // チャット ID をログに出力 + XposedBridge.log("Chat ID: " + chatId); + String canceledContent = getCanceledContentFromFile(context, moduleContext); + // チェックボックスの状態を確認 + if (limeOptions.hide_canceled_message.checked) { + XposedBridge.log("hide_canceled_message is checked"); + + // content と server_id, chat_id を取得 (複数レコードを想定) + Cursor cursor = db1.rawQuery("SELECT content, server_id, chat_id FROM chat_history WHERE chat_id=?", new String[]{chatId}); + if (cursor != null) { + try { + while (cursor.moveToNext()) { + String content = cursor.getString(0); + String serverId = cursor.getString(1); + String currentChatId = cursor.getString(2); + + XposedBridge.log("Content: " + content); + XposedBridge.log("Server ID: " + serverId); + XposedBridge.log("Current Chat ID: " + currentChatId); + + // content が「削除されたメッセージです」の場合のみ処理 + if (content != null && content.contains(canceledContent)) { + if (currentChatId != null && !currentChatId.startsWith("/")) { + // chat_id の先頭に "/" を付ける + String updatedChatId = "/" + currentChatId; + + // 更新クエリを実行 (content が「削除されたメッセージです」の場合のみ) + db1.execSQL("UPDATE chat_history SET chat_id=? WHERE chat_id=? AND server_id=? AND content=?", + new Object[]{updatedChatId, currentChatId, serverId, content}); + XposedBridge.log("Updated Chat ID: " + updatedChatId); + } + } + } + } finally { + cursor.close(); + } + } else { + XposedBridge.log("No rows found for chat_id: " + chatId); + } + } else { + XposedBridge.log("hide_canceled_message is NOT checked"); + + // hide_canceled_message.checked が false の場合 + String chatId1 = "/"+(String) param.getResult(); + Cursor cursor = db1.rawQuery("SELECT content, server_id, chat_id FROM chat_history WHERE chat_id=?", new String[]{chatId1}); + if (cursor != null) { + try { + while (cursor.moveToNext()) { + String content = cursor.getString(0); + String serverId = cursor.getString(1); + String currentChatId = cursor.getString(2); + + XposedBridge.log("Content: " + content); + XposedBridge.log("Server ID: " + serverId); + XposedBridge.log("Current Chat ID: " + currentChatId); + + // content が「削除されたメッセージです」の場合のみ処理 + if (content != null && content.contains(canceledContent)) { + if (currentChatId != null && currentChatId.startsWith("/")) { + // chat_id から "/" を除外する + String updatedChatId = currentChatId.substring(1); + + // 更新クエリを実行 (content が「削除されたメッセージです」の場合のみ) + db1.execSQL("UPDATE chat_history SET chat_id=? WHERE chat_id=? AND server_id=? AND content=?", + new Object[]{updatedChatId, currentChatId, serverId, content}); + XposedBridge.log("Updated Chat ID: " + updatedChatId); + } + } + } + } finally { + cursor.close(); + } + } else { + XposedBridge.log("No rows found for chat_id: " + chatId); + } + } + } + }); + } + + private void updateMessageAsCanceled(SQLiteDatabase db1, String serverId, Context context, Context moduleContext) { + // canceledContent をファイルから取得 + String canceledContent = getCanceledContentFromFile(context, moduleContext); + + // 既存のメッセージを取得 + Cursor cursor = db1.rawQuery("SELECT * FROM chat_history WHERE server_id=?", new String[]{serverId}); + + if (cursor.moveToFirst()) { + // カラムの値を取得(null の場合もそのまま代入) + String type = cursor.isNull(cursor.getColumnIndex("type")) ? null : cursor.getString(cursor.getColumnIndex("type")); + String chatId = cursor.isNull(cursor.getColumnIndex("chat_id")) ? null : cursor.getString(cursor.getColumnIndex("chat_id")); + String fromMid = cursor.isNull(cursor.getColumnIndex("from_mid")) ? null : cursor.getString(cursor.getColumnIndex("from_mid")); + String createdTime = cursor.isNull(cursor.getColumnIndex("created_time")) ? null : cursor.getString(cursor.getColumnIndex("created_time")); + String deliveredTime = cursor.isNull(cursor.getColumnIndex("delivered_time")) ? null : cursor.getString(cursor.getColumnIndex("delivered_time")); + String status = cursor.isNull(cursor.getColumnIndex("status")) ? null : cursor.getString(cursor.getColumnIndex("status")); + Integer sentCount = cursor.isNull(cursor.getColumnIndex("sent_count")) ? null : cursor.getInt(cursor.getColumnIndex("sent_count")); + Integer readCount = cursor.isNull(cursor.getColumnIndex("read_count")) ? null : cursor.getInt(cursor.getColumnIndex("read_count")); + String locationName = cursor.isNull(cursor.getColumnIndex("location_name")) ? null : cursor.getString(cursor.getColumnIndex("location_name")); + String locationAddress = cursor.isNull(cursor.getColumnIndex("location_address")) ? null : cursor.getString(cursor.getColumnIndex("location_address")); + String locationPhone = cursor.isNull(cursor.getColumnIndex("location_phone")) ? null : cursor.getString(cursor.getColumnIndex("location_phone")); + Double locationLatitude = cursor.isNull(cursor.getColumnIndex("location_latitude")) ? null : cursor.getDouble(cursor.getColumnIndex("location_latitude")); + Double locationLongitude = cursor.isNull(cursor.getColumnIndex("location_longitude")) ? null : cursor.getDouble(cursor.getColumnIndex("location_longitude")); + String attachmentImage = cursor.isNull(cursor.getColumnIndex("attachement_image")) ? null : cursor.getString(cursor.getColumnIndex("attachement_image")); + Integer attachmentImageHeight = cursor.isNull(cursor.getColumnIndex("attachement_image_height")) ? null : cursor.getInt(cursor.getColumnIndex("attachement_image_height")); + Integer attachmentImageWidth = cursor.isNull(cursor.getColumnIndex("attachement_image_width")) ? null : cursor.getInt(cursor.getColumnIndex("attachement_image_width")); + Integer attachmentImageSize = cursor.isNull(cursor.getColumnIndex("attachement_image_size")) ? null : cursor.getInt(cursor.getColumnIndex("attachement_image_size")); + String attachmentType = cursor.isNull(cursor.getColumnIndex("attachement_type")) ? null : cursor.getString(cursor.getColumnIndex("attachement_type")); + String attachmentLocalUri = cursor.isNull(cursor.getColumnIndex("attachement_local_uri")) ? null : cursor.getString(cursor.getColumnIndex("attachement_local_uri")); + String parameter = cursor.isNull(cursor.getColumnIndex("parameter")) ? null : cursor.getString(cursor.getColumnIndex("parameter")); + String chunks = cursor.isNull(cursor.getColumnIndex("chunks")) ? null : cursor.getString(cursor.getColumnIndex("chunks")); + + // 既存のレコードがあるか確認 + Cursor existingCursor = db1.rawQuery("SELECT * FROM chat_history WHERE server_id=? AND content=?", new String[]{serverId, chatId}); + if (!existingCursor.moveToFirst()) { + // 新しいレコードを挿入 + ContentValues values = new ContentValues(); + values.put("server_id", serverId); + values.put("type", type); + values.put("chat_id", chatId); + values.put("from_mid", fromMid); + values.put("content", canceledContent); // ファイルから取得した内容に変更 + values.put("created_time", createdTime); + values.put("delivered_time", deliveredTime); + values.put("status", status); + if (sentCount != null) values.put("sent_count", sentCount); + if (readCount != null) values.put("read_count", readCount); + values.put("location_name", locationName); + values.put("location_address", locationAddress); + values.put("location_phone", locationPhone); + if (locationLatitude != null) values.put("location_latitude", locationLatitude); + if (locationLongitude != null) values.put("location_longitude", locationLongitude); + values.put("attachement_image", attachmentImage); + if (attachmentImageHeight != null) values.put("attachement_image_height", attachmentImageHeight); + if (attachmentImageWidth != null) values.put("attachement_image_width", attachmentImageWidth); + if (attachmentImageSize != null) values.put("attachement_image_size", attachmentImageSize); + values.put("attachement_type", attachmentType); + values.put("attachement_local_uri", attachmentLocalUri); + values.put("parameter", parameter); + values.put("chunks", chunks); + + db1.insert("chat_history", null, values); + } + existingCursor.close(); + } + + cursor.close(); + } private void saveUnresolvedIds(String serverId, String talkId, String filePath) { String newEntry = "serverId:" + serverId + ",talkId:" + talkId; try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) { @@ -483,7 +656,45 @@ public class UnsentRec implements IHook { } } + private String getCanceledContentFromFile(Context context,Context moduleContext) { + // フォルダのパスを設定 + File dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "LimeBackup/Setting"); + if (!dir.exists()) { + if (!dir.mkdirs()) { + return moduleContext.getResources().getString(R.string.canceled_message_txt); // フォルダ作成失敗時のデフォルト値 + } + } + // ファイルのパスを設定 + File file = new File(dir, "canceled_message.txt"); + + // ファイルが存在しない場合、デフォルトの文字列を書き込む + if (!file.exists()) { + try (FileOutputStream fos = new FileOutputStream(file)) { + + String defaultContent = moduleContext.getResources().getString(R.string.canceled_message_txt); + fos.write(defaultContent.getBytes()); + } catch (IOException e) { + String defaultContent = moduleContext.getResources().getString(R.string.canceled_message_txt); + return defaultContent; // ファイル書き込み失敗時のデフォルト値 + } + } + + // ファイルから文字列を読み取る + StringBuilder contentBuilder = new StringBuilder(); + try (BufferedReader br = new BufferedReader(new FileReader(file))) { + String line; + while ((line = br.readLine()) != null) { + contentBuilder.append(line).append("\n"); + } + } catch (IOException e) { + return moduleContext.getResources().getString(R.string.canceled_message_txt); // ファイル読み込み失敗時のデフォルト値 + } + + // 読み取った内容を返す(末尾の改行を削除) + String content = contentBuilder.toString().trim(); + return content.isEmpty() ? moduleContext.getResources().getString(R.string.canceled_message_txt) : content; // 空ファイルの場合のデフォルト値 + } private void resolveUnresolvedIds(XC_LoadPackage.LoadPackageParam loadPackageParam, Context context, SQLiteDatabase db1, SQLiteDatabase db2, Context moduleContext) { String unresolvedFilePath = context.getFilesDir() + "/UnresolvedIds.txt"; diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 39e39ca..b5fc717 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -149,6 +149,9 @@ 年齢確認をスキップ GroupNotification 設定ボタンをLINEラボ画面のみに追加する + 取り消されたメッセージのトークを変更する + 取り消しされたメッセージです + 取り消されたメッセージのメッセージを非表示にする diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 7f217b9..c87cc3f 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -141,5 +141,8 @@ (緊急)在聊天室內已讀確認按鈕右邊設定一個刪除紀錄按鈕 跳過年齡驗證 只在LINE Lab畫面中加入設定按鈕 + 更改已取消訊息的通知對話 + 這是一條已取消的訊息 + 隱藏已取消訊息的訊息 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4e65265..d1e5d27 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -155,4 +155,7 @@ Skip Age Verification GroupNotification Add the settings button exclusively to the LINE Lab screen. + Change notification talk for canceled messages + This is a canceled message + Hide notification talk for canceled messages diff --git a/gradle.properties b/gradle.properties index 8533c59..dee3138 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,15 +1,7 @@ -# Project-wide Gradle settings. -# IDE (e.g. Android Studio) users: -# Gradle settings configured through the IDE *will override* -# any settings specified in this file. -# For more details on how to configure your build environment visit -# http://www.gradle.org/docs/current/userguide/build_environment.html -# Specifies the JVM arguments used for the daemon process. -# The setting is particularly useful for tweaking memory settings. -org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 -# Enables namespacing of each library's R class so that its R class includes only the -# resources declared in the library itself and none from the library's dependencies, -# thereby reducing the size of the R class for that library android.nonTransitiveRClass=true +android.nonFinalResIds=false +android.enableR8.fullMode=true +android.experimental.enableNewResourceShrinker=true +android.experimental.enableNewResourceShrinker.preciseShrinking=true android.useAndroidX=true -android.enableJetifier=true +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fb602ee..63532c5 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip +distributionSha256Sum=8d97a97984f6cbd2b85fe4c60a743440a347544bf18818048e611f5288d46c94 networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/settings.gradle b/settings.gradle index 24302b7..1e3701d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,7 +4,7 @@ pluginManagement { mavenCentral() } plugins { - id 'com.android.application' version '8.6.1' apply false + id 'com.android.application' version '8.8.0' apply false } } dependencyResolutionManagement {