Skip to content

Commit

Permalink
"トーク履歴のバックアップを開始
Browse files Browse the repository at this point in the history
"トークフォルダのバックアップを開始"
"バックアップを開始"
送信時のインテントメッセージによる、バックアップ種類の変更
リダイレクト有効時のLINEの設定のアカウントセンターがWEBアプリで開かれないように
  • Loading branch information
areteruhiro committed Nov 3, 2024
1 parent 464c20b commit 6acbb72
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 39 deletions.
2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ android {
minSdk 28
targetSdk 34
versionCode 15
versionName "1.12.4a"
versionName "1.12.5"
multiDexEnabled false
proguardFiles += 'proguard-rules.pro'
buildConfigField 'String', 'HOOK_TARGET_VERSION', '"141700420"'
Expand Down
143 changes: 109 additions & 34 deletions app/src/main/java/io/github/hiro/lime/hooks/AutomaticBackup.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package io.github.hiro.lime.hooks;

import static android.content.ContentValues.TAG;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Environment;;
import android.util.Log;
import android.widget.Toast;
import java.io.File;
import java.io.FileInputStream;
Expand All @@ -23,50 +26,122 @@
import io.github.hiro.lime.hooks.IHook;

public class AutomaticBackup implements IHook {
@Override
public void hook(LimeOptions limeOptions, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {

XposedHelpers.findAndHookMethod("jp.naver.line.android.activity.schemeservice.LineSchemeServiceActivity",
loadPackageParam.classLoader, "onCreate", Bundle.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Intent intent = ((Activity) param.thisObject).getIntent();
XposedBridge.log("onCreate Intent action: " + intent.getAction());
handleIntent(intent, param.thisObject);
}
});
}
@Override
public void hook(LimeOptions limeOptions, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {

private void handleIntent(Intent intent, Object activity) {
if (intent != null) {
String text = intent.getStringExtra(Intent.EXTRA_TEXT);
if ("バックアップを開始".equals(text)) {
backupChatHistory(((Activity) activity).getApplicationContext());
}
XposedHelpers.findAndHookMethod("jp.naver.line.android.activity.schemeservice.LineSchemeServiceActivity",
loadPackageParam.classLoader, "onCreate", Bundle.class, new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
Intent intent = ((Activity) param.thisObject).getIntent();
XposedBridge.log("onCreate Intent action: " + intent.getAction());
handleIntent(intent, param.thisObject);
}
});
}

private void handleIntent(Intent intent, Object activity) {
if (intent != null) {
String text = intent.getStringExtra(Intent.EXTRA_TEXT);
if ("トーク履歴のバックアップを開始".equals(text)) {
backupChatHistory(((Activity) activity).getApplicationContext());
}
if ("トークフォルダのバックアップを開始".equals(text)) {
backupChatsFolder(((Activity) activity).getApplicationContext());
}
if ("バックアップを開始".equals(text)) {
backupChatHistory(((Activity) activity).getApplicationContext());
backupChatsFolder(((Activity) activity).getApplicationContext());
}
}
}


private void backupChatHistory(Context appContext) {
File originalDbFile = appContext.getDatabasePath("naver_line");
File backupDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "LimeBackup");
if (!backupDir.exists() && !backupDir.mkdirs()) {
return;

private void backupChatHistory(Context appContext) {
File originalDbFile = appContext.getDatabasePath("naver_line");
File backupDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "LimeBackup");
if (!backupDir.exists() && !backupDir.mkdirs()) {
return;
}
File backupFileWithTimestamp = new File(backupDir, "naver_line_backup_" + ".db");

try (FileChannel source = new FileInputStream(originalDbFile).getChannel()) {
try (FileChannel destinationWithTimestamp = new FileOutputStream(backupFileWithTimestamp).getChannel()) {
destinationWithTimestamp.transferFrom(source, 0, source.size());
}
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date());
File backupFileWithTimestamp = new File(backupDir, "naver_line_backup_" + timeStamp + ".db");
showToast(appContext, "自動バックアップが成功しました"); // トーストをUIスレッドで表示

try (FileChannel source = new FileInputStream(originalDbFile).getChannel()) {
try (FileChannel destinationWithTimestamp = new FileOutputStream(backupFileWithTimestamp).getChannel()) {
destinationWithTimestamp.transferFrom(source, 0, source.size());
}
showToast(appContext, "自動バックアップが成功しました"); // トーストをUIスレッドで表示
} catch (IOException e) {
showToast(appContext, "自動バックアップ中にエラーが発生しました: " + e.getMessage());
}
}

private void backupChatsFolder(Context context) {

File originalChatsDir = new File(Environment.getExternalStorageDirectory(), "Android/data/jp.naver.line.android/files/chats");


File backupDir = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "LimeBackup");


if (!backupDir.exists() && !backupDir.mkdirs()) {
Log.e(TAG, "Failed to create backup directory: " + backupDir.getAbsolutePath());
return;
}


File backupChatsDir = new File(backupDir, "chats_backup");
if (!backupChatsDir.exists() && !backupChatsDir.mkdirs()) {
Log.e(TAG, "Failed to create chats backup directory: " + backupChatsDir.getAbsolutePath());
return;
}


try {
copyDirectory(originalChatsDir, backupChatsDir);
Log.i(TAG, "Chats folder successfully backed up to: " + backupChatsDir.getAbsolutePath());
Toast.makeText(context, "チャットフォルダのバックアップが成功しました", Toast.LENGTH_SHORT).show();
} catch (IOException e) {
Log.e(TAG, "Error while backing up chats folder", e);
Toast.makeText(context, "チャットフォルダのバックアップ中にエラーが発生しました", Toast.LENGTH_SHORT).show();
}
}


private void copyDirectory(File sourceDir, File destDir) throws IOException {
if (!sourceDir.exists()) {
throw new IOException("Source directory does not exist: " + sourceDir.getAbsolutePath());
}

} catch (IOException e) {
showToast(appContext, "自動バックアップ中にエラーが発生しました: " + e.getMessage());
if (!destDir.exists()) {
destDir.mkdirs();
}

File[] files = sourceDir.listFiles();
if (files != null) {
for (File file : files) {
File destFile = new File(destDir, file.getName());
if (file.isDirectory()) {

copyDirectory(file, destFile);
} else {

copyFile(file, destFile);
}
}
}
}
private void copyFile(File sourceFile, File destFile) throws IOException {
try (FileChannel sourceChannel = new FileInputStream(sourceFile).getChannel();
FileChannel destChannel = new FileOutputStream(destFile).getChannel()) {
destChannel.transferFrom(sourceChannel, 0, sourceChannel.size());
}
}



private void showToast(final Context context, final String message) {
private void showToast(final Context context, final String message) {
new android.os.Handler(context.getMainLooper()).post(() ->
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
);
Expand Down
16 changes: 14 additions & 2 deletions app/src/main/java/io/github/hiro/lime/hooks/RedirectWebView.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
import io.github.hiro.lime.LimeOptions;

public class RedirectWebView implements IHook {


@Override
public void hook(LimeOptions limeOptions, XC_LoadPackage.LoadPackageParam loadPackageParam) throws Throwable {
if (!limeOptions.redirectWebView.checked) return;
Expand All @@ -31,10 +33,20 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
WebView webView = findWebView(rootView);

if (webView != null) {
String currentUrl = webView.getUrl();

if (currentUrl != null &&
(currentUrl.startsWith("https://account-center.lylink.yahoo.co.jp") ||
currentUrl.startsWith("https://access.line.me") ||
currentUrl.startsWith("https://id.lylink.yahoo.co.jp/federation/ly/normal/callback/first"))) {

return;
}

webView.setVisibility(View.GONE);
webView.stopLoading();

Uri uri = Uri.parse(webView.getUrl());
Uri uri = Uri.parse(currentUrl);

if (limeOptions.openInBrowser.checked) {
Intent intent = new Intent(Intent.ACTION_VIEW);
Expand All @@ -49,13 +61,13 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
}

activity.finish();

}
}
}
);
}


private WebView findWebView(View view) {
if (view instanceof WebView) {
return (WebView) view;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Map;
import de.robv.android.xposed.XC_MethodHook;
import de.robv.android.xposed.XposedBridge;
import de.robv.android.xposed.callbacks.XC_LoadPackage;
Expand All @@ -19,6 +18,7 @@ public void hook(LimeOptions limeOptions, XC_LoadPackage.LoadPackageParam loadPa
new XC_MethodHook() {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
// syncの場合の処理
if (param.args[0].toString().equals("sync")) {
Object wrapper = param.args[1].getClass().getDeclaredField("a").get(param.args[1]);
Field operationResponseField = wrapper.getClass().getSuperclass().getDeclaredField("value_");
Expand All @@ -35,9 +35,54 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
}
}
}

// getChats_resultの場合の処理
if (param.args[0].toString().equals("getChats_result")) {
try {
// wrapperオブジェクトにアクセス
Object wrapper = param.args[1].getClass().getDeclaredField("a").get(param.args[1]);
Field responseField = wrapper.getClass().getSuperclass().getDeclaredField("value_");
responseField.setAccessible(true);
Object getChatsResponse = responseField.get(wrapper);

// GetChatsResponseからチャットのリストを取得
ArrayList<?> chats = (ArrayList<?>) getChatsResponse.getClass().getDeclaredField("chats").get(getChatsResponse);
XposedBridge.log("Number of chats: " + chats.size()); // チャット数をログ出力

// 各チャットをループ処理
for (Object chat : chats) {
try {
// notificationDisabledフィールドを取得
Field notificationDisabledField = chat.getClass().getDeclaredField("notificationDisabled");
notificationDisabledField.setAccessible(true);

// notificationDisabledの値をログ出力
XposedBridge.log("Chat notificationDisabled before: " + notificationDisabledField.get(chat));

// notificationDisabledをtrueに設定
notificationDisabledField.set(chat, true);
XposedBridge.log("Chat notificationDisabled after: " + notificationDisabledField.get(chat));

// 他のフィールドの値をログ出力する例
Field chatMidField = chat.getClass().getDeclaredField("chatMid"); // 例としてchatMidフィールドを取得
chatMidField.setAccessible(true);
XposedBridge.log("Chat MID: " + chatMidField.get(chat));

} catch (NoSuchFieldException e) {
XposedBridge.log("Field 'notificationDisabled' not found in chat object: " + e.getMessage());
} catch (IllegalAccessException e) {
XposedBridge.log("Cannot access 'notificationDisabled' field: " + e.getMessage());
}
}

// すべてのnotificationDisabledフィールドがtrueに設定されたことをログに出力
XposedBridge.log("All notificationDisabled fields have been set to true.");
} catch (Exception e) {
XposedBridge.log("Error while processing getChats_result: " + e.getMessage());
}
}
}
}
);

}
}

0 comments on commit 6acbb72

Please sign in to comment.