diff --git a/app/src/main/java/moe/ono/creator/GetChannelArkDialog.java b/app/src/main/java/moe/ono/creator/GetChannelArkDialog.java new file mode 100644 index 00000000..14188830 --- /dev/null +++ b/app/src/main/java/moe/ono/creator/GetChannelArkDialog.java @@ -0,0 +1,206 @@ +/** + * License + * 本文件及代码仅供 cwuom/ono 使用 + * 基于 cwuom/ono 开发的开源项目需保证文件中声明本信息 + * 禁止 私有项目、闭源项目和以收费形式二次分发的项目 使用 + */ + +package moe.ono.creator; + +import static moe.ono.util.Session.getCurrentChatType; +import static moe.ono.util.Session.getCurrentPeerID; +import static moe.ono.util.analytics.ActionReporter.reportVisitor; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.os.Handler; +import android.os.Looper; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import androidx.annotation.NonNull; + +import com.lxj.xpopup.XPopup; +import com.lxj.xpopup.core.BasePopupView; +import com.lxj.xpopup.core.BottomPopupView; +import com.lxj.xpopup.util.XPopupUtils; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.util.Base64; +import java.util.Objects; + +import moe.ono.R; +import moe.ono.hooks.base.util.Toasts; +import moe.ono.hooks.item.developer.GetCookie; +import moe.ono.ui.CommonContextWrapper; +import moe.ono.util.AppRuntimeHelper; +import moe.ono.util.Logger; +import moe.ono.util.Session; +import moe.ono.util.SyncUtils; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +@SuppressLint("ResourceType") +public class GetChannelArkDialog extends BottomPopupView { + private static BasePopupView popupView; + + public GetChannelArkDialog(@NonNull Context context) { + super(context); + } + + public static void createView(Context context) { + Context fixContext = CommonContextWrapper.createAppCompatContext(context); + XPopup.Builder NewPop = new XPopup.Builder(fixContext).moveUpToKeyboard(true).isDestroyOnDismiss(true); + NewPop.maxHeight((int) (XPopupUtils.getScreenHeight(context) * .7f)); + NewPop.popupHeight((int) (XPopupUtils.getScreenHeight(context) * .63f)); + + + reportVisitor(AppRuntimeHelper.getAccount(), "CreateView-GetChannelArkDialog"); + + popupView = NewPop.asCustom(new GetChannelArkDialog(fixContext)); + popupView.show(); + } + + @SuppressLint("SetTextI18n") + @Override + protected void onCreate() { + super.onCreate(); + new Handler(Looper.getMainLooper()).postDelayed(() -> { + Button btnSend = findViewById(R.id.btn_send); + TextView tvTarget = findViewById(R.id.tv_target); + + EditText title = findViewById(R.id.ark_title); + EditText desc = findViewById(R.id.ark_desc); + EditText jump_url = findViewById(R.id.jump_url); + EditText ark_preview_url = findViewById(R.id.ark_preview_url); + EditText ark_tag_name = findViewById(R.id.ark_tag_name); + EditText ark_tag_icon = findViewById(R.id.ark_tag_icon); + EditText ark_prompt = findViewById(R.id.ark_prompt); + + title.setVisibility(VISIBLE); + desc.setVisibility(VISIBLE); + jump_url.setVisibility(VISIBLE); + ark_preview_url.setVisibility(VISIBLE); + ark_tag_name.setVisibility(VISIBLE); + ark_tag_icon.setVisibility(VISIBLE); + ark_prompt.setVisibility(VISIBLE); + + title.clearFocus(); + desc.clearFocus(); + jump_url.clearFocus(); + ark_preview_url.clearFocus(); + ark_tag_name.clearFocus(); + ark_tag_icon.clearFocus(); + ark_prompt.clearFocus(); + + int chat_type = getCurrentChatType(); + if (chat_type == 1) { + tvTarget.setText("当前会话: " + getCurrentPeerID() + " | " + "好友"); + } else if (chat_type == 2) { + tvTarget.setText("当前会话: " + getCurrentPeerID() + " | " + "群聊"); + } else { + tvTarget.setText("当前会话: " + getCurrentPeerID() + " | " + "未知"); + } + + btnSend.setOnClickListener(v -> { + try { + String cookie = GetCookie.Companion.getCookie("qun.qq.com"); + assert cookie != null; + String gtk = GetCookie.Companion.getBknByCookie(cookie); + + OkHttpClient client = new OkHttpClient(); + String json = "{\n" + + " \"appId\": \"com.tencent.tuwen.lua\",\n" + + " \"bizSrc\": \"guild.share\",\n" + + " \"prompt\": \""+((ark_prompt.getText().toString().isEmpty()) ? title.getText().toString() : ark_prompt.getText().toString())+"\",\n" + + " \"meta\": \"{\\\"news\\\":{\\\"title\\\":\\\""+title.getText().toString()+"\\\",\\\"desc\\\":\\\""+desc.getText().toString()+"\\\",\\\"tag\\\":\\\""+((ark_tag_name.getText().toString().isEmpty()) ? "QQ频道" : ark_tag_name.getText().toString())+"\\\",\\\"tagIcon\\\":\\\""+((ark_tag_icon.getText().toString().isEmpty()) ? "https://tianxuan.gtimg.cn/47329_bd95e16e/assets/guild-icon.png" : ark_tag_icon.getText().toString())+"\\\",\\\"preview\\\":\\\""+ark_preview_url.getText().toString()+"\\\",\\\"jumpUrl\\\":\\\""+jump_url.getText().toString()+"\\\"}}\"\n" + + "}"; + MediaType mediaType = MediaType.parse("application/json; charset=utf-8"); + RequestBody body = RequestBody.create(json, mediaType); + Request request = new Request.Builder().url("https://qun.qq.com/qunng/http2rpc/gotrpc/auth/trpc.group_pro.guild_activity.Components/GetArkMsgWithSign?bkn=" + gtk) + .header("Host", "qun.qq.com") + .header("User-Agent", "24117RK2CC Build/AQ3A.240829.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/139.0.7258.143 Mobile Safari/537.36 V1_AND_SQ_9.1.35_8708_YYB_D QQ/9.1.35.22670 NetType/WIFI WebP/0.4.1 AppId/537265587 Pixel/1440 StatusBarHeight/138 SimpleUISwitch/0 QQTheme/1103 StudyMode/0 CurrentMode/0 CurrentFontScale/1.0 GlobalDensityScale/0.96 AllowLandscape/false InMagicWin/0") + .header("X-Request-With", "com.tencent.mobileqq") + .header("Cookie", cookie) + .header("x-oidb", "{\"uint32_service_type\":2,\"uint32_command\":\"0x9064\"}") + .header("Referer", "https://qun.qq.com/qunng/guild/tianxuan/p/53049_a53f0edz?traceTint=tianxuan_copy") + .post(body) + .build(); + + client.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(@NonNull Call call, @NonNull IOException e) { + SyncUtils.runOnUiThread(() -> Toasts.error(v.getContext(), "网络错误")); + } + + @Override + public void onResponse(@NonNull Call call, @NonNull Response response) { + try { + if (!response.isSuccessful()) { + SyncUtils.runOnUiThread(() -> Toasts.error(v.getContext(), "请求失败: " + response.code())); + return; + } + + assert response.body() != null; + String result = response.body().string(); + + SyncUtils.runOnUiThread(() -> { + JSONObject json = null; + try { + json = new JSONObject(result); + } catch (JSONException e) { + Toasts.error(v.getContext(), "JSON 解析失败"); + } + assert json != null; + String base64 = Objects.requireNonNull(json.optJSONObject("data")).optString("signed_ark"); + byte[] decodedBytes = Base64.getDecoder().decode(base64); + String ark = new String(decodedBytes); + try { + PacketHelperDialog.send_ark_msg(ark, Session.getContact()); + } catch (JSONException e) { + Toasts.error(v.getContext(), "发送失败"); + } + }); + } catch (Exception e) { + Logger.e(e); + } + } + }); + } catch (Exception e) { + Logger.e(e); + } + + popupView.dismiss(); + }); + }, 100); + + + } + + + + + @Override + protected void beforeDismiss() { + super.beforeDismiss(); + } + + @Override + protected void onDismiss() { + super.onDismiss(); + } + + @Override + protected int getImplLayoutId() { + return R.layout.get_channel_ark; + } +} diff --git a/app/src/main/java/moe/ono/hooks/item/chat/BottomShortcutMenu.kt b/app/src/main/java/moe/ono/hooks/item/chat/BottomShortcutMenu.kt index c4cddbe5..843157cc 100644 --- a/app/src/main/java/moe/ono/hooks/item/chat/BottomShortcutMenu.kt +++ b/app/src/main/java/moe/ono/hooks/item/chat/BottomShortcutMenu.kt @@ -5,17 +5,21 @@ import android.content.Context import android.os.Bundle import android.view.View import android.view.ViewGroup +import android.widget.EditText import android.widget.ImageView import android.widget.LinearLayout +import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.lxj.xpopup.XPopup import de.robv.android.xposed.XC_MethodHook import de.robv.android.xposed.XC_MethodHook.MethodHookParam import de.robv.android.xposed.XposedHelpers import moe.ono.R +import moe.ono.bridge.ntapi.ChatTypeConstants import moe.ono.config.CacheConfig import moe.ono.config.ConfigManager import moe.ono.constants.Constants import moe.ono.creator.FakeFileSender +import moe.ono.creator.GetChannelArkDialog import moe.ono.creator.PacketHelperDialog import moe.ono.creator.QQMessageTrackerDialog import moe.ono.hooks.XHook @@ -23,6 +27,8 @@ import moe.ono.hooks._base.BaseSwitchFunctionHookItem import moe.ono.hooks._core.annotation.HookItem import moe.ono.hooks._core.factory.HookItemFactory.getItem import moe.ono.hooks.base.util.Toasts +import moe.ono.hooks.item.developer.GetBknByCookie +import moe.ono.hooks.item.developer.GetCookie import moe.ono.hooks.item.developer.QQHookCodec import moe.ono.hooks.item.developer.QQPacketHelperEntry import moe.ono.hooks.item.sigma.QQMessageTracker @@ -30,6 +36,7 @@ import moe.ono.reflex.XMethod import moe.ono.ui.CommonContextWrapper import moe.ono.util.Initiator import moe.ono.util.Logger +import moe.ono.util.Session import moe.ono.util.SyncUtils @SuppressLint("DiscouragedApi") @@ -50,6 +57,10 @@ class BottomShortcutMenu : BaseSwitchFunctionHookItem() { ).ignoreParam().get() hookAfter(method) { param: MethodHookParam -> + if (Session.getContact().chatType != ChatTypeConstants.C2C && Session.getContact().chatType != ChatTypeConstants.GROUP) { + return@hookAfter + } + val imageView = param.result as ImageView if ("拍照".contentEquals(imageView.contentDescription)) { imageView.addOnAttachStateChangeListener(object : View.OnAttachStateChangeListener { @@ -123,6 +134,15 @@ class BottomShortcutMenu : BaseSwitchFunctionHookItem() { ).path ) + val getCookie = ConfigManager.getDefaultConfig().getBooleanOrFalse(Constants.PrekXXX + getItem( + GetCookie::class.java).path) + + val getBknByCookie = ConfigManager.getDefaultConfig().getBooleanOrFalse(Constants.PrekXXX + getItem( + GetBknByCookie::class.java).path) + + val getChannelArk = ConfigManager.getDefaultConfig().getBooleanOrFalse(Constants.PrekXXX + getItem( + GetChannelArk::class.java).path) + val items = ArrayList() if (qqPacketHelper) { items.add("QQPacketHelper") @@ -133,6 +153,15 @@ class BottomShortcutMenu : BaseSwitchFunctionHookItem() { if (qqMessageTracker) { items.add("已读追踪") } + if (getCookie) { + items.add("GetCookie") + } + if (getBknByCookie) { + items.add("GetBknByCookie") + } + if (getChannelArk) { + items.add("GetChannelArk") + } if (getItem(QQHookCodec::class.java).isEnabled) { if (!messageEncryptor) { @@ -186,7 +215,55 @@ class BottomShortcutMenu : BaseSwitchFunctionHookItem() { ).path, false ) } + "GetCookie" -> { + SyncUtils.runOnUiThread { + val builder = MaterialAlertDialogBuilder( + CommonContextWrapper.createAppCompatContext(view.context) + ) + + builder.setTitle("请输入域名") + + val domain = + EditText(CommonContextWrapper.createAppCompatContext(view.context)) + domain.setHint("请输入域名") + domain.setText("qzone.qq.com") + + builder.setView(domain) + + builder.setNegativeButton("取消") { dialog, i -> + dialog.dismiss() + } + builder.setPositiveButton("确定") { dialog, i -> + GetCookie.getCookie(view.context, domain.text.toString()) + } + + builder.show() + } + } + "GetBknByCookie" -> { + SyncUtils.runOnUiThread { + val builder = MaterialAlertDialogBuilder(CommonContextWrapper.createAppCompatContext(view.context)) + + builder.setTitle("请输入 Cookie") + + val cookie = EditText(CommonContextWrapper.createAppCompatContext(view.context)) + cookie.setHint("请输入 Cookie") + + builder.setView(cookie) + builder.setNegativeButton("取消") { dialog, i -> + dialog.dismiss() + } + builder.setPositiveButton("确定") { dialog, i -> + GetBknByCookie.getBkn(CommonContextWrapper.createAppCompatContext(view.context), cookie.text.toString()) + } + + builder.show() + } + } + "GetChannelArk" -> { + SyncUtils.runOnUiThread { GetChannelArkDialog.createView(view.context) } + } } } .show() diff --git a/app/src/main/java/moe/ono/hooks/item/chat/GetChannelArk.java b/app/src/main/java/moe/ono/hooks/item/chat/GetChannelArk.java new file mode 100644 index 00000000..c0c66465 --- /dev/null +++ b/app/src/main/java/moe/ono/hooks/item/chat/GetChannelArk.java @@ -0,0 +1,18 @@ +package moe.ono.hooks.item.chat; + +import android.annotation.SuppressLint; + +import androidx.annotation.NonNull; + +import moe.ono.hooks._base.BaseSwitchFunctionHookItem; +import moe.ono.hooks._core.annotation.HookItem; + +@SuppressLint("DiscouragedApi") +@HookItem(path = "聊天与消息/GetChannelArk", description = "获取频道卡片\n* 需在 快捷菜单 中使用") +public class GetChannelArk extends BaseSwitchFunctionHookItem { + + @Override + public void entry(@NonNull ClassLoader classLoader) throws Throwable { + + } +} \ No newline at end of file diff --git a/app/src/main/java/moe/ono/hooks/item/developer/GetBknByCookie.kt b/app/src/main/java/moe/ono/hooks/item/developer/GetBknByCookie.kt new file mode 100644 index 00000000..b459ca1b --- /dev/null +++ b/app/src/main/java/moe/ono/hooks/item/developer/GetBknByCookie.kt @@ -0,0 +1,53 @@ +/** + * License + * 本文件及代码仅供 cwuom/ono 使用 + * 基于 cwuom/ono 开发的开源项目需保证文件中声明本信息 + * 禁止 私有项目、闭源项目和以收费形式二次分发的项目 使用 + */ + +package moe.ono.hooks.item.developer + +import android.annotation.SuppressLint +import android.content.Context +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import moe.ono.hooks._base.BaseSwitchFunctionHookItem +import moe.ono.hooks._core.annotation.HookItem +import moe.ono.hooks.base.util.Toasts +import moe.ono.util.Logger +import moe.ono.util.SystemServiceUtils + +@SuppressLint("DiscouragedApi") +@HookItem( + path = "开发者选项/GetBknByCookie", + description = "依赖快捷菜单, 打开后在快捷菜单使用" +) +class GetBknByCookie : BaseSwitchFunctionHookItem() { + override fun entry(classLoader: ClassLoader) {} + + companion object { + fun getBkn(context: Context, cookie: String) { + try { + val mContext = context + val builder = MaterialAlertDialogBuilder(mContext) + + val bkn = GetCookie.getBknByCookie(cookie) + + builder.setTitle("Bkn") + builder.setMessage("注意, 千万不要泄露你的 Bkn 给任何人\n\n$bkn") + + builder.setNegativeButton("关闭") { dialog, count -> + dialog.dismiss() + } + builder.setPositiveButton("复制") { dialog, i -> + SystemServiceUtils.copyToClipboard(builder.context, bkn) + Toasts.success(builder.context, "复制成功") + dialog.dismiss() + } + + builder.show() + } catch (e: Exception) { + Logger.e(e) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/moe/ono/hooks/item/developer/GetCookie.kt b/app/src/main/java/moe/ono/hooks/item/developer/GetCookie.kt new file mode 100644 index 00000000..5343f430 --- /dev/null +++ b/app/src/main/java/moe/ono/hooks/item/developer/GetCookie.kt @@ -0,0 +1,148 @@ +/** + * License + * 本文件及代码仅供 cwuom/ono 使用 + * 基于 cwuom/ono 开发的开源项目需保证文件中声明本信息 + * 禁止 私有项目、闭源项目和以收费形式二次分发的项目 使用 + */ + +package moe.ono.hooks.item.developer + +import android.annotation.SuppressLint +import android.content.Context +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import moe.ono.bridge.ManagerHelper +import moe.ono.hooks._base.BaseSwitchFunctionHookItem +import moe.ono.hooks._core.annotation.HookItem +import moe.ono.hooks.base.util.Toasts +import moe.ono.ui.CommonContextWrapper +import moe.ono.util.Logger +import moe.ono.util.QAppUtils.getCurrentUin +import moe.ono.util.SystemServiceUtils + +@SuppressLint("DiscouragedApi") +@HookItem( + path = "开发者选项/GetCookie", + description = "依赖快捷菜单, 打开后在快捷菜单使用" +) +class GetCookie : BaseSwitchFunctionHookItem() { + override fun entry(classLoader: ClassLoader) {} + + companion object { + fun getCookie(context: Context, domain: String) { + try { + val ticketManager = ManagerHelper.getManager(2) + val uin = getCurrentUin().toString() + + val getPSkeyMethod = ticketManager.javaClass.getDeclaredMethod("getPskey", String::class.java, String::class.java) + val getSkeyMethod = ticketManager.javaClass.getDeclaredMethod("getSkey", String::class.java) +// val getSuperkeyMethod = ticketManager.javaClass.getDeclaredMethod("getSuperkey", String::class.java) + val getPt4TokenMethod = ticketManager.javaClass.getDeclaredMethod("getPt4Token", String::class.java, String::class.java) + + val pskey = getPSkeyMethod.invoke(ticketManager, uin, domain) as String + val skey = getSkeyMethod.invoke(ticketManager, uin) as String +// val superkey = getSuperkeyMethod.invoke(ticketManager, uin) as String? + val pt4Token = getPt4TokenMethod.invoke(ticketManager, uin, domain) as String? + val p_uin = "o$uin" + + val cookieMap: MutableMap = HashMap() + + Logger.d("PSkey: $pskey skey: $skey pt4Token: $pt4Token") + + cookieMap["uin"] = p_uin + cookieMap["p_uin"] = p_uin + cookieMap["skey"] = skey + cookieMap["p_skey"] = pskey +// if (superkey != null) { +// cookieMap["superkey"] = superkey +// } + if (pt4Token != null) { + cookieMap["pt4Token"] = pt4Token + } + + val cookie = buildString { + cookieMap.forEach { (key, value) -> + append("$key=$value; ") + } + }.removeSuffix("; ") + + val mContext = CommonContextWrapper.createAppCompatContext(context) + val builder = MaterialAlertDialogBuilder(mContext) + + builder.setTitle("Cookie") + builder.setMessage("注意, 千万不要泄露你的 Cookie 给任何人\n\n$cookie") + + builder.setNegativeButton("关闭") { dialog, count -> + dialog.dismiss() + } + builder.setPositiveButton("复制") { dialog, i -> + SystemServiceUtils.copyToClipboard(builder.context, cookie) + Toasts.success(builder.context, "复制成功") + dialog.dismiss() + } + + builder.show() + } catch (e: Exception) { + Logger.e(e) + } + } + + fun getCookie(domain: String): String? { + try { + val ticketManager = ManagerHelper.getManager(2) + val uin = getCurrentUin().toString() + + val getPSkeyMethod = ticketManager.javaClass.getDeclaredMethod("getPskey", String::class.java, String::class.java) + val getSkeyMethod = ticketManager.javaClass.getDeclaredMethod("getSkey", String::class.java) + // val getSuperkeyMethod = ticketManager.javaClass.getDeclaredMethod("getSuperkey", String::class.java) + val getPt4TokenMethod = ticketManager.javaClass.getDeclaredMethod("getPt4Token", String::class.java, String::class.java) + + val pskey = getPSkeyMethod.invoke(ticketManager, uin, domain) as String + val skey = getSkeyMethod.invoke(ticketManager, uin) as String + // val superkey = getSuperkeyMethod.invoke(ticketManager, uin) as String? + val pt4Token = getPt4TokenMethod.invoke(ticketManager, uin, domain) as String? + val p_uin = "o$uin" + + val cookieMap: MutableMap = HashMap() + + Logger.d("PSkey: $pskey skey: $skey pt4Token: $pt4Token") + + cookieMap["uin"] = p_uin + cookieMap["p_uin"] = p_uin + cookieMap["skey"] = skey + cookieMap["p_skey"] = pskey +// if (superkey != null) { +// cookieMap["superkey"] = superkey +// } + if (pt4Token != null) { + cookieMap["pt4Token "] = pt4Token + } + + val cookie = buildString { + cookieMap.forEach { (key, value) -> + append("$key=$value; ") + } + }.removeSuffix("; ") + + return cookie + } catch (e: Exception) { + Logger.e(e) + } + return null + } + + fun getBknByPSkey(PSkey: String): Int { + var hash = 5381 + for (c in PSkey) { + hash += (hash shl 5) + c.code + } + return hash and 0x7FFFFFFF + } + + fun getBknByCookie(cookie: String): String { + val pskey = cookie.split("; ")[0].replace("p_skey=", "") + + val bkn = getBknByPSkey(pskey).toString() + return bkn + } + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/get_channel_ark.xml b/app/src/main/res/layout/get_channel_ark.xml new file mode 100644 index 00000000..f16a5319 --- /dev/null +++ b/app/src/main/res/layout/get_channel_ark.xml @@ -0,0 +1,369 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b4c1bee6..cac1121f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -12,4 +12,5 @@ cwuom/ono OUOSettingActivityStep2 Message Tracker + Get Channel Ark \ No newline at end of file