friend-polling-worker.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. #!/usr/bin/env node
  2. /**
  3. * 好友轮询兜底 Worker
  4. *
  5. * 独立运行,定期检查已加好友但尚未建群的客户,触发自动建群。
  6. *
  7. * 启动方式:
  8. * node scripts/friend-polling-worker.js
  9. * INTERVAL_MS=300000 node scripts/friend-polling-worker.js
  10. */
  11. const fs = require('fs');
  12. const path = require('path');
  13. const { outputsRoot } = require('../mcp/src/core/output-paths');
  14. const { readCustomerById, writeCustomer } = require('../mcp/src/core/customer-broker-store');
  15. const { readConfirmedMapping } = require('../mcp/src/core/webhook-processor');
  16. const { triggerAutoCreateGroup } = require('../mcp/src/core/webhook-processor');
  17. const INTERVAL_MS = Number(process.env.FRIEND_POLL_INTERVAL_MS || 300000);
  18. function customersDir() {
  19. return path.join(outputsRoot(), 'customers');
  20. }
  21. function safeReadJson(filePath) {
  22. try {
  23. if (!fs.existsSync(filePath)) return null;
  24. return JSON.parse(fs.readFileSync(filePath, 'utf8'));
  25. } catch {
  26. return null;
  27. }
  28. }
  29. function listCustomerFiles() {
  30. const dir = customersDir();
  31. if (!fs.existsSync(dir)) return [];
  32. return fs.readdirSync(dir)
  33. .filter(f => f.endsWith('.json') && !f.startsWith('index-'))
  34. .map(f => path.join(dir, f));
  35. }
  36. function hasActiveGroupForCustomer(customerId) {
  37. const mapping = readConfirmedMapping();
  38. for (const entry of Object.values(mapping)) {
  39. if (entry.customerId === customerId && entry.status === 'ACTIVE') return true;
  40. }
  41. return false;
  42. }
  43. async function runOnce() {
  44. const files = listCustomerFiles();
  45. let checked = 0;
  46. let created = 0;
  47. let ignored = 0;
  48. let errors = 0;
  49. for (const file of files) {
  50. const customer = safeReadJson(file);
  51. if (!customer || !customer.customerId) continue;
  52. // 只处理有 brokerId 且标记为已通过好友但尚未建群的客户
  53. if (!customer.brokerId) continue;
  54. if (customer.friendRequestStatus !== 'ACCEPTED' && customer.friendRequestStatus !== 'PENDING') continue;
  55. if (hasActiveGroupForCustomer(customer.customerId)) continue;
  56. checked++;
  57. const event = {
  58. parsedType: 'CONTACT_ADDED_OR_CHANGED',
  59. msgType: 2131,
  60. externalUserId: customer.externalUserId,
  61. guid: undefined,
  62. timestamp: Math.floor(Date.now() / 1000),
  63. eventId: `poll-${customer.customerId}-${Date.now()}`,
  64. raw: {}
  65. };
  66. const result = await triggerAutoCreateGroup(event);
  67. if (result.success) {
  68. created++;
  69. customer.friendRequestStatus = 'ACCEPTED';
  70. writeCustomer(customer);
  71. } else if (result.ignored) {
  72. ignored++;
  73. } else {
  74. errors++;
  75. }
  76. }
  77. console.log(`[FriendPolling] 本轮完成: 检查 ${checked}, 建群 ${created}, 忽略 ${ignored}, 错误 ${errors}`);
  78. return { checked, created, ignored, errors };
  79. }
  80. async function main() {
  81. console.log(`[FriendPolling] 启动,轮询间隔 ${INTERVAL_MS}ms`);
  82. while (true) {
  83. try {
  84. await runOnce();
  85. } catch (err) {
  86. console.error('[FriendPolling] 本轮异常:', err.message);
  87. }
  88. await new Promise(resolve => setTimeout(resolve, INTERVAL_MS));
  89. }
  90. }
  91. main().catch(err => {
  92. console.error('[FriendPolling] 致命错误:', err);
  93. process.exit(1);
  94. });