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

既読者機能の修正

This commit is contained in:
areteruhiro 2024-12-25 02:22:46 +09:00
parent 87f45c7961
commit 64a87bbce8
9 changed files with 245 additions and 74 deletions

View File

@ -42,6 +42,9 @@ public class LimeOptions {
public Option removeAllServices = new Option("remove_Services", R.string.RemoveService, false); public Option removeAllServices = new Option("remove_Services", R.string.RemoveService, false);
public Option calltone = new Option("calltone", R.string.calltone, false); public Option calltone = new Option("calltone", R.string.calltone, false);
public Option ReadChecker = new Option("ReadChecker", R.string.ReadChecker, false); public Option ReadChecker = new Option("ReadChecker", R.string.ReadChecker, false);
public Option MySendMessage = new Option("MySendMessage", R.string.MySendMessage, 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);
@ -69,7 +72,7 @@ public class LimeOptions {
preventUnsendMessage, preventUnsendMessage,
sendMuteMessage, sendMuteMessage,
Archived, Archived,
ReadChecker, ReadChecker,MySendMessage,
removeKeepUnread, removeKeepUnread,
KeepUnreadLSpatch, KeepUnreadLSpatch,
blockTracking, blockTracking,
@ -83,4 +86,5 @@ public class LimeOptions {
}; };
} }

View File

@ -37,6 +37,7 @@ import io.github.hiro.lime.hooks.RemoveIcons;
import io.github.hiro.lime.hooks.RemoveNotification; import io.github.hiro.lime.hooks.RemoveNotification;
import io.github.hiro.lime.hooks.RemoveReplyMute; import io.github.hiro.lime.hooks.RemoveReplyMute;
import io.github.hiro.lime.hooks.RemoveVoiceRecord; import io.github.hiro.lime.hooks.RemoveVoiceRecord;
import io.github.hiro.lime.hooks.Resend;
import io.github.hiro.lime.hooks.Ringtone; import io.github.hiro.lime.hooks.Ringtone;
import io.github.hiro.lime.hooks.SendMuteMessage; import io.github.hiro.lime.hooks.SendMuteMessage;
import io.github.hiro.lime.hooks.SpoofAndroidId; import io.github.hiro.lime.hooks.SpoofAndroidId;
@ -45,6 +46,7 @@ import io.github.hiro.lime.hooks.UnsentRec;
import io.github.hiro.lime.hooks.Archived; import io.github.hiro.lime.hooks.Archived;
import io.github.hiro.lime.hooks.ReadChecker; import io.github.hiro.lime.hooks.ReadChecker;
import io.github.hiro.lime.hooks.DarkColor; import io.github.hiro.lime.hooks.DarkColor;
import io.github.hiro.lime.hooks.test;
public class Main implements IXposedHookLoadPackage, IXposedHookInitPackageResources, IXposedHookZygoteInit { public class Main implements IXposedHookLoadPackage, IXposedHookInitPackageResources, IXposedHookZygoteInit {
@ -86,7 +88,7 @@ public class Main implements IXposedHookLoadPackage, IXposedHookInitPackageResou
new RemoveNotification(), new RemoveNotification(),
new Disabled_Group_notification(), new Disabled_Group_notification(),
new PhotoAddNotification(), new PhotoAddNotification(),
new RemoveVoiceRecord() new RemoveVoiceRecord(),
}; };
public void handleLoadPackage(@NonNull XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable { public void handleLoadPackage(@NonNull XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {

View File

@ -1,11 +1,14 @@
package io.github.hiro.lime.hooks; package io.github.hiro.lime.hooks;
import android.app.AndroidAppHelper; import android.app.AndroidAppHelper;
import android.content.Context; import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.util.DisplayMetrics;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -18,18 +21,20 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
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.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;
public class KeepUnread implements IHook { public class KeepUnread implements IHook {
static boolean keepUnread = true; static boolean keepUnread = 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 (limeOptions.removeKeepUnread.checked) return; if (limeOptions.removeKeepUnread.checked) return;
XposedHelpers.findAndHookMethod( XposedHelpers.findAndHookMethod(
"com.linecorp.line.chatlist.view.fragment.ChatListFragment", "com.linecorp.line.chatlist.view.fragment.ChatListFragment",
loadPackageParam.classLoader, loadPackageParam.classLoader,
@ -41,28 +46,38 @@ public class KeepUnread implements IHook {
View rootView = (View) param.getResult(); View rootView = (View) param.getResult();
Context appContext = rootView.getContext(); Context appContext = rootView.getContext();
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);
RelativeLayout layout = new RelativeLayout(appContext); RelativeLayout layout = new RelativeLayout(appContext);
RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT); RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
layout.setLayoutParams(layoutParams); layout.setLayoutParams(layoutParams);
keepUnread = readStateFromFile(appContext); keepUnread = readStateFromFile(appContext);
ImageView imageView = new ImageView(appContext); ImageView imageView = new ImageView(appContext);
updateSwitchImage(imageView, keepUnread, moduleContext); updateSwitchImage(imageView, keepUnread, moduleContext);
DisplayMetrics displayMetrics = appContext.getResources().getDisplayMetrics();
int screenWidth = displayMetrics.widthPixels;
int screenHeight = displayMetrics.heightPixels;
int horizontalMargin = (int) (screenWidth * 0.5); Resources resources = appContext.getResources();
int verticalMargin = (int) (screenHeight * 0.015); Configuration configuration = resources.getConfiguration();
int smallestWidthDp = configuration.smallestScreenWidthDp;
float density = resources.getDisplayMetrics().density;
int horizontalMarginPx = (int) ((smallestWidthDp * 0.5) * density);
int verticalMarginPx = (int) (15 * density);
RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams( RelativeLayout.LayoutParams imageParams = new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
imageParams.setMargins(horizontalMargin, verticalMargin, 0, 0); // 動的に計算されたマージンを設定 imageParams.setMargins(horizontalMarginPx, verticalMarginPx, 0, 0);
imageView.setOnClickListener(v -> { imageView.setOnClickListener(v -> {
keepUnread = !keepUnread; keepUnread = !keepUnread;
@ -70,23 +85,40 @@ public class KeepUnread implements IHook {
saveStateToFile(appContext, keepUnread); saveStateToFile(appContext, keepUnread);
}); });
layout.addView(imageView, imageParams); layout.addView(imageView, imageParams);
if (rootView instanceof ViewGroup) { if (rootView instanceof ViewGroup) {
ViewGroup rootViewGroup = (ViewGroup) rootView; ViewGroup rootViewGroup = (ViewGroup) rootView;
if (rootViewGroup.getChildCount() > 0 && rootViewGroup.getChildAt(0) instanceof ListView) {
ListView listView = (ListView) rootViewGroup.getChildAt(0);
boolean added = false;
for (int i = 0; i < rootViewGroup.getChildCount(); i++) {
View child = rootViewGroup.getChildAt(i);
if (child instanceof ListView) {
ListView listView = (ListView) child;
listView.addFooterView(layout); listView.addFooterView(layout);
} else { added = true;
break;
}
}
if (!added) {
rootViewGroup.addView(layout); rootViewGroup.addView(layout);
} }
} }
} }
private void updateSwitchImage(ImageView imageView, boolean isOn, Context moduleContext) { private void updateSwitchImage(ImageView imageView, boolean isOn, Context moduleContext) {
String imageName = isOn ? "switch_on" : "switch_off"; String imageName = isOn ? "switch_on" : "switch_off";
int imageResource = moduleContext.getResources().getIdentifier(imageName, "drawable", "io.github.hiro.lime"); int imageResource = moduleContext.getResources().getIdentifier(imageName, "drawable", "io.github.hiro.lime");
if (imageResource != 0) { if (imageResource != 0) {
Drawable drawable = moduleContext.getResources().getDrawable(imageResource, null); Drawable drawable = moduleContext.getResources().getDrawable(imageResource, null);
if (drawable != null) { if (drawable != null) {
@ -96,12 +128,14 @@ public class KeepUnread implements IHook {
} }
} }
private Drawable scaleDrawable(Drawable drawable, int width, int height) { private Drawable scaleDrawable(Drawable drawable, int width, int height) {
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap(); Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, width, height, true); Bitmap scaledBitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
return new BitmapDrawable(scaledBitmap); return new BitmapDrawable(scaledBitmap);
} }
private void saveStateToFile(Context context, boolean state) { private void saveStateToFile(Context context, boolean state) {
String filename = "keep_unread_state.txt"; String filename = "keep_unread_state.txt";
try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) { try (FileOutputStream fos = context.openFileOutput(filename, Context.MODE_PRIVATE)) {
@ -110,6 +144,7 @@ public class KeepUnread implements IHook {
} }
} }
private boolean readStateFromFile(Context context) { private boolean readStateFromFile(Context context) {
String filename = "keep_unread_state.txt"; String filename = "keep_unread_state.txt";
try (FileInputStream fis = context.openFileInput(filename)) { try (FileInputStream fis = context.openFileInput(filename)) {
@ -126,6 +161,7 @@ public class KeepUnread implements IHook {
} }
); );
XposedHelpers.findAndHookMethod( XposedHelpers.findAndHookMethod(
loadPackageParam.classLoader.loadClass(Constants.MARK_AS_READ_HOOK.className), loadPackageParam.classLoader.loadClass(Constants.MARK_AS_READ_HOOK.className),
Constants.MARK_AS_READ_HOOK.methodName, Constants.MARK_AS_READ_HOOK.methodName,
@ -138,19 +174,6 @@ public class KeepUnread implements IHook {
} }
} }
); );
}
}
XposedBridge.hookAllMethods(
loadPackageParam.classLoader.loadClass(Constants.RESPONSE_HOOK.className),
Constants.RESPONSE_HOOK.methodName,
new XC_MethodHook() {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (param.args[0] != null && param.args[0].toString().equals("sendChatChecked")) {
param.setResult(null);
}
}
}
);
}
}

View File

@ -1,6 +1,10 @@
package io.github.hiro.lime.hooks; package io.github.hiro.lime.hooks;
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;
@ -49,6 +53,8 @@ public class ReadChecker implements IHook {
private SQLiteDatabase db4 = null; private SQLiteDatabase db4 = null;
private boolean shouldHookOnCreate = false; private boolean shouldHookOnCreate = false;
private String currentGroupId = null; private String currentGroupId = null;
@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 (!limeOptions.ReadChecker.checked) return; if (!limeOptions.ReadChecker.checked) return;
@ -57,6 +63,7 @@ public class ReadChecker implements IHook {
protected void afterHookedMethod(MethodHookParam param) throws Throwable { protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Application appContext = (Application) param.thisObject; Application appContext = (Application) param.thisObject;
if (appContext == null) { if (appContext == null) {
return; return;
} }
@ -68,14 +75,19 @@ public class ReadChecker implements IHook {
SQLiteDatabase.OpenParams dbParams1 = builder1.build(); SQLiteDatabase.OpenParams dbParams1 = builder1.build();
SQLiteDatabase.OpenParams.Builder builder2 = new SQLiteDatabase.OpenParams.Builder(); SQLiteDatabase.OpenParams.Builder builder2 = new SQLiteDatabase.OpenParams.Builder();
builder2.addOpenFlags(SQLiteDatabase.OPEN_READWRITE); builder2.addOpenFlags(SQLiteDatabase.OPEN_READWRITE);
SQLiteDatabase.OpenParams dbParams2 = builder2.build(); SQLiteDatabase.OpenParams dbParams2 = builder2.build();
db3 = SQLiteDatabase.openDatabase(dbFile3, dbParams1); db3 = SQLiteDatabase.openDatabase(dbFile3, dbParams1);
db4 = SQLiteDatabase.openDatabase(dbFile4, dbParams2); db4 = SQLiteDatabase.openDatabase(dbFile4, dbParams2);
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);
initializeLimeDatabase(appContext); initializeLimeDatabase(appContext);
@ -85,12 +97,14 @@ public class ReadChecker implements IHook {
}); });
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();
//XposedBridge.log(chatId); ////XposedBridge.log(chatId);
if (isGroupExists(chatId)) { if (isGroupExists(chatId)) {
shouldHookOnCreate = true; shouldHookOnCreate = true;
currentGroupId = chatId; currentGroupId = chatId;
@ -102,10 +116,13 @@ public class ReadChecker implements IHook {
}); });
Class<?> chatHistoryActivityClass = XposedHelpers.findClass("jp.naver.line.android.activity.chathistory.ChatHistoryActivity", loadPackageParam.classLoader); Class<?> chatHistoryActivityClass = XposedHelpers.findClass("jp.naver.line.android.activity.chathistory.ChatHistoryActivity", loadPackageParam.classLoader);
XposedHelpers.findAndHookMethod(chatHistoryActivityClass, "onCreate", Bundle.class, new XC_MethodHook() { XposedHelpers.findAndHookMethod(chatHistoryActivityClass, "onCreate", Bundle.class, new XC_MethodHook() {
Context moduleContext; Context moduleContext;
@Override @Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable { protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (moduleContext == null) { if (moduleContext == null) {
@ -113,22 +130,25 @@ public class ReadChecker implements IHook {
Context systemContext = (Context) XposedHelpers.callMethod(param.thisObject, "getApplicationContext"); Context systemContext = (Context) XposedHelpers.callMethod(param.thisObject, "getApplicationContext");
moduleContext = systemContext.createPackageContext("io.github.hiro.lime", Context.CONTEXT_IGNORE_SECURITY); moduleContext = systemContext.createPackageContext("io.github.hiro.lime", Context.CONTEXT_IGNORE_SECURITY);
} catch (Exception e) { } catch (Exception e) {
XposedBridge.log("Failed to get module context: " + e.getMessage()); //XposedBridge.log("Failed to get module context: " + e.getMessage());
} }
} }
} }
@Override @Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable { protected void afterHookedMethod(MethodHookParam param) throws Throwable {
if (moduleContext == null) { if (moduleContext == null) {
XposedBridge.log("Module context is null. Skipping hook."); //XposedBridge.log("Module context is null. Skipping hook.");
return; return;
} }
if (shouldHookOnCreate && currentGroupId != null) { if (shouldHookOnCreate && currentGroupId != null) {
if (!isNoGroup(currentGroupId)) { if (!isNoGroup(currentGroupId)) {
Activity activity = (Activity) param.thisObject; Activity activity = (Activity) param.thisObject;
addButton(activity, moduleContext); addButton(activity, moduleContext);
} }
} }
@ -136,11 +156,14 @@ public class ReadChecker implements IHook {
}); });
} }
private boolean isGroupExists(String groupId) { private boolean isGroupExists(String groupId) {
if (limeDatabase == null) { if (limeDatabase == null) {
// //XposedBridge.log("Database is not initialized."); // ////XposedBridge.log("Database is not initialized.");
return false; return false;
} }
String query = "SELECT 1 FROM group_messages WHERE group_id = ?"; String query = "SELECT 1 FROM group_messages WHERE group_id = ?";
@ -150,9 +173,10 @@ public class ReadChecker implements IHook {
return exists; return exists;
} }
private boolean isNoGroup(String groupId) { private boolean isNoGroup(String groupId) {
if (limeDatabase == null) { if (limeDatabase == null) {
// //XposedBridge.log("Database is not initialized."); // ////XposedBridge.log("Database is not initialized.");
return true; return true;
} }
String query = "SELECT group_name FROM group_messages WHERE group_id = ?"; String query = "SELECT group_name FROM group_messages WHERE group_id = ?";
@ -163,17 +187,21 @@ public class ReadChecker implements IHook {
noGroup = groupName == null || groupName.isEmpty(); noGroup = groupName == null || groupName.isEmpty();
} }
cursor.close(); cursor.close();
return noGroup; return noGroup;
} }
private void addButton(Activity activity, Context moduleContext) { private void addButton(Activity activity, Context moduleContext) {
Button button = new Button(activity); Button button = new Button(activity);
button.setText("R"); button.setText("R");
button.setBackgroundColor(Color.BLACK); button.setBackgroundColor(Color.BLACK);
button.setTextColor(Color.WHITE); button.setTextColor(Color.WHITE);
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
@ -182,6 +210,7 @@ public class ReadChecker implements IHook {
frameParams.topMargin = 150; frameParams.topMargin = 150;
button.setLayoutParams(frameParams); button.setLayoutParams(frameParams);
button.setOnClickListener(new View.OnClickListener() { button.setOnClickListener(new View.OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
@ -191,28 +220,36 @@ public class ReadChecker implements IHook {
} }
}); });
ViewGroup layout = activity.findViewById(android.R.id.content); ViewGroup layout = activity.findViewById(android.R.id.content);
layout.addView(button); layout.addView(button);
} }
private void showDataForGroupId(Activity activity, String groupId, Context moduleContext) { private void showDataForGroupId(Activity activity, String groupId, Context moduleContext) {
if (limeDatabase == null) { if (limeDatabase == null) {
return; return;
} }
String query = "SELECT server_id, content, created_time FROM group_messages WHERE group_id=? ORDER BY created_time ASC"; String query = "SELECT server_id, content, created_time FROM group_messages WHERE group_id=? ORDER BY created_time ASC";
Cursor cursor = limeDatabase.rawQuery(query, new String[]{groupId}); Cursor cursor = limeDatabase.rawQuery(query, new String[]{groupId});
Map<String, DataItem> dataItemMap = new HashMap<>(); Map<String, DataItem> dataItemMap = new HashMap<>();
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
String serverId = cursor.getString(0); String serverId = cursor.getString(0);
String content = cursor.getString(1); String content = cursor.getString(1);
String createdTime = cursor.getString(2); String createdTime = cursor.getString(2);
List<String> user_nameList = getuser_namesForServerId(serverId); List<String> user_nameList = getuser_namesForServerId(serverId);
if (dataItemMap.containsKey(serverId)) { if (dataItemMap.containsKey(serverId)) {
DataItem existingItem = dataItemMap.get(serverId); DataItem existingItem = dataItemMap.get(serverId);
existingItem.user_names.addAll(user_nameList); existingItem.user_names.addAll(user_nameList);
@ -224,16 +261,26 @@ public class ReadChecker implements IHook {
} }
cursor.close(); cursor.close();
List<DataItem> sortedDataItems = new ArrayList<>(dataItemMap.values()); List<DataItem> sortedDataItems = new ArrayList<>(dataItemMap.values());
Collections.sort(sortedDataItems, Comparator.comparing(item -> item.createdTime)); Collections.sort(sortedDataItems, Comparator.comparing(item -> item.createdTime));
StringBuilder resultBuilder = new StringBuilder(); StringBuilder resultBuilder = new StringBuilder();
for (DataItem item : sortedDataItems) { for (DataItem item : sortedDataItems) {
resultBuilder.append("Content: ").append(item.content != null ? item.content : "Media").append("\n"); resultBuilder.append("Content: ").append(item.content != null ? item.content : "Media").append("\n");
resultBuilder.append("Created Time: ").append(item.createdTime).append("\n"); resultBuilder.append("Created Time: ").append(item.createdTime).append("\n");
if (!item.user_names.isEmpty()) { if (!item.user_names.isEmpty()) {
resultBuilder.append(moduleContext.getResources().getString(R.string.Reader)+" (").append(item.user_names.size()).append("):\n");
int newlineCount = 0;
for (String user_name : item.user_names) {
newlineCount += countNewlines(user_name);
}
resultBuilder.append(moduleContext.getResources().getString(R.string.Reader))
.append(" (").append(item.user_names.size() + newlineCount).append("):\n");
for (String user_name : item.user_names) { for (String user_name : item.user_names) {
resultBuilder.append("- ").append(user_name).append("\n"); resultBuilder.append("- ").append(user_name).append("\n");
} }
@ -243,23 +290,31 @@ public class ReadChecker implements IHook {
resultBuilder.append("\n"); resultBuilder.append("\n");
} }
TextView textView = new TextView(activity); TextView textView = new TextView(activity);
textView.setText(resultBuilder.toString()); textView.setText(resultBuilder.toString());
textView.setPadding(20, 20, 20, 20); textView.setPadding(20, 20, 20, 20);
ScrollView scrollView = new ScrollView(activity); ScrollView scrollView = new ScrollView(activity);
scrollView.addView(textView); scrollView.addView(textView);
AlertDialog.Builder builder = new AlertDialog.Builder(activity); AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle("READ Data"); builder.setTitle("READ Data");
builder.setView(scrollView); builder.setView(scrollView);
builder.setPositiveButton("OK", null); builder.setPositiveButton("OK", null);
builder.setNegativeButton(moduleContext.getResources().getString(R.string.Delete), (dialog, which) -> { builder.setNegativeButton(moduleContext.getResources().getString(R.string.Delete), (dialog, which) -> {
new AlertDialog.Builder(activity) new AlertDialog.Builder(activity)
.setTitle(moduleContext.getResources().getString(R.string.check)) .setTitle(moduleContext.getResources().getString(R.string.check))
.setMessage(moduleContext.getResources().getString(R.string.really_delete)) .setMessage(moduleContext.getResources().getString(R.string.really_delete))
@ -268,46 +323,69 @@ public class ReadChecker implements IHook {
.show(); .show();
}); });
AlertDialog dialog = builder.create(); AlertDialog dialog = builder.create();
dialog.show(); dialog.show();
scrollView.post(() -> scrollView.fullScroll(View.FOCUS_DOWN)); scrollView.post(() -> scrollView.fullScroll(View.FOCUS_DOWN));
} }
private void deleteGroupData(String groupId, Activity activity, Context moduleContext) { private void deleteGroupData(String groupId, Activity activity, Context moduleContext) {
if (limeDatabase == null) { if (limeDatabase == null) {
return; return;
} }
String deleteQuery = "DELETE FROM group_messages WHERE group_id=?"; String deleteQuery = "DELETE FROM group_messages WHERE group_id=?";
limeDatabase.execSQL(deleteQuery, new String[]{groupId}); limeDatabase.execSQL(deleteQuery, new String[]{groupId});
Toast.makeText(activity, moduleContext.getResources().getString(R.string.Reader_Data_Delete_Success), Toast.LENGTH_SHORT).show(); Toast.makeText(activity, moduleContext.getResources().getString(R.string.Reader_Data_Delete_Success), Toast.LENGTH_SHORT).show();
} }
private List<String> getuser_namesForServerId(String serverId) { private List<String> getuser_namesForServerId(String serverId) {
if (limeDatabase == null) { if (limeDatabase == null) {
return Collections.emptyList(); return Collections.emptyList();
} }
String query = "SELECT user_name FROM group_messages WHERE server_id=?";
// user_name のすべてのエントリを取得する
String query = "SELECT user_name FROM group_messages WHERE server_id=? ORDER BY created_time ASC";
Cursor cursor = limeDatabase.rawQuery(query, new String[]{serverId}); Cursor cursor = limeDatabase.rawQuery(query, new String[]{serverId});
List<String> userNames = new ArrayList<>(); List<String> user_names = new ArrayList<>();
while (cursor.moveToNext()) {
if (cursor.moveToFirst()) {
String userNameStr = cursor.getString(0); String userNameStr = cursor.getString(0);
if (userNameStr != null) { if (userNameStr != null) {
// user_nameをそのままリストに追加
userNames.add(userNameStr);
String[] names = userNameStr.split("\n");
Collections.addAll(user_names, names);
} }
} }
cursor.close(); cursor.close();
return userNames; return user_names;
} }
private int countNewlines(String text) {
if (text == null || text.isEmpty()) {
return 0;
}
int count = 0;
for (char c : text.toCharArray()) {
if (c == '\n') {
count++;
}
}
return count;
}
private static class DataItem { private static class DataItem {
String serverId; String serverId;
String content; String content;
String createdTime; String createdTime;
Set<String> user_names; Set<String> user_names;
DataItem(String serverId, String content, String createdTime) { DataItem(String serverId, String content, String createdTime) {
this.serverId = serverId; this.serverId = serverId;
this.content = content; this.content = content;
@ -315,6 +393,8 @@ public class ReadChecker implements IHook {
this.user_names = new HashSet<>(); this.user_names = new HashSet<>();
} }
} }
private void catchNotification(XC_LoadPackage.LoadPackageParam loadPackageParam, SQLiteDatabase db3, SQLiteDatabase db4, Context appContext, Context moduleContext) { private void catchNotification(XC_LoadPackage.LoadPackageParam loadPackageParam, SQLiteDatabase db3, SQLiteDatabase db4, Context appContext, Context moduleContext) {
try { try {
XposedBridge.hookAllMethods( XposedBridge.hookAllMethods(
@ -324,12 +404,12 @@ public class ReadChecker implements IHook {
@Override @Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable { protected void afterHookedMethod(MethodHookParam param) throws Throwable {
String paramValue = param.args[0].toString(); String paramValue = param.args[0].toString();
//XposedBridge.log(paramValue);
if (appContext == null) { if (appContext == null) {
//XposedBridge.log("appContext is null!"); //
return; return;
} }
Context moduleContext; Context moduleContext;
try { try {
moduleContext = appContext.createPackageContext( moduleContext = appContext.createPackageContext(
@ -339,6 +419,7 @@ public class ReadChecker implements IHook {
return; return;
} }
if (paramValue != null && paramValue.contains("type:NOTIFIED_READ_MESSAGE")) { if (paramValue != null && paramValue.contains("type:NOTIFIED_READ_MESSAGE")) {
List<String> messages = extractMessages(paramValue); List<String> messages = extractMessages(paramValue);
for (String message : messages) { for (String message : messages) {
@ -353,21 +434,26 @@ public class ReadChecker implements IHook {
} }
} }
private List<String> extractMessages(String paramValue) { private List<String> extractMessages(String paramValue) {
List<String> messages = new ArrayList<>(); List<String> messages = new ArrayList<>();
Pattern pattern = Pattern.compile("type:NOTIFIED_READ_MESSAGE.*?(?=type:|$)"); Pattern pattern = Pattern.compile("type:NOTIFIED_READ_MESSAGE.*?(?=type:|$)");
Matcher matcher = pattern.matcher(paramValue); Matcher matcher = pattern.matcher(paramValue);
while (matcher.find()) { while (matcher.find()) {
messages.add(matcher.group().trim()); messages.add(matcher.group().trim());
} }
return messages; return messages;
} }
private void fetchDataAndSave(SQLiteDatabase db3, SQLiteDatabase db4, String paramValue, Context context, Context moduleContext) { private void fetchDataAndSave(SQLiteDatabase db3, SQLiteDatabase db4, String paramValue, Context context, Context moduleContext) {
File dbFile = new File(context.getFilesDir(), "data_log.txt"); File dbFile = new File(context.getFilesDir(), "data_log.txt");
try { try {
String serverId = extractServerId(paramValue, context); String serverId = extractServerId(paramValue, context);
String checkedUser = extractCheckedUser(paramValue); String checkedUser = extractCheckedUser(paramValue);
@ -375,6 +461,7 @@ public class ReadChecker implements IHook {
writeToFile(dbFile, "Missing parameters: serverId=" + serverId + ", checkedUser=" + checkedUser); writeToFile(dbFile, "Missing parameters: serverId=" + serverId + ", checkedUser=" + checkedUser);
return; return;
} }
String SendUser = queryDatabase(db3, "SELECT from_mid FROM chat_history WHERE server_id=?", serverId);
String groupId = queryDatabase(db3, "SELECT chat_id FROM chat_history WHERE server_id=?", serverId); String groupId = queryDatabase(db3, "SELECT chat_id FROM chat_history WHERE server_id=?", serverId);
String groupName = queryDatabase(db3, "SELECT name FROM groups WHERE id=?", groupId); String groupName = queryDatabase(db3, "SELECT name FROM groups WHERE id=?", groupId);
String content = queryDatabase(db3, "SELECT content FROM chat_history WHERE server_id=?", serverId); String content = queryDatabase(db3, "SELECT content FROM chat_history WHERE server_id=?", serverId);
@ -400,15 +487,17 @@ public class ReadChecker implements IHook {
} }
} }
String finalContent = (content != null && !content.isEmpty()) ? content : (!mediaDescription.isEmpty() ? mediaDescription : "No content:" + serverId); String finalContent = (content != null && !content.isEmpty()) ? content : (!mediaDescription.isEmpty() ? mediaDescription : "No content:" + serverId);
saveData(groupId, serverId, checkedUser, groupName, finalContent, user_name, timeFormatted, context); saveData(SendUser, groupId, serverId, checkedUser, groupName, finalContent, user_name, timeFormatted, context);
// markPreviousMessagesAsRead(groupId, checkedUser, timeEpochStr, context); // markPreviousMessagesAsRead(groupId, checkedUser, timeEpochStr, context);
} catch (Exception e) { } catch (Exception e) {
} }
} }
private void markPreviousMessagesAsRead(String groupId, String checkedUser, String timeEpochStr, Context context) { private void markPreviousMessagesAsRead(String groupId, String checkedUser, String timeEpochStr, Context context) {
initializeLimeDatabase(context); initializeLimeDatabase(context);
try { try {
String query = "SELECT server_id, content, created_time, user_name FROM group_messages " + String query = "SELECT server_id, content, created_time, user_name FROM group_messages " +
"WHERE group_id=? AND created_time<? AND user_name NOT LIKE ?"; "WHERE group_id=? AND created_time<? AND user_name NOT LIKE ?";
@ -423,10 +512,12 @@ public class ReadChecker implements IHook {
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put("user_name", updatedUserName); values.put("user_name", updatedUserName);
limeDatabase.update("group_messages", values, "group_id=? AND server_id=?", limeDatabase.update("group_messages", values, "group_id=? AND server_id=?",
new String[]{groupId, previousServerId}); new String[]{groupId, previousServerId});
// //XposedBridge.log("Marked as read in lime_data.db: Group_id: " + groupId + ", Server_id: " + previousServerId + ", Updated user_name: " + updatedUserName);
// ////XposedBridge.log("Marked as read in lime_data.db: Group_id: " + groupId + ", Server_id: " + previousServerId + ", Updated user_name: " + updatedUserName);
} }
cursor.close(); cursor.close();
} catch (Exception e) { } catch (Exception e) {
@ -434,6 +525,7 @@ public class ReadChecker implements IHook {
} }
} }
private void writeToFile(File file, String text) { private void writeToFile(File file, String text) {
try (FileWriter writer = new FileWriter(file, true)) { try (FileWriter writer = new FileWriter(file, true)) {
writer.write(text + "\n"); writer.write(text + "\n");
@ -441,6 +533,7 @@ public class ReadChecker implements IHook {
} }
} }
private String formatMessageTime(String timeEpochStr) { private String formatMessageTime(String timeEpochStr) {
if (timeEpochStr == null) return null; if (timeEpochStr == null) return null;
long timeEpoch = Long.parseLong(timeEpochStr); long timeEpoch = Long.parseLong(timeEpochStr);
@ -448,43 +541,55 @@ public class ReadChecker implements IHook {
return sdf.format(new Date(timeEpoch)); return sdf.format(new Date(timeEpoch));
} }
private String extractCheckedUser(String paramValue) { private String extractCheckedUser(String paramValue) {
Pattern pattern = Pattern.compile("param2:([a-zA-Z0-9]+)"); Pattern pattern = Pattern.compile("param2:([a-zA-Z0-9]+)");
Matcher matcher = pattern.matcher(paramValue); Matcher matcher = pattern.matcher(paramValue);
return matcher.find() ? matcher.group(1) : null; return matcher.find() ? matcher.group(1) : null;
} }
private String extractServerId(String paramValue, Context context) { private String extractServerId(String paramValue, Context context) {
Pattern pattern = Pattern.compile("param3:([0-9]+)"); Pattern pattern = Pattern.compile("param3:([0-9]+)");
Matcher matcher = pattern.matcher(paramValue); Matcher matcher = pattern.matcher(paramValue);
//XposedBridge.log(paramValue);
if (matcher.find()) { if (matcher.find()) {
return matcher.group(1); return matcher.group(1);
} else { } else {
saveParamToFile(paramValue, context); saveParamToFile(paramValue, context);
return null; return null;
} }
} }
private void saveParamToFile(String paramValue, Context context) { private void saveParamToFile(String paramValue, Context context) {
try { try {
File logFile = new File(context.getFilesDir(), "missing_param_values.txt"); File logFile = new File(context.getFilesDir(), "missing_param_values.txt");
if (!logFile.exists()) { if (!logFile.exists()) {
logFile.createNewFile(); logFile.createNewFile();
} }
FileWriter writer = new FileWriter(logFile, true); FileWriter writer = new FileWriter(logFile, true);
writer.append("Missing serverId in paramValue:").append(paramValue).append("\n"); writer.append("Missing serverId in paramValue:").append(paramValue).append("\n");
writer.close(); writer.close();
} catch (IOException ignored) { } catch (IOException ignored) {
// //XposedBridge.log("Error writing paramValue to file: " + e.getMessage()); // ////XposedBridge.log("Error writing paramValue to file: " + e.getMessage());
} }
} }
private String queryDatabase(SQLiteDatabase db, String query, String... selectionArgs) { private String queryDatabase(SQLiteDatabase db, String query, String... selectionArgs) {
if (db == null) { if (db == null) {
// //XposedBridge.log("Database is not initialized."); // ////XposedBridge.log("Database is not initialized.");
return null; return null;
} }
Cursor cursor = db.rawQuery(query, selectionArgs); Cursor cursor = db.rawQuery(query, selectionArgs);
@ -496,21 +601,24 @@ public class ReadChecker implements IHook {
return result; return result;
} }
private void initializeLimeDatabase(Context context) { private void initializeLimeDatabase(Context context) {
File oldDbFile = new File(context.getFilesDir(), "lime_data.db"); File oldDbFile = new File(context.getFilesDir(), "lime_data.db");
if (oldDbFile.exists()) { if (oldDbFile.exists()) {
boolean deleted = oldDbFile.delete(); boolean deleted = oldDbFile.delete();
if (deleted) { if (deleted) {
//XposedBridge.log("Old database file lime_data.db deleted."); ////XposedBridge.log("Old database file lime_data.db deleted.");
} else { } else {
//XposedBridge.log("Failed to delete old database file lime_data.db."); ////XposedBridge.log("Failed to delete old database file lime_data.db.");
} }
} }
// 新しいデータベースファイルの初期化 // 新しいデータベースファイルの初期化
File dbFile = new File(context.getFilesDir(), "lime_checked_data.db"); File dbFile = new File(context.getFilesDir(), "lime_checked_data.db");
limeDatabase = SQLiteDatabase.openOrCreateDatabase(dbFile, null); limeDatabase = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
String createGroupTable = "CREATE TABLE IF NOT EXISTS group_messages (" + String createGroupTable = "CREATE TABLE IF NOT EXISTS group_messages (" +
"group_id TEXT NOT NULL, " + "group_id TEXT NOT NULL, " +
"server_id TEXT NOT NULL, " + "server_id TEXT NOT NULL, " +
@ -522,12 +630,16 @@ public class ReadChecker implements IHook {
"PRIMARY KEY(group_id, server_id, checked_user)" + "PRIMARY KEY(group_id, server_id, checked_user)" +
");"; ");";
limeDatabase.execSQL(createGroupTable); limeDatabase.execSQL(createGroupTable);
// //XposedBridge.log("Database initialized and group_messages table created."); // ////XposedBridge.log("Database initialized and group_messages table created.");
} }
private void saveData(String groupId, String serverId, String checkedUser, String groupName, String content, String user_name, String createdTime, Context context) {
private void saveData( String SendUser, String groupId, String serverId, String checkedUser, String groupName, String content, String user_name, String createdTime, Context context) {
if (groupName == null) { if (groupName == null) {
Log.w("saveData", "group_name is null. Skipping save operation.");
return; return;
} }
Cursor cursor = null; Cursor cursor = null;
@ -539,22 +651,23 @@ public class ReadChecker implements IHook {
String existingUserName = cursor.getString(1); String existingUserName = cursor.getString(1);
String currentTime = getCurrentTime(); String currentTime = getCurrentTime();
if (count > 0) { if (count > 0) {
if (!existingUserName.contains(user_name)) { if (!existingUserName.contains(user_name)) {
String updatedUserName = existingUserName + (existingUserName.isEmpty() ? "" : "\n") + "-" + user_name + " [" + currentTime + "]"; String updatedUserName = existingUserName + (existingUserName.isEmpty() ? "" : "\n") + "-" + user_name + " [" + currentTime + "]";
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put("user_name", updatedUserName); values.put("user_name", updatedUserName);
limeDatabase.update("group_messages", values, "server_id=? AND checked_user=?", new String[]{serverId, checkedUser}); limeDatabase.update("group_messages", values, "server_id=? AND checked_user=?", new String[]{serverId, checkedUser});
// //XposedBridge.log("User name updated for server_id: " + serverId + ", checked_user: " + checkedUser); // ////XposedBridge.log("User name updated for server_id: " + serverId + ", checked_user: " + checkedUser);
} }
} else { } else {
insertNewRecord(groupId, serverId, checkedUser, groupName, content, "-" + user_name + " [" + currentTime + "]", createdTime); insertNewRecord(SendUser, groupId, serverId, checkedUser, groupName, content, "-" + user_name + " [" + currentTime + "]", createdTime);
} }
updateOtherRecordsUserNames(groupId, user_name, currentTime); updateOtherRecordsUserNames(groupId, user_name, currentTime);
} }
} catch (Exception e) { } catch (Exception e) {
Log.e("saveData", "Error during data existence check or update:", e);
} finally { } finally {
if (cursor != null) { if (cursor != null) {
cursor.close(); cursor.close();
@ -562,23 +675,27 @@ public class ReadChecker implements IHook {
} }
} }
private void updateOtherRecordsUserNames(String groupId, String user_name, String currentTime) { private void updateOtherRecordsUserNames(String groupId, String user_name, String currentTime) {
Cursor cursor = null; Cursor cursor = null;
try { try {
String selectOtherQuery = "SELECT server_id, user_name FROM group_messages WHERE group_id=? AND user_name NOT LIKE ?"; String selectOtherQuery = "SELECT server_id, user_name FROM group_messages WHERE group_id=? AND user_name NOT LIKE ?";
cursor = limeDatabase.rawQuery(selectOtherQuery, new String[]{groupId, "%-" + user_name + "%"}); cursor = limeDatabase.rawQuery(selectOtherQuery, new String[]{groupId, "%-" + user_name + "%"});
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
String serverId = cursor.getString(cursor.getColumnIndexOrThrow("server_id")); String serverId = cursor.getString(cursor.getColumnIndexOrThrow("server_id"));
String existingUserName = cursor.getString(cursor.getColumnIndexOrThrow("user_name")); String existingUserName = cursor.getString(cursor.getColumnIndexOrThrow("user_name"));
if (!existingUserName.contains(user_name)) { if (!existingUserName.contains(user_name)) {
String updatedUserName = existingUserName + (existingUserName.isEmpty() ? "" : "\n") + "-" + user_name + " [" + currentTime + "]"; String updatedUserName = existingUserName + (existingUserName.isEmpty() ? "" : "\n") + "-" + user_name + " [" + currentTime + "]";
ContentValues values = new ContentValues(); ContentValues values = new ContentValues();
values.put("user_name", updatedUserName); values.put("user_name", updatedUserName);
limeDatabase.update("group_messages", values, "group_id=? AND server_id=?", new String[]{groupId, serverId}); limeDatabase.update("group_messages", values, "group_id=? AND server_id=?", new String[]{groupId, serverId});
// //XposedBridge.log("Updated user_name for other records in group_id: " + groupId + ", server_id: " + serverId); // ////XposedBridge.log("Updated user_name for other records in group_id: " + groupId + ", server_id: " + serverId);
} }
} }
} catch (Exception e) { } catch (Exception e) {
@ -590,18 +707,32 @@ public class ReadChecker implements IHook {
} }
} }
private String getCurrentTime() { private String getCurrentTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault()); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault());
return sdf.format(new Date()); return sdf.format(new Date());
} }
private void insertNewRecord(String groupId, String serverId, String checkedUser, String groupName, String content, String user_name, String createdTime) {
try { private void insertNewRecord(String SendUser, String groupId, String serverId, String checkedUser, String groupName, String content, String user_name, String createdTime) {
String insertQuery = "INSERT INTO group_messages(group_id, server_id, checked_user, group_name, content, user_name, created_time)" + String insertQuery = "INSERT INTO group_messages(group_id, server_id, checked_user, group_name, content, user_name, created_time)" +
" VALUES(?, ?, ?, ?, ?, ?, ?);"; " VALUES(?, ?, ?, ?, ?, ?, ?);";
if (!limeOptions.MySendMessage.checked) {
limeDatabase.execSQL(insertQuery, new Object[]{groupId, serverId, checkedUser, groupName, content, user_name, createdTime}); limeDatabase.execSQL(insertQuery, new Object[]{groupId, serverId, checkedUser, groupName, content, user_name, createdTime});
return;
}
if (SendUser == null) {
try {
limeDatabase.beginTransaction();
limeDatabase.execSQL(insertQuery, new Object[]{groupId, serverId, checkedUser, groupName, content, user_name, createdTime});
limeDatabase.setTransactionSuccessful();
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace();
} finally {
limeDatabase.endTransaction();
}
} else {
}
} }
} }
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -131,4 +131,8 @@
<string name="AutomaticBackup">定期的にバックアップ</string> <string name="AutomaticBackup">定期的にバックアップ</string>
<string name="set_id">Set ID</string> <string name="set_id">Set ID</string>
<string name="RemoveVoiceRecord">音声ボタンを無効化</string> <string name="RemoveVoiceRecord">音声ボタンを無効化</string>
<string name="MySendMessage">(既読機能)自分以外のメッセージを保存しない</string>
</resources> </resources>

View File

@ -125,4 +125,6 @@
<string name="AutomaticBackup">將您訂閱的群組的通知靜音</string> <string name="AutomaticBackup">將您訂閱的群組的通知靜音</string>
<string name="RemoveVoiceRecord">禁用音訊按鈕</string> <string name="RemoveVoiceRecord">禁用音訊按鈕</string>
<string name="MySendMessage">(讀取功能)請勿儲存您自己以外的信息</string>
</resources> </resources>

View File

@ -134,4 +134,9 @@
<string name="Talk_Auto_Back_up_Error">Error occurred during automatic backup</string> <string name="Talk_Auto_Back_up_Error">Error occurred during automatic backup</string>
<string name="Talk_Picture_Back_up_Success">The talk picture folder was successfully backed up</string> <string name="Talk_Picture_Back_up_Success">The talk picture folder was successfully backed up</string>
<string name="RemoveVoiceRecord">Enable audio button</string> <string name="RemoveVoiceRecord">Enable audio button</string>
<string name="MySendMessage">(Read function) Do not save messages other than your own</string>
</resources> </resources>