找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 13|回复: 0

消息通知监听/推送栏采集消息实时监听采集入库

[复制链接]
发表于 半小时前 | 显示全部楼层 |阅读模式
消息通知监听/推送栏采集消息实时监听采集入库

QQ截图20261027081708.png

QQ截图20261027081700.png

QQ截图20261027081646.png

  1. /**
  2. * 案例21:消息通知监听/推送栏采集消息实时监听采集入库
  3. *
  4. * 经典实用案例"消息通知监听/推送采集",转换成 AIWROK 实现:
  5. *   event.onNotificationEvent 注册通知监听 → 每条通知取 包名/标题/内容
  6. *   → 完整打印 → SQLite 入库(notify_records 表) → 关键词命中打 ⚠
  7. *
  8. * 与前面案例的本质区别(全新数据源 + 全新工作模式):
  9. *   15-18 轮询拉API,19 读设备状态,20 轮询截屏看像素,21 直接**躺在系统通知栏等消息上门**——
  10. *   这是全系列第一个"事件驱动"案例:前面全是"脚本主动去问",本案例是"消息自己来找脚本"。
  11. *   通知是 APP 推送给系统的,不用打开APP、不用截屏、息屏也能采,
  12. *   短信验证码/到账提醒/订单状态这类"实时推送数据"的唯一正解(轮询又慢又费电还漏消息)。
  13. *
  14. * 新知识点:
  15. * 1. event.onNotificationEvent(回调) 事件驱动:脚本主循环只负责待机+发演示通知,
  16. *    采集逻辑全在回调里,消息随时到随时采,和主循环互不阻塞(对比案例20的"每10秒问一次");
  17. * 2. **通知监听权限**:系统设置→通知使用权(Notification Access)必须授权给 AIWROK,
  18. *    不授权一条都收不到(本案例最大踩坑点,启动日志有自检提示);
  19. * 3. 脚本自己发通知闭环演示:app.getContext() 拿 Context → Java NotificationManager.notify,
  20. *    和案例20 Intent 启动页面同一招——脚本不仅能"读"系统,还能"驱动"系统,
  21. *    发出去的通知立刻被自己的监听器收到,全自动演示不用手动操作;
  22. *    **自造通知必须 setContentIntent**(AIWROK 监听回调内部会碰它,不设直接 NPE,详见 发送演示通知 注释);
  23. * 4. 通知去重:进度条类通知会反复 post 同内容,重复的只打提示不重复入库;
  24. * 5. 按包名实时统计:每轮打印"哪个APP推了几条",消息监控大屏的雏形。
  25. *
  26. * 教学演示方法(全自动,无需手动操作):
  27. *   跑起来后每 8 秒自动发一条演示通知(银行到账/支付宝支付/验证码/快递取件/系统清理 循环),
  28. *   监听器立刻捕获→打印→入库→命中"支付/转账/验证码"打 ⚠;
  29. *   期间你从电脑 adb 或任何 APP 推真实通知,同样会被采到(数据源是系统级,不分来源)。
  30. *   可选真实通知演示(电脑执行):adb shell cmd notification post -S bigtext -t '测试标题' 'tag1' '测试内容'
  31. */

  32. // ========== 配置区(改这里) ==========
  33. var 监控关键词列表 = ["支付", "转账", "验证码", "订单", "退款"];   // 通知内容命中任一词打 ⚠(盯敏感消息的雏形)
  34. var 演示通知间隔秒 = 8;            // 每 8 秒自动发一条演示通知(给监听器喂数据,演示闭环节奏)
  35. var SQLite路径 = "/sdcard/通知消息.db";
  36. var 表名 = "notify_records";
  37. var 通知渠道ID = "aiwork_demo";    // Android 8+ 发通知必须先建渠道(系统硬规定,不建 notify 静默失败)

  38. // 演示通知剧本(循环发,模拟真实APP推送流)
  39. var 演示通知脚本 = [
  40.     { 标题: "【短信】银行客服", 内容: "您尾号8888的储蓄卡账户收入转账汇款 200,000,000.00 元(贰亿元整),余额 200,013,140.00 元" },
  41.     { 标题: "【支付宝】到账通知", 内容: "你有一笔订单已支付成功,买家已确认收货" },
  42.     { 标题: "【验证码】某某科技", 内容: "验证码:863214,5分钟内有效,请勿泄露给他人" },
  43.     { 标题: "【菜鸟】取件提醒", 内容: "您的包裹已到站,取件码 3-2-1145,凭码取件" },
  44.     { 标题: "【系统】存储清理", 内容: "检测到 1.2GB 可清理垃圾,点击释放空间" }
  45. ];

  46. // ========== 全局变量顶部声明 ==========
  47. var gStop = false;
  48. var 数据库连接 = null;
  49. var 数据库可用 = false;
  50. var 累计捕获条数 = 0;
  51. var 累计入库条数 = 0;
  52. var 累计命中条数 = 0;
  53. var 已见过 = {};                   // 通知去重表: key=包名|标题|内容
  54. var 包名统计 = {};                 // 按来源包名计数(监控大屏雏形)
  55. var 通知ID = 1;                    // notify 的 id 不能重复(同 id 会覆盖旧通知)
  56. var 通知渠道已建 = false;

  57. // ========== importClass 只能放最外层 ==========
  58. importClass(android.database.sqlite.SQLiteDatabase);
  59. importClass(java.io.File);

  60. // ========== 工具函数(先定义后调用) ==========

  61. // 连接 SQLite(零配置,同案例20)
  62. function dbConnect() {
  63.     try {
  64.         数据库连接 = SQLiteDatabase.openOrCreateDatabase(new File(SQLite路径), null);
  65.         数据库可用 = true;
  66.         printl("[DB] ✅ SQLite 已打开: " + SQLite路径);
  67.     } catch (e) {
  68.         数据库可用 = false;
  69.         printl("[DB] ⚠ 打开失败(只打印不入库): " + e);
  70.     }
  71. }

  72. // 建表(存在则跳过):package_name/title/content/matched/collected_at
  73. function dbEnsureTable() {
  74.     if (!数据库可用) return;
  75.     try {
  76.         var sql = "CREATE TABLE IF NOT EXISTS " + 表名 + " (" +
  77.             "id INTEGER PRIMARY KEY AUTOINCREMENT," +
  78.             "package_name TEXT, title TEXT, content TEXT, matched INTEGER, collected_at INTEGER)";
  79.         数据库连接.execSQL(sql);
  80.         printl("[DB] ✅ 表已就绪: " + 表名);
  81.     } catch (e) {
  82.         printl("[DB] ⚠ 建表失败: " + e);
  83.         数据库可用 = false;
  84.     }
  85. }

  86. // 通知入库(matched=1 表示命中监控词)
  87. function dbInsertNotify(包名, 标题, 内容, matched) {
  88.     if (!数据库可用) return false;
  89.     try {
  90.         var stmt = 数据库连接.compileStatement("INSERT INTO " + 表名 +
  91.             " (package_name, title, content, matched, collected_at) VALUES (?,?,?,?,?)");
  92.         stmt.bindString(1, "" + 包名);
  93.         stmt.bindString(2, "" + 标题);
  94.         stmt.bindString(3, "" + 内容);
  95.         stmt.bindLong(4, matched);
  96.         stmt.bindLong(5, time.nowStamp());   // nowStamp=毫秒数字;time.now()是格式化字符串不能bindLong
  97.         stmt.executeInsert();
  98.         stmt.close();
  99.         return true;
  100.     } catch (e) {
  101.         printl("[DB] ❌ 入库失败: " + e);
  102.         return false;
  103.     }
  104. }

  105. function dbClose() {
  106.     if (数据库可用 && 数据库连接 != null) {
  107.         try { 数据库连接.close(); } catch (e) {}
  108.         数据库连接 = null;
  109.     }
  110. }

  111. // 关键词检查:命中返回关键词,否则 null
  112. function 查找命中关键词(文本) {
  113.     for (var k = 0; k < 监控关键词列表.length; k++) {
  114.         if (文本.indexOf(监控关键词列表[k]) >= 0) {
  115.             return 监控关键词列表[k];
  116.         }
  117.     }
  118.     return null;
  119. }

  120. // 打印按包名统计(监控大屏雏形)
  121. function 打印统计() {
  122.     var 各包 = [];
  123.     for (var p in 包名统计) {
  124.         各包.push(p + " × " + 包名统计[p]);
  125.     }
  126.     printl("📊 统计:捕获 " + 累计捕获条数 + " 条 | 入库 " + 累计入库条数 +
  127.         " 条 | 命中 " + 累计命中条数 + " 条 | 来源[" + (各包.length > 0 ? 各包.join(", ") : "暂无") + "]");
  128. }

  129. // ========== 通知监听回调(事件驱动核心,消息来了系统自己调这里) ==========
  130. // 踩坑实录:回调里抛异常会打断监听,必须全程 try/catch 兜住;
  131. // getTitle/getText 可能返回 null,拼接前先判空,否则日志里出现 "null"
  132. function 通知到达(通知) {
  133.     try {
  134.         累计捕获条数++;   // 先计数证明回调触发过(后面取数据若抛异常,自检不会误报成"没给通知使用权")
  135.         var 包名 = "" + 通知.getPackage();
  136.         var 标题 = 通知.getTitle();
  137.         标题 = (标题 == null) ? "" : ("" + 标题);
  138.         var 内容 = 通知.getText();
  139.         内容 = (内容 == null) ? "" : ("" + 内容);

  140.         包名统计[包名] = (包名统计[包名] || 0) + 1;

  141.         printl("\n🔔 通知到达 [" + time.StampToTime(time.nowStamp()) + "] 来自: " + 包名);
  142.         printl("   📌 标题: " + 标题);
  143.         printl("   📄 内容: " + 内容);

  144.         // 去重:进度条类通知会反复 post 同内容,重复只打提示不入库
  145.         var 去重键 = 包名 + "|" + 标题 + "|" + 内容;
  146.         if (已见过[去重键]) {
  147.             printl("   ℹ 与已采集通知完全相同(重复推送),本轮不入库");
  148.             return;
  149.         }

  150.         var 命中词 = 查找命中关键词(标题 + " " + 内容);
  151.         var 命中标记 = 命中词 != null ? 1 : 0;
  152.         if (命中词 != null) {
  153.             累计命中条数++;
  154.             printl("   ⚠ 报警:通知命中关键词「" + 命中词 + "」!");
  155.         }

  156.         printl("   💾 写入数据库(表: " + 表名 + ")...");
  157.         if (dbInsertNotify(包名, 标题, 内容, 命中标记)) {
  158.             累计入库条数++;
  159.             已见过[去重键] = true;
  160.             printl("   ✓ 已入库 [" + 累计入库条数 + "]" + (命中词 != null ? " ⚠命中" : ""));
  161.         } else {
  162.             printl("   ✗ 入库失败");
  163.         }
  164.     } catch (e) {
  165.         printl("⚠ 通知回调异常(已兜住,监听继续): " + e);
  166.     }
  167. }

  168. // ========== 脚本自己发通知(闭环演示,和案例20 Intent 同一招) ==========
  169. // 踩坑实录:①Android 8+ 不先 createNotificationChannel 就 notify,通知会**静默不显示**
  170. // (不抛异常!系统日志里才有提示),必须首次发送前建渠道;②通知 id 重复会覆盖旧通知,用自增;
  171. // ③**必须 setContentIntent**——AIWROK 的 notification 包装对象在监听回调里取标题/内容时
  172. // 内部会碰 contentIntent(PendingIntent.getIntentSender()),自造通知不设它,
  173. // 回调直接抛 NPE(实测锤死:日志"收到通知消息"有,但回调里 NullPointerException);
  174. // ④Android 12+ PendingIntent 必须显式加 FLAG_IMMUTABLE(0x04000000),否则同样抛异常
  175. function 发送演示通知(标题, 内容) {
  176.     try {
  177.         var ctx = app.getContext();
  178.         var nm = ctx.getSystemService("notification");   // Context.NOTIFICATION_SERVICE 的值就是 "notification"
  179.         if (android.os.Build.VERSION.SDK_INT >= 26 && !通知渠道已建) {
  180.             var channel = new android.app.NotificationChannel(通知渠道ID, "AIWROK演示频道", 4); // 4=IMPORTANCE_HIGH
  181.             nm.createNotificationChannel(channel);
  182.             通知渠道已建 = true;
  183.             printl("📮 已创建通知渠道: " + 通知渠道ID + "(Android 8+ 发通知的硬性前提)");
  184.         }
  185.         var builder;
  186.         if (android.os.Build.VERSION.SDK_INT >= 26) {
  187.             builder = new android.app.Notification.Builder(ctx, 通知渠道ID);
  188.         } else {
  189.             builder = new android.app.Notification.Builder(ctx);
  190.         }
  191.         // 点通知跳回本APP首页(真实APP的通知都带这个,不带回调里会 NPE,见上方踩坑③)
  192.         var 启动意图 = ctx.getPackageManager().getLaunchIntentForPackage(ctx.getPackageName());
  193.         var 待处理意图 = android.app.PendingIntent.getActivity(ctx, 通知ID, 启动意图, 0x04000000); // FLAG_IMMUTABLE
  194.         builder.setContentTitle("" + 标题);
  195.         builder.setContentText("" + 内容);
  196.         builder.setSmallIcon(android.R.drawable.stat_notify_chat);
  197.         builder.setContentIntent(待处理意图);
  198.         nm.notify(通知ID, builder.build());
  199.         通知ID++;
  200.         printl("📤 已发出演示通知:「" + 标题 + "」→ " + 内容);
  201.     } catch (e) {
  202.         printl("⚠ 演示通知发送失败(不影响真实通知采集): " + e);
  203.     }
  204. }

  205. // ========== 主流程 ==========
  206. printl("===== 案例21 通知栏消息实时监听采集入库 启动 =====");

  207. // 步骤0:连库建表
  208. dbConnect();
  209. dbEnsureTable();
  210. printl("⚙ 配置:每 " + 演示通知间隔秒 + " 秒发一条演示通知 · 监控词(" + 监控关键词列表.join("/") +
  211.     ") · 库: " + SQLite路径);
  212. printl("ℹ 全新数据源:不拉API不截屏,直接**躺在通知栏等消息上门**——息屏也能采,事件驱动零轮询");
  213. printl("ℹ 全系列第一个事件驱动案例:主循环只管发演示通知+待机,采集全在 onNotificationEvent 回调里");
  214. printl("🔑 前提检查:系统设置→通知使用权 必须授权给本APP,否则一条通知都收不到(见下方自检)");

  215. // 步骤1:注册监听(只注册一次,之后消息来了系统自动回调 通知到达)
  216. event.onNotificationEvent(通知到达);
  217. printl("👂 通知监听已注册,等待消息...");

  218. // 停止按钮清理(顺序:先置标志,stopAllRuning 放最后)
  219. event.onStopBtnEvent(function () {
  220.     gStop = true;
  221.     dbClose();
  222.     runTime.stopAllRuning();
  223. });

  224. // 步骤2:主循环——每轮发一条演示通知喂给监听器,分片等待打进度(事件驱动下主循环纯待机)
  225. var 轮次 = 0;
  226. while (!gStop) {
  227.     轮次++;
  228.     var 剧本 = 演示通知脚本[(轮次 - 1) % 演示通知脚本.length];
  229.     printl("\n---- 第 " + 轮次 + " 轮:发送演示通知[" + 剧本.标题 + "] ----");
  230.     发送演示通知(剧本.标题, 剧本.内容);

  231.     // 自检:发过通知但一条都没被回调捕获→十有八九是没授权通知使用权
  232.     if (轮次 === 1) {
  233.         sleep.second(秒 = 2);   // 给系统一点投递时间
  234.         if (累计捕获条数 === 0) {
  235.             printl("⚠ 自检:通知已发出但没采集成功——两种可能:①系统设置→通知使用权 没授权给本APP;②授权了但回调取数据抛异常(看上方有没有'通知回调异常'日志)");
  236.         }
  237.     }

  238.     // 分片等待下一轮:每 5 秒打一条进度+统计(回调在等待期间照常工作,这就是事件驱动)
  239.     var 剩余 = 演示通知间隔秒;
  240.     while (剩余 > 0 && !gStop) {
  241.         var 步长 = 剩余 >= 5 ? 5 : 剩余;
  242.         printl("⏳ 监听待机中...下一条演示通知 " + 剩余 + " 秒后发出(期间任何APP通知都会即时捕获)");
  243.         打印统计();
  244.         sleep.second(秒 = 步长);
  245.         剩余 = 剩余 - 步长;
  246.     }
  247. }

  248. dbClose();
  249. printl("===== 流程结束:共发 " + 轮次 + " 条演示通知,捕获 " + 累计捕获条数 +
  250.     " 条,画面入库 " + 累计入库条数 + " 条,命中关键词 " + 累计命中条数 + " 条 =====");
复制代码


您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|

GMT+8, 2026-10-28 07:27

安卓脚本软件 · 键鼠盒子 · HID · 机械臂

快速回复 返回顶部 返回列表

随机推荐 · 热帖 / 新帖 / 随便看看

社区会员(随机展示)