1
0
mirror of https://github.com/areteruhiro/LIME-beta-hiro.git synced 2025-02-15 09:51:38 +09:00

Compare commits

...

3 Commits

Author SHA1 Message Date
areteruhiro
b0d9f39ab1 test 2025-01-30 01:22:45 +09:00
areteruhiro
f112129775 Merge remote-tracking branch 'origin/master'
# Conflicts:
#	app/build.gradle
2025-01-29 23:25:22 +09:00
areteruhiro
a76ab155ab 取り消しメッセージのバグ修正 2025-01-29 23:24:18 +09:00
3 changed files with 120 additions and 64 deletions

View File

@ -10,7 +10,7 @@ android {
minSdk 28 minSdk 28
targetSdk 35 targetSdk 35
versionCode 11501 versionCode 11501
versionName "1.16.01beta" versionName "1.16.00beta"
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,12 +1,14 @@
package io.github.hiro.lime.hooks; package io.github.hiro.lime.hooks;
import static android.content.ContentValues.TAG;
import static io.github.hiro.lime.Main.limeOptions; import static io.github.hiro.lime.Main.limeOptions;
import android.app.Activity; import android.app.Activity;
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.ContentResolver;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
@ -18,8 +20,13 @@ import android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment; import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Button; import android.widget.Button;
@ -192,16 +199,19 @@ public class ReadChecker implements IHook {
return noGroup; return noGroup;
} }
private static final String TAG = "FileHandler";
private void addButton(Activity activity, Context moduleContext) { public void addButton(Activity activity, Context moduleContext) {
// ファイルパスを取得 // ファイル取得処理フォールバック対応
File dir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "LimeBackup"); File file = getFile(activity);
File file = new File(dir, "margin_settings.txt"); if (file == null) {
Log.e(TAG, "Failed to get a valid file.");
return;
}
// デフォルト値 // デフォルト値
float readCheckerHorizontalMarginFactor = 0.5f; // デフォルト値 float readCheckerHorizontalMarginFactor = 0.5f;
int readCheckerVerticalMarginDp = 100; // デフォルト値 int readCheckerVerticalMarginDp = 100;
float readCheckerSizeDp = 60; // デフォルト値 float readCheckerSizeDp = 60;
// ファイルの内容を読み込む // ファイルの内容を読み込む
if (file.exists()) { if (file.exists()) {
@ -210,12 +220,16 @@ public class ReadChecker implements IHook {
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
String[] parts = line.split("=", 2); String[] parts = line.split("=", 2);
if (parts.length == 2) { if (parts.length == 2) {
if (parts[0].trim().equals("Read_checker_horizontalMarginFactor")) { switch (parts[0].trim()) {
readCheckerHorizontalMarginFactor = Float.parseFloat(parts[1].trim()); case "Read_checker_horizontalMarginFactor":
} else if (parts[0].trim().equals("Read_checker_verticalMarginDp")) { readCheckerHorizontalMarginFactor = Float.parseFloat(parts[1].trim());
readCheckerVerticalMarginDp = Integer.parseInt(parts[1].trim()); break;
} else if (parts[0].trim().equals("chat_read_check_size")) { case "Read_checker_verticalMarginDp":
readCheckerSizeDp = Float.parseFloat(parts[1].trim()); readCheckerVerticalMarginDp = Integer.parseInt(parts[1].trim());
break;
case "chat_read_check_size":
readCheckerSizeDp = Float.parseFloat(parts[1].trim());
break;
} }
} }
} }
@ -223,9 +237,10 @@ public class ReadChecker implements IHook {
} }
} }
// 画像ボタンの設定
ImageView imageButton = new ImageView(activity); ImageView imageButton = new ImageView(activity);
String imageName = "read_checker.png"; String imageName = "read_checker.png";
File imageFile = new File(dir, imageName); File imageFile = new File(file.getParent(), imageName);
if (!imageFile.exists()) { if (!imageFile.exists()) {
try (InputStream in = moduleContext.getResources().openRawResource( try (InputStream in = moduleContext.getResources().openRawResource(
@ -245,11 +260,11 @@ public class ReadChecker implements IHook {
Drawable drawable = Drawable.createFromPath(imageFile.getAbsolutePath()); Drawable drawable = Drawable.createFromPath(imageFile.getAbsolutePath());
if (drawable != null) { if (drawable != null) {
int sizeInPx = dpToPx(moduleContext, readCheckerSizeDp); int sizeInPx = dpToPx(moduleContext, readCheckerSizeDp);
drawable = scaleDrawable(drawable, sizeInPx, sizeInPx);
imageButton.setImageDrawable(drawable); imageButton.setImageDrawable(drawable);
} }
} }
// 画像ボタンのレイアウト
FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams( FrameLayout.LayoutParams frameParams = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT FrameLayout.LayoutParams.WRAP_CONTENT
@ -260,13 +275,9 @@ public class ReadChecker implements IHook {
frameParams.setMargins(horizontalMarginPx, verticalMarginPx, 0, 0); frameParams.setMargins(horizontalMarginPx, verticalMarginPx, 0, 0);
imageButton.setLayoutParams(frameParams); imageButton.setLayoutParams(frameParams);
imageButton.setOnClickListener(v -> {
imageButton.setOnClickListener(new View.OnClickListener() { if (currentGroupId != null) {
@Override showDataForGroupId(activity, currentGroupId, moduleContext);
public void onClick(View v) {
if (currentGroupId != null) {
showDataForGroupId(activity, currentGroupId, moduleContext);
}
} }
}); });
@ -274,29 +285,25 @@ public class ReadChecker implements IHook {
if (limeOptions.ReadCheckerChatdataDelete.checked) { if (limeOptions.ReadCheckerChatdataDelete.checked) {
Button deleteButton = new Button(activity); Button deleteButton = new Button(activity);
deleteButton.setText(moduleContext.getResources().getString(R.string.Delete)); deleteButton.setText(moduleContext.getResources().getString(R.string.Delete));
deleteButton.setBackgroundColor(Color.RED); // ボタンの背景色を赤に設定 deleteButton.setBackgroundColor(Color.RED);
deleteButton.setTextColor(Color.WHITE); // ボタンのテキスト色を白に設定 deleteButton.setTextColor(Color.WHITE);
FrameLayout.LayoutParams deleteButtonParams = new FrameLayout.LayoutParams( FrameLayout.LayoutParams deleteButtonParams = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT FrameLayout.LayoutParams.WRAP_CONTENT
); );
// Delete ボタンの位置を画像ボタンの右側に設定
deleteButtonParams.setMargins(horizontalMarginPx + dpToPx(moduleContext, readCheckerSizeDp) + 20, verticalMarginPx, 0, 0); deleteButtonParams.setMargins(horizontalMarginPx + dpToPx(moduleContext, readCheckerSizeDp) + 20, verticalMarginPx, 0, 0);
deleteButton.setLayoutParams(deleteButtonParams); deleteButton.setLayoutParams(deleteButtonParams);
deleteButton.setOnClickListener(new View.OnClickListener() { deleteButton.setOnClickListener(v -> {
@Override if (currentGroupId != null) {
public void onClick(View v) { new AlertDialog.Builder(activity)
if (currentGroupId != null) { .setTitle(moduleContext.getResources().getString(R.string.check))
new AlertDialog.Builder(activity) .setMessage(moduleContext.getResources().getString(R.string.really_delete))
.setTitle(moduleContext.getResources().getString(R.string.check)) .setPositiveButton(moduleContext.getResources().getString(R.string.yes), (dialog, which) -> deleteGroupData(currentGroupId, activity, moduleContext))
.setMessage(moduleContext.getResources().getString(R.string.really_delete)) .setNegativeButton(moduleContext.getResources().getString(R.string.no), null)
.setPositiveButton(moduleContext.getResources().getString(R.string.yes), (confirmDialog, confirmWhich) -> deleteGroupData(currentGroupId, activity, moduleContext)) .show();
.setNegativeButton(moduleContext.getResources().getString(R.string.no), null)
.show();
}
} }
}); });
@ -308,6 +315,50 @@ public class ReadChecker implements IHook {
layout.addView(imageButton); layout.addView(imageButton);
} }
private File getFile(Activity activity) {
File dir;
File file;
try {
dir = new File(activity.getExternalFilesDir(null), "LimeBackup");
if (!dir.exists() && !dir.mkdirs()) {
throw new IOException("Failed to create directory");
}
file = new File(dir, "margin_settings.txt");
if (!file.exists() && !file.createNewFile()) {
throw new IOException("Failed to create file");
}
return file;
} catch (Exception e) {
Log.e(TAG, "getExternalFilesDir failed, falling back to MediaStore.", e);
return getFileFromMediaStore(activity, "LimeBackup/margin_settings.txt");
}
}
private File getFileFromMediaStore(Context context, String relativePath) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
ContentResolver resolver = context.getContentResolver();
ContentValues values = new ContentValues();
values.put(MediaStore.Downloads.DISPLAY_NAME, "margin_settings.txt");
values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS + "/LimeBackup");
values.put(MediaStore.Downloads.MIME_TYPE, "text/plain");
Uri fileUri = resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
if (fileUri != null) {
try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(fileUri, "rw")) {
if (pfd != null) {
return new File(context.getFilesDir(), "margin_settings.txt");
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
private int dpToPx(@NonNull Context context, float dp) { private int dpToPx(@NonNull Context context, float dp) {
float density = context.getResources().getDisplayMetrics().density; float density = context.getResources().getDisplayMetrics().density;
return Math.round(dp * density); return Math.round(dp * density);

View File

@ -316,27 +316,21 @@ public class UnsentRec implements IHook {
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()) { result = cursor.getString(0);
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(); 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)))) {
@ -387,16 +381,14 @@ public class UnsentRec implements IHook {
String param12 = null; String param12 = null;
String param22 = null; String param22 = null;
String operationContent = null; String operationContent = null;
String serverId = null;
String talkId = null; String talkId = null;
String serverId = null; // serverIdをここで宣言
String[] parts = operation.split(","); String[] parts = operation.split(",");
for (String part : parts) { for (String part : parts) {
part = part.trim(); part = part.trim();
if (part.startsWith("param1:")) { if (part.startsWith("param1:")) {
talkId = part.substring("param1:".length()).trim(); talkId = part.substring("param1:".length()).trim();
} else if (part.startsWith("param2:")) {
serverId = part.substring("param2:".length()).trim();
} else if (part.startsWith("revision:")) { } else if (part.startsWith("revision:")) {
revision = part.substring("revision:".length()).trim(); revision = part.substring("revision:".length()).trim();
} else if (part.startsWith("createdTime:")) { } else if (part.startsWith("createdTime:")) {
@ -414,10 +406,23 @@ public class UnsentRec implements IHook {
} }
} }
if (serverId != null && talkId != null) { // typeがNOTIFIED_DESTROY_MESSAGEの場合のみserverIdを設定
// 新しいメソッドを呼び出してメッセージを取り消し済みとして更新 if ("NOTIFIED_DESTROY_MESSAGE".equals(type)) {
for (String part : parts) {
part = part.trim();
if (part.startsWith("param2:")) {
serverId = part.substring("param2:".length()).trim();
break; // param2が見つかったらループを抜ける
}
}
updateMessageAsCanceled(db1, serverId,context,moduleContext); updateMessageAsCanceled(db1, serverId,context,moduleContext);
// serverIdを使用した処理をここに追加
XposedBridge.log("Server ID for NOTIFIED_DESTROY_MESSAGE: " + serverId);
}
// serverIdとtalkIdが両方ともnullでない場合に処理を行う
if (serverId != null && talkId != null) {
XposedBridge.log(paramValue + serverId);
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);
@ -459,6 +464,8 @@ public class UnsentRec implements IHook {
break; break;
} }
} }
XposedBridge.log("S" + serverId);
// 新しいメソッドを呼び出してメッセージを取り消し済みとして更新
String logEntry = (timeFormatted != null ? timeFormatted : "No Time: ") String logEntry = (timeFormatted != null ? timeFormatted : "No Time: ")
+ name + name
@ -482,21 +489,16 @@ public class UnsentRec implements IHook {
} }
} }
} }
private void canceled_message(XC_LoadPackage.LoadPackageParam loadPackageParam, Context context, SQLiteDatabase db1, SQLiteDatabase db2, Context moduleContext) { 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); Class<?> chatHistoryRequestClass = XposedHelpers.findClass("com.linecorp.line.chat.request.ChatHistoryRequest", loadPackageParam.classLoader);
XposedHelpers.findAndHookMethod(chatHistoryRequestClass, "getChatId", new XC_MethodHook() { XposedHelpers.findAndHookMethod(chatHistoryRequestClass, "getChatId", new XC_MethodHook() {
@Override @Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable { protected void afterHookedMethod(MethodHookParam param) throws Throwable {
String chatId = (String) param.getResult(); String chatId = (String) param.getResult();
// チャット ID をログに出力
XposedBridge.log("Chat ID: " + chatId);
String canceledContent = getCanceledContentFromFile(context, moduleContext); String canceledContent = getCanceledContentFromFile(context, moduleContext);
// チェックボックスの状態を確認 // チェックボックスの状態を確認
if (limeOptions.hide_canceled_message.checked) { if (limeOptions.hide_canceled_message.checked) {
XposedBridge.log("hide_canceled_message is checked");
// content server_id, chat_id を取得 (複数レコードを想定) // content server_id, chat_id を取得 (複数レコードを想定)
Cursor cursor = db1.rawQuery("SELECT content, server_id, chat_id, parameter FROM chat_history WHERE chat_id=?", new String[]{chatId}); Cursor cursor = db1.rawQuery("SELECT content, server_id, chat_id, parameter FROM chat_history WHERE chat_id=?", new String[]{chatId});
@ -530,10 +532,9 @@ public class UnsentRec implements IHook {
cursor.close(); cursor.close();
} }
} else { } else {
XposedBridge.log("No rows found for chat_id: " + chatId);
} }
} else { } else {
XposedBridge.log("hide_canceled_message is NOT checked");
// hide_canceled_message.checked false の場合 // hide_canceled_message.checked false の場合
String chatId1 = "/" + (String) param.getResult(); String chatId1 = "/" + (String) param.getResult();
@ -578,10 +579,10 @@ public class UnsentRec implements IHook {
private void updateMessageAsCanceled(SQLiteDatabase db1, String serverId, Context context, Context moduleContext) { private void updateMessageAsCanceled(SQLiteDatabase db1, String serverId, Context context, Context moduleContext) {
// canceledContent をファイルから取得 // canceledContent をファイルから取得
String canceledContent = getCanceledContentFromFile(context, moduleContext); String canceledContent = getCanceledContentFromFile(context, moduleContext);
// 既存のメッセージを取得 // 既存のメッセージを取得
Cursor cursor = db1.rawQuery("SELECT * FROM chat_history WHERE server_id=?", new String[]{serverId}); Cursor cursor = db1.rawQuery("SELECT * FROM chat_history WHERE server_id=?", new String[]{serverId});
if (cursor.moveToFirst()) { if (cursor.moveToFirst()) {
// カラムの値を取得null の場合もそのまま代入 // カラムの値を取得null の場合もそのまま代入
String type = cursor.isNull(cursor.getColumnIndex("type")) ? null : cursor.getString(cursor.getColumnIndex("type")); String type = cursor.isNull(cursor.getColumnIndex("type")) ? null : cursor.getString(cursor.getColumnIndex("type"));
@ -609,6 +610,7 @@ public class UnsentRec implements IHook {
// 既存のレコードがあるか確認 // 既存のレコードがあるか確認
Cursor existingCursor = db1.rawQuery("SELECT * FROM chat_history WHERE server_id=? AND content=?", new String[]{serverId, chatId}); Cursor existingCursor = db1.rawQuery("SELECT * FROM chat_history WHERE server_id=? AND content=?", new String[]{serverId, chatId});
if (!existingCursor.moveToFirst()) { if (!existingCursor.moveToFirst()) {
XposedBridge.log(serverId);
// 新しいレコードを挿入 // 新しいレコードを挿入
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put("server_id", serverId); values.put("server_id", serverId);
@ -626,11 +628,14 @@ public class UnsentRec implements IHook {
values.put("location_phone", locationPhone); values.put("location_phone", locationPhone);
if (locationLatitude != null) values.put("location_latitude", locationLatitude); if (locationLatitude != null) values.put("location_latitude", locationLatitude);
if (locationLongitude != null) values.put("location_longitude", locationLongitude); if (locationLongitude != null) values.put("location_longitude", locationLongitude);
values.put("attachement_image", attachmentImage); values.put("attachement_image", "0");
if (attachmentImageHeight != null) values.put("attachement_image_height", attachmentImageHeight); if (attachmentImageHeight != null) values.put("attachement_image_height", attachmentImageHeight);
else values.put("attachement_image_height", (String) null);
if (attachmentImageWidth != null) values.put("attachement_image_width", attachmentImageWidth); if (attachmentImageWidth != null) values.put("attachement_image_width", attachmentImageWidth);
else values.put("attachement_image_width", (String) null);
if (attachmentImageSize != null) values.put("attachement_image_size", attachmentImageSize); if (attachmentImageSize != null) values.put("attachement_image_size", attachmentImageSize);
values.put("attachement_type", attachmentType); else values.put("attachement_image_size", (String) null);
values.put("attachement_type", "0");
values.put("attachement_local_uri", attachmentLocalUri); values.put("attachement_local_uri", attachmentLocalUri);
values.put("parameter", "LIMEsUnsend"); // ここを修正 values.put("parameter", "LIMEsUnsend"); // ここを修正
values.put("chunks", chunks); values.put("chunks", chunks);