1
0
mirror of https://github.com/areteruhiro/LIME-beta-hiro.git synced 2025-02-05 21:11:39 +09:00

既読確認機能を割り込み処理をするように

gradleをバージョンアップ
設定の取得をファイルから行うように
This commit is contained in:
areteruhiro 2025-01-28 18:05:12 +09:00
parent a9048992e0
commit e36587053d
19 changed files with 473 additions and 521 deletions

View File

@ -10,7 +10,7 @@ android {
minSdk 28 minSdk 28
targetSdk 35 targetSdk 35
versionCode 11501 versionCode 11501
versionName "1.15.01" versionName "1.16.10beta"
multiDexEnabled false multiDexEnabled false
proguardFiles += 'proguard-rules.pro' proguardFiles += 'proguard-rules.pro'
buildConfigField 'String', 'HOOK_TARGET_VERSION', '"141910383"' buildConfigField 'String', 'HOOK_TARGET_VERSION', '"141910383"'

View File

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application <application
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"

View File

@ -49,7 +49,7 @@ public class LimeOptions {
public Option MySendMessage = new Option("MySendMessage", R.string.MySendMessage, false); public Option MySendMessage = new Option("MySendMessage", R.string.MySendMessage, false);
public Option AgeCheckSkip = new Option("AgeCheckSkip", R.string.AgeCheckSkip, false); public Option AgeCheckSkip = new Option("AgeCheckSkip", R.string.AgeCheckSkip, false);
public Option hide_canceled_message = new Option("hide_canceled_message", R.string.hide_canceled_message, false);
public Option RemoveNotification = new Option("RemoveNotification", R.string.removeNotification, false); public Option RemoveNotification = new Option("RemoveNotification", R.string.removeNotification, false);
public Option DarkColor = new Option("DarkColor", R.string.DarkColor, false); public Option DarkColor = new Option("DarkColor", R.string.DarkColor, false);
public Option NoMuteMessage = new Option("NoMuteMessage", R.string.NoMuteMessage, false); public Option NoMuteMessage = new Option("NoMuteMessage", R.string.NoMuteMessage, false);
@ -75,7 +75,7 @@ public class LimeOptions {
redirectWebView, redirectWebView,
openInBrowser, openInBrowser,
preventMarkAsRead, preventMarkAsRead,
preventUnsendMessage, preventUnsendMessage,hide_canceled_message,
sendMuteMessage, sendMuteMessage,
Archived, Archived,
ReadChecker,MySendMessage,ReadCheckerChatdataDelete, ReadChecker,MySendMessage,ReadCheckerChatdataDelete,

View File

@ -1,59 +1,24 @@
package io.github.hiro.lime; package io.github.hiro.lime;
import android.content.res.XModuleResources; import android.content.res.XModuleResources;
import android.os.Environment;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import de.robv.android.xposed.IXposedHookInitPackageResources; import de.robv.android.xposed.IXposedHookInitPackageResources;
import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.IXposedHookLoadPackage;
import de.robv.android.xposed.IXposedHookZygoteInit; import de.robv.android.xposed.IXposedHookZygoteInit;
import de.robv.android.xposed.XSharedPreferences; import de.robv.android.xposed.XSharedPreferences;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_InitPackageResources; import de.robv.android.xposed.callbacks.XC_InitPackageResources;
import de.robv.android.xposed.callbacks.XC_LayoutInflated; import de.robv.android.xposed.callbacks.XC_LayoutInflated;
import de.robv.android.xposed.callbacks.XC_LoadPackage; import de.robv.android.xposed.callbacks.XC_LoadPackage;
import io.github.hiro.lime.hooks.AddRegistrationOptions; import io.github.hiro.lime.hooks.*;
import io.github.hiro.lime.hooks.AgeCheckSkip;
import io.github.hiro.lime.hooks.AutomaticBackup;
import io.github.hiro.lime.hooks.CheckHookTargetVersion;
import io.github.hiro.lime.hooks.Constants;
import io.github.hiro.lime.hooks.Disabled_Group_notification;
import io.github.hiro.lime.hooks.EmbedOptions;
import io.github.hiro.lime.hooks.IHook;
import io.github.hiro.lime.hooks.KeepUnread;
import io.github.hiro.lime.hooks.KeepUnreadLSpatch;
import io.github.hiro.lime.hooks.ModifyRequest;
import io.github.hiro.lime.hooks.ModifyResponse;
import io.github.hiro.lime.hooks.PhotoAddNotification;
import io.github.hiro.lime.hooks.OutputRequest;
import io.github.hiro.lime.hooks.OutputResponse;
import io.github.hiro.lime.hooks.PreventMarkAsRead;
import io.github.hiro.lime.hooks.PreventUnsendMessage;
import io.github.hiro.lime.hooks.RedirectWebView;
import io.github.hiro.lime.hooks.RemoveAds;
import io.github.hiro.lime.hooks.RemoveFlexibleContents;
import io.github.hiro.lime.hooks.RemoveIconLabels;
import io.github.hiro.lime.hooks.RemoveIcons;
import io.github.hiro.lime.hooks.RemoveNotification;
import io.github.hiro.lime.hooks.RemoveReplyMute;
import io.github.hiro.lime.hooks.RemoveVoiceRecord;
import io.github.hiro.lime.hooks.RingTone;
import io.github.hiro.lime.hooks.SendMuteMessage;
import io.github.hiro.lime.hooks.SpoofAndroidId;
import io.github.hiro.lime.hooks.SpoofUserAgent;
import io.github.hiro.lime.hooks.UnsentRec;
import io.github.hiro.lime.hooks.Archived;
import io.github.hiro.lime.hooks.ReadChecker;
import io.github.hiro.lime.hooks.DarkColor;
import java.io.File;
public class Main implements IXposedHookLoadPackage, IXposedHookInitPackageResources, IXposedHookZygoteInit { public class Main implements IXposedHookLoadPackage, IXposedHookInitPackageResources, IXposedHookZygoteInit {
public static String modulePath; public static String modulePath;
public static CustomPreferences customPreferences;
public static XSharedPreferences xModulePrefs; public static XSharedPreferences xModulePrefs;
public static XSharedPreferences xPackagePrefs; public static XSharedPreferences xPackagePrefs;
public static XSharedPreferences xPrefs; public static XSharedPreferences xPrefs;
@ -91,23 +56,48 @@ public class Main implements IXposedHookLoadPackage, IXposedHookInitPackageResou
new PhotoAddNotification(), new PhotoAddNotification(),
new RemoveVoiceRecord(), new RemoveVoiceRecord(),
new AgeCheckSkip() new AgeCheckSkip()
}; };
@Override
public void initZygote(@NonNull StartupParam startupParam) throws Throwable {
modulePath = startupParam.modulePath;
customPreferences = new CustomPreferences(); // CustomPreferences を初期化
}
public void handleLoadPackage(@NonNull XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { public void handleLoadPackage(@NonNull XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
if (!loadPackageParam.packageName.equals(Constants.PACKAGE_NAME)) return; if (!loadPackageParam.packageName.equals(Constants.PACKAGE_NAME)) return;
Constants.initializeHooks(loadPackageParam); Constants.initializeHooks(loadPackageParam);
xModulePrefs = new XSharedPreferences(Constants.MODULE_NAME, "options"); xModulePrefs = new XSharedPreferences(Constants.MODULE_NAME, "options");
xPackagePrefs = new XSharedPreferences(Constants.PACKAGE_NAME, Constants.MODULE_NAME + "-options"); xPackagePrefs = new XSharedPreferences(Constants.PACKAGE_NAME, Constants.MODULE_NAME + "-options");
if (xModulePrefs.getBoolean("unembed_options", false)) {
// 設定ファイルを再読み込み
xModulePrefs.reload();
xPackagePrefs.reload();
// unembed_optionsの値をログに出力
boolean unembedOptions = xModulePrefs.getBoolean("unembed_options", false);
XposedBridge.log("unembed_options: " + unembedOptions);
if (unembedOptions) {
xPrefs = xModulePrefs; xPrefs = xModulePrefs;
XposedBridge.log("Using module preferences");
// xModulePrefsから設定を読み込む
for (LimeOptions.Option option : limeOptions.options) {
option.checked = xModulePrefs.getBoolean(option.name, option.checked);
}
} else { } else {
xPrefs = xPackagePrefs; xPrefs = xPackagePrefs;
} XposedBridge.log("Using package preferences");
// customPreferencesから設定を読み込む
for (LimeOptions.Option option : limeOptions.options) { for (LimeOptions.Option option : limeOptions.options) {
option.checked = xPrefs.getBoolean(option.name, option.checked); option.checked = Boolean.parseBoolean(customPreferences.getSetting(option.name, String.valueOf(option.checked)));
}
} }
// 各フックを適用
for (IHook hook : hooks) { for (IHook hook : hooks) {
hook.hook(limeOptions, loadPackageParam); hook.hook(limeOptions, loadPackageParam);
} }
@ -120,8 +110,7 @@ public class Main implements IXposedHookLoadPackage, IXposedHookInitPackageResou
XModuleResources xModuleResources = XModuleResources.createInstance(modulePath, resparam.res); XModuleResources xModuleResources = XModuleResources.createInstance(modulePath, resparam.res);
// 既存のリソースフック
if (limeOptions.removeIconLabels.checked) { if (limeOptions.removeIconLabels.checked) {
resparam.res.setReplacement(Constants.PACKAGE_NAME, "dimen", "main_bnb_button_height", xModuleResources.fwd(R.dimen.main_bnb_button_height)); resparam.res.setReplacement(Constants.PACKAGE_NAME, "dimen", "main_bnb_button_height", xModuleResources.fwd(R.dimen.main_bnb_button_height));
resparam.res.setReplacement(Constants.PACKAGE_NAME, "dimen", "main_bnb_button_width", xModuleResources.fwd(R.dimen.main_bnb_button_width)); resparam.res.setReplacement(Constants.PACKAGE_NAME, "dimen", "main_bnb_button_width", xModuleResources.fwd(R.dimen.main_bnb_button_width));
@ -137,11 +126,6 @@ public class Main implements IXposedHookLoadPackage, IXposedHookInitPackageResou
resparam.res.setReplacement(Constants.PACKAGE_NAME, "dimen", "home_tab_v3_service_icon_size", xModuleResources.fwd(R.dimen.home_tab_v3_service_icon_size)); resparam.res.setReplacement(Constants.PACKAGE_NAME, "dimen", "home_tab_v3_service_icon_size", xModuleResources.fwd(R.dimen.home_tab_v3_service_icon_size));
} }
} }
@Override
public void initZygote(@NonNull StartupParam startupParam) throws Throwable {
modulePath = startupParam.modulePath;
}
} }

View File

@ -0,0 +1,51 @@
package io.github.hiro.lime.hooks;
import android.os.Environment;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
public class CustomPreferences {
private static final String SETTINGS_DIR = "LimeBackup/Setting";
private static final String SETTINGS_FILE = "settings.properties";
private final File settingsFile;
public CustomPreferences() {
File dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), SETTINGS_DIR);
if (!dir.exists()) {
dir.mkdirs();
}
settingsFile = new File(dir, SETTINGS_FILE);
}
public void saveSetting(String key, String value) {
Properties properties = new Properties();
try (FileInputStream fis = new FileInputStream(settingsFile)) {
properties.load(fis);
} catch (IOException e) {
// ファイルが存在しない場合新規作成する
}
try (FileOutputStream fos = new FileOutputStream(settingsFile)) {
properties.setProperty(key, value);
properties.store(fos, null);
} catch (IOException e) {
e.printStackTrace();
}
}
public String getSetting(String key, String defaultValue) {
Properties properties = new Properties();
try (FileInputStream fis = new FileInputStream(settingsFile)) {
properties.load(fis);
return properties.getProperty(key, defaultValue);
} catch (IOException e) {
e.printStackTrace();
}
return defaultValue;
}
}

View File

@ -80,14 +80,16 @@ public class EmbedOptions implements IHook {
e.printStackTrace(); e.printStackTrace();
} }
CustomPreferences customPreferences = new CustomPreferences();
for (LimeOptions.Option option : limeOptions.options) {
option.checked = Boolean.parseBoolean(customPreferences.getSetting(option.name, String.valueOf(option.checked)));
}
Context moduleContext = AndroidAppHelper.currentApplication().createPackageContext( Context moduleContext = AndroidAppHelper.currentApplication().createPackageContext(
"io.github.hiro.lime", Context.CONTEXT_IGNORE_SECURITY); "io.github.hiro.lime", Context.CONTEXT_IGNORE_SECURITY);
ViewGroup viewGroup = ((ViewGroup) param.args[0]); ViewGroup viewGroup = ((ViewGroup) param.args[0]);
Context context = viewGroup.getContext(); Context context = viewGroup.getContext();
Utils.addModuleAssetPath(context); Utils.addModuleAssetPath(context);
SharedPreferences prefs = context.getSharedPreferences(Constants.MODULE_NAME + "-options", Context.MODE_PRIVATE);
LinearLayout layout = new LinearLayout(context); LinearLayout layout = new LinearLayout(context);
layout.setLayoutParams(new LinearLayout.LayoutParams( layout.setLayoutParams(new LinearLayout.LayoutParams(
@ -128,8 +130,7 @@ public class EmbedOptions implements IHook {
} }
{ {
final String script = new String(Base64.decode(prefs.getString("encoded_js_modify_request", ""), Base64.NO_WRAP)); final String script = new String(Base64.decode(customPreferences.getSetting("encoded_js_modify_request", ""), Base64.NO_WRAP));
LinearLayout layoutModifyRequest = new LinearLayout(context); LinearLayout layoutModifyRequest = new LinearLayout(context);
layoutModifyRequest.setLayoutParams(new LinearLayout.LayoutParams( layoutModifyRequest.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT,
@ -264,13 +265,31 @@ public class EmbedOptions implements IHook {
}); });
layout.addView(KeepUnread_Button); layout.addView(KeepUnread_Button);
if (limeOptions.preventUnsendMessage.checked) {
Button canceled_message_Button = new Button(context);
canceled_message_Button.setLayoutParams(buttonParams);
canceled_message_Button.setText(moduleContext.getResources().getString(R.string.canceled_message));
canceled_message_Button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Cancel_Message_Button(context, moduleContext);
}
});
layout.addView(canceled_message_Button);
}
builder.setPositiveButton(R.string.positive_button, new DialogInterface.OnClickListener() { builder.setPositiveButton(R.string.positive_button, new DialogInterface.OnClickListener() {
@Override @Override
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
String code = editText.getText().toString(); String code = editText.getText().toString();
if (!code.equals(script)) { if (!code.equals(script)) {
prefs.edit().putString("encoded_js_modify_request", Base64.encodeToString(code.getBytes(), Base64.NO_WRAP)).commit(); // CustomPreferencesのインスタンスを作成
CustomPreferences customPreferences = new CustomPreferences();
// Base64エンコードして設定を保存
String encodedCode = Base64.encodeToString(code.getBytes(), Base64.NO_WRAP);
customPreferences.saveSetting("encoded_js_modify_request", encodedCode);
Toast.makeText(context.getApplicationContext(), context.getString(R.string.restarting), Toast.LENGTH_SHORT).show(); Toast.makeText(context.getApplicationContext(), context.getString(R.string.restarting), Toast.LENGTH_SHORT).show();
Process.killProcess(Process.myPid()); Process.killProcess(Process.myPid());
context.startActivity(new Intent().setClassName(Constants.PACKAGE_NAME, "jp.naver.line.android.activity.SplashActivity")); context.startActivity(new Intent().setClassName(Constants.PACKAGE_NAME, "jp.naver.line.android.activity.SplashActivity"));
@ -305,8 +324,7 @@ public class EmbedOptions implements IHook {
} }
{ {
final String script = new String(Base64.decode(prefs.getString("encoded_js_modify_response", ""), Base64.NO_WRAP)); final String script = new String(Base64.decode(customPreferences.getSetting("encoded_js_modify_response", ""), Base64.NO_WRAP));
LinearLayout layoutModifyResponse = new LinearLayout(context); LinearLayout layoutModifyResponse = new LinearLayout(context);
layoutModifyResponse.setLayoutParams(new LinearLayout.LayoutParams( layoutModifyResponse.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT,
@ -379,8 +397,7 @@ public class EmbedOptions implements IHook {
public void onClick(DialogInterface dialog, int which) { public void onClick(DialogInterface dialog, int which) {
String code = editText.getText().toString(); String code = editText.getText().toString();
if (!code.equals(script)) { if (!code.equals(script)) {
prefs.edit().putString("encoded_js_modify_response", Base64.encodeToString(code.getBytes(), Base64.NO_WRAP)).commit(); customPreferences.saveSetting("encoded_js_modify_response", Base64.encodeToString(code.getBytes(), Base64.NO_WRAP)); Toast.makeText(context.getApplicationContext(), context.getString(R.string.restarting), Toast.LENGTH_SHORT).show();
Toast.makeText(context.getApplicationContext(), context.getString(R.string.restarting), Toast.LENGTH_SHORT).show();
Process.killProcess(Process.myPid()); Process.killProcess(Process.myPid());
context.startActivity(new Intent().setClassName(Constants.PACKAGE_NAME, "jp.naver.line.android.activity.SplashActivity")); context.startActivity(new Intent().setClassName(Constants.PACKAGE_NAME, "jp.naver.line.android.activity.SplashActivity"));
} }
@ -427,7 +444,7 @@ public class EmbedOptions implements IHook {
if (limeOptions.options[i].checked != switchView.isChecked()) { if (limeOptions.options[i].checked != switchView.isChecked()) {
optionChanged = true; optionChanged = true;
} }
prefs.edit().putBoolean(limeOptions.options[i].name, switchView.isChecked()).commit(); customPreferences.saveSetting(limeOptions.options[i].name, String.valueOf(switchView.isChecked()));
} }
if (optionChanged) { if (optionChanged) {
@ -489,427 +506,89 @@ public class EmbedOptions implements IHook {
); );
} }
XposedBridge.hookAllMethods(
loadPackageParam.classLoader.loadClass("com.linecorp.line.settings.lab.LineUserLabSettingsFragment"),
"onViewCreated",
new XC_MethodHook() {
}
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;
}
}
// ファイルのパスを設定
File file = new File(dir, "canceled_message.txt");
// ファイルが存在しない場合デフォルトのメッセージを設定
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;
}
}
// ファイルの内容を読み取る
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 @Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable { public void onClick(View v) {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
Context moduleContext = AndroidAppHelper.currentApplication().createPackageContext( writer.write(editText.getText().toString());
"io.github.hiro.lime", Context.CONTEXT_IGNORE_SECURITY); Toast.makeText(context, "Saved successfully", Toast.LENGTH_SHORT).show();
ViewGroup viewGroup = ((ViewGroup) param.args[0]); } catch (IOException e) {
Context context = viewGroup.getContext(); Toast.makeText(context, "Failed to save file: " + e.getMessage(), Toast.LENGTH_SHORT).show();
Utils.addModuleAssetPath(context); }
SharedPreferences prefs = context.getSharedPreferences(Constants.MODULE_NAME + "-options", Context.MODE_PRIVATE); }
});
// レイアウトの設定
LinearLayout layout = new LinearLayout(context); LinearLayout layout = new LinearLayout(context);
layout.setLayoutParams(new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT));
layout.setOrientation(LinearLayout.VERTICAL); layout.setOrientation(LinearLayout.VERTICAL);
layout.setPadding(Utils.dpToPx(20, context), Utils.dpToPx(20, context), Utils.dpToPx(20, context), Utils.dpToPx(20, context)); layout.addView(editText);
layout.addView(saveButton);
Switch switchRedirectWebView = null;
for (LimeOptions.Option option : limeOptions.options) {
final String name = option.name;
Switch switchView = new Switch(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
params.topMargin = Utils.dpToPx(20, context);
switchView.setLayoutParams(params);
switchView.setText(option.id);
switchView.setChecked(option.checked);
if (name.equals("redirect_webview")) switchRedirectWebView = switchView;
else if (name.equals("open_in_browser")) {
switchRedirectWebView.setOnCheckedChangeListener((buttonView, isChecked) -> {
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);
}
});
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);
}
{
final String script = new String(Base64.decode(prefs.getString("encoded_js_modify_response", ""), Base64.NO_WRAP));
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);
}
}
}
);
// 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) { public int getStatusBarHeight(Context context) {
int result = 0; int result = 0;

View File

@ -21,7 +21,8 @@ public class ModifyRequest implements IHook {
new XC_MethodHook() { new XC_MethodHook() {
@Override @Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable { 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(); Context ctx = Context.enter();
ctx.setOptimizationLevel(-1); ctx.setOptimizationLevel(-1);
try { try {

View File

@ -21,14 +21,20 @@ public class ModifyResponse implements IHook {
new XC_MethodHook() { new XC_MethodHook() {
@Override @Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable { 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(); Context ctx = Context.enter();
ctx.setOptimizationLevel(-1); ctx.setOptimizationLevel(-1); // 最適化レベルを無効化
try { try {
Scriptable scope = ctx.initStandardObjects(); Scriptable scope = ctx.initStandardObjects();
Object jsData = Context.javaToJS(new Communication(Communication.Type.RESPONSE, param.args[0].toString(), param.args[1]), scope); 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, "data", jsData);
ScriptableObject.putProperty(scope, "console", Context.javaToJS(new Console(), scope)); ScriptableObject.putProperty(scope, "console", Context.javaToJS(new Console(), scope));
ctx.evaluateString(scope, script, "Script", 1, null); ctx.evaluateString(scope, script, "Script", 1, null);
} catch (Exception e) { } catch (Exception e) {
XposedBridge.log(e.toString()); XposedBridge.log(e.toString());

View File

@ -3,11 +3,11 @@ package io.github.hiro.lime.hooks;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.support.customtabs.CustomTabsIntent;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.webkit.WebView; import android.webkit.WebView;
import androidx.browser.customtabs.CustomTabsIntent;
import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.XposedHelpers;

View File

@ -7,13 +7,21 @@ import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage; import de.robv.android.xposed.callbacks.XC_LoadPackage;
import io.github.hiro.lime.LimeOptions; import io.github.hiro.lime.LimeOptions;
import io.github.hiro.lime.Main;
public class SpoofAndroidId implements IHook { public class SpoofAndroidId implements IHook {
@Override @Override
public void hook(LimeOptions limeOptions, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { 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( XposedHelpers.findAndHookMethod(
Settings.Secure.class, Settings.Secure.class,
"getString", "getString",
@ -22,7 +30,9 @@ public class SpoofAndroidId implements IHook {
new XC_MethodHook() { new XC_MethodHook() {
@Override @Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable { protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
// ANDROID_ID が要求された場合
if (param.args[1].toString().equals(Settings.Secure.ANDROID_ID)) { if (param.args[1].toString().equals(Settings.Secure.ANDROID_ID)) {
// 偽の ANDROID_ID を返す
param.setResult("0000000000000000"); param.setResult("0000000000000000");
} }
} }

View File

@ -1,22 +1,28 @@
package io.github.hiro.lime.hooks; package io.github.hiro.lime.hooks;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences;
import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.XposedHelpers;
import de.robv.android.xposed.callbacks.XC_LoadPackage; import de.robv.android.xposed.callbacks.XC_LoadPackage;
import io.github.hiro.lime.LimeOptions; import io.github.hiro.lime.LimeOptions;
import io.github.hiro.lime.Main;
public class SpoofUserAgent implements IHook { public class SpoofUserAgent implements IHook {
private boolean hasLoggedSpoofedUserAgent = false; private boolean hasLoggedSpoofedUserAgent = false;
@Override @Override
public void hook(LimeOptions limeOptions, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { 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( XposedHelpers.findAndHookMethod(
loadPackageParam.classLoader.loadClass(Constants.USER_AGENT_HOOK.className), loadPackageParam.classLoader.loadClass(Constants.USER_AGENT_HOOK.className),
Constants.USER_AGENT_HOOK.methodName, Constants.USER_AGENT_HOOK.methodName,
@ -24,16 +30,17 @@ public class SpoofUserAgent implements IHook {
new XC_MethodHook() { new XC_MethodHook() {
@Override @Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable { protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
SharedPreferences prefs = Main.xPackagePrefs; // 設定から値を取得
String device = customPreferences.getSetting("device_name", "ANDROID");
String device = prefs.getString("device_name", "ANDROID"); String androidVersion = customPreferences.getSetting("android_version", "14.16.0");
String androidVersion = prefs.getString("android_version", "14.16.0"); String osName = customPreferences.getSetting("os_name", "Android OS");
String osName = prefs.getString("os_name", "Android OS"); String osVersion = customPreferences.getSetting("os_version", "14");
String osVersion = prefs.getString("os_version", "14");
// 偽の User-Agent を生成
String spoofedUserAgent = device + "\t" + androidVersion + "\t" + osName + "\t" + osVersion; String spoofedUserAgent = device + "\t" + androidVersion + "\t" + osName + "\t" + osVersion;
param.setResult(spoofedUserAgent); param.setResult(spoofedUserAgent);
// ログに出力初回のみ
if (!hasLoggedSpoofedUserAgent) { if (!hasLoggedSpoofedUserAgent) {
XposedBridge.log("Spoofed User-Agent: " + spoofedUserAgent); XposedBridge.log("Spoofed User-Agent: " + spoofedUserAgent);
hasLoggedSpoofedUserAgent = true; hasLoggedSpoofedUserAgent = true;

View File

@ -1,14 +1,18 @@
package io.github.hiro.lime.hooks; package io.github.hiro.lime.hooks;
import static io.github.hiro.lime.Main.limeOptions;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.AndroidAppHelper; import android.app.AndroidAppHelper;
import android.app.Application; import android.app.Application;
import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.graphics.Color; import android.graphics.Color;
import android.os.Environment;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -24,6 +28,7 @@ import java.io.BufferedReader;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader; import java.io.FileReader;
import java.io.FileWriter; import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
@ -298,27 +303,40 @@ public class UnsentRec implements IHook {
SQLiteDatabase db2 = SQLiteDatabase.openDatabase(dbFile2, dbParams2); SQLiteDatabase db2 = SQLiteDatabase.openDatabase(dbFile2, dbParams2);
hookMessageDeletion(loadPackageParam, appContext, db1, db2); hookMessageDeletion(loadPackageParam, appContext, db1, db2,moduleContext);
resolveUnresolvedIds(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) { if (db == null) {
return null; return null;
} }
Cursor cursor = db.rawQuery(query, selectionArgs); Cursor cursor = db.rawQuery(query, selectionArgs);
String result = null; String result = null;
if (cursor.moveToFirst()) {
if (cursor != null && cursor.moveToFirst()) {
do {
result = cursor.getString(0); 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(); cursor.close();
return result; return result;
} }
private int countLinesInFile(File file) { private int countLinesInFile(File file) {
int count = 0; int count = 0;
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)))) { 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 { try {
XposedBridge.hookAllMethods( XposedBridge.hookAllMethods(
loadPackageParam.classLoader.loadClass(Constants.RESPONSE_HOOK.className), loadPackageParam.classLoader.loadClass(Constants.RESPONSE_HOOK.className),
@ -355,7 +373,6 @@ public class UnsentRec implements IHook {
} catch (ClassNotFoundException ignored) { } catch (ClassNotFoundException ignored) {
} }
} }
private void processMessage(String paramValue, Context moduleContext, SQLiteDatabase db1, SQLiteDatabase db2, Context context) { private void processMessage(String paramValue, Context moduleContext, SQLiteDatabase db1, SQLiteDatabase db2, Context context) {
String unresolvedFilePath = context.getFilesDir() + "/UnresolvedIds.txt"; String unresolvedFilePath = context.getFilesDir() + "/UnresolvedIds.txt";
@ -398,6 +415,9 @@ public class UnsentRec implements IHook {
} }
if (serverId != null && talkId != null) { if (serverId != null && talkId != null) {
// 新しいメソッドを呼び出してメッセージを取り消し済みとして更新
updateMessageAsCanceled(db1, serverId,context,moduleContext);
String content = queryDatabase(db1, "SELECT content FROM chat_history WHERE server_id=?", serverId); 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 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); 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) { } 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) { private void saveUnresolvedIds(String serverId, String talkId, String filePath) {
String newEntry = "serverId:" + serverId + ",talkId:" + talkId; String newEntry = "serverId:" + serverId + ",talkId:" + talkId;
try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) { 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) { private void resolveUnresolvedIds(XC_LoadPackage.LoadPackageParam loadPackageParam, Context context, SQLiteDatabase db1, SQLiteDatabase db2, Context moduleContext) {
String unresolvedFilePath = context.getFilesDir() + "/UnresolvedIds.txt"; String unresolvedFilePath = context.getFilesDir() + "/UnresolvedIds.txt";

View File

@ -149,6 +149,9 @@
<string name="AgeCheckSkip">年齢確認をスキップ</string> <string name="AgeCheckSkip">年齢確認をスキップ</string>
<string name="GroupNotification">GroupNotification</string> <string name="GroupNotification">GroupNotification</string>
<string name="LINELabOnly">設定ボタンをLINEラボ画面のみに追加する</string> <string name="LINELabOnly">設定ボタンをLINEラボ画面のみに追加する</string>
<string name="canceled_message">取り消されたメッセージのトークを変更する</string>
<string name="canceled_message_txt">取り消しされたメッセージです</string>
<string name="hide_canceled_message">取り消されたメッセージのメッセージを非表示にする</string>
</resources> </resources>

View File

@ -141,5 +141,8 @@
<string name="ReadCheckerChatdataDelete">(緊急)在聊天室內已讀確認按鈕右邊設定一個刪除紀錄按鈕</string> <string name="ReadCheckerChatdataDelete">(緊急)在聊天室內已讀確認按鈕右邊設定一個刪除紀錄按鈕</string>
<string name="AgeCheckSkip">跳過年齡驗證</string> <string name="AgeCheckSkip">跳過年齡驗證</string>
<string name="LINELabOnly">只在LINE Lab畫面中加入設定按鈕</string> <string name="LINELabOnly">只在LINE Lab畫面中加入設定按鈕</string>
<string name="canceled_message">更改已取消訊息的通知對話</string>
<string name="canceled_message_txt">這是一條已取消的訊息</string>
<string name="hide_canceled_message">隱藏已取消訊息的訊息</string>
</resources> </resources>

View File

@ -155,4 +155,7 @@
<string name="AgeCheckSkip">Skip Age Verification</string> <string name="AgeCheckSkip">Skip Age Verification</string>
<string name="GroupNotification">GroupNotification</string> <string name="GroupNotification">GroupNotification</string>
<string name="LINELabOnly">Add the settings button exclusively to the LINE Lab screen.</string> <string name="LINELabOnly">Add the settings button exclusively to the LINE Lab screen.</string>
<string name="canceled_message">Change notification talk for canceled messages</string>
<string name="canceled_message_txt">This is a canceled message</string>
<string name="hide_canceled_message">Hide notification talk for canceled messages</string>
</resources> </resources>

View File

@ -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.nonTransitiveRClass=true
android.nonFinalResIds=false
android.enableR8.fullMode=true
android.experimental.enableNewResourceShrinker=true
android.experimental.enableNewResourceShrinker.preciseShrinking=true
android.useAndroidX=true android.useAndroidX=true
android.enableJetifier=true org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8

View File

@ -1,7 +1,7 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26 distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip distributionSha256Sum=8d97a97984f6cbd2b85fe4c60a743440a347544bf18818048e611f5288d46c94
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@ -4,7 +4,7 @@ pluginManagement {
mavenCentral() mavenCentral()
} }
plugins { plugins {
id 'com.android.application' version '8.6.1' apply false id 'com.android.application' version '8.8.0' apply false
} }
} }
dependencyResolutionManagement { dependencyResolutionManagement {