viewer_filters.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. const fs = require('fs');
  2. const path = require('path');
  3. const { getConfig } = require('../configs/config-manager.js');
  4. const COOLDOWN_MS = 5 * 60 * 1000;
  5. const statePath = path.join(__dirname, '../configs/follow_state.json');
  6. const readJson = (p, fallback) => {
  7. try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (e) { return fallback; }
  8. };
  9. const writeJson = (p, data) => {
  10. try { fs.writeFileSync(p, JSON.stringify(data, null, 2)); } catch (e) {}
  11. };
  12. const loadState = () => {
  13. const s = readJson(statePath, { pending: [], accepted: [], lastAcceptMs: 0 });
  14. if (!Array.isArray(s.pending)) s.pending = [];
  15. if (!Array.isArray(s.accepted)) s.accepted = [];
  16. if (typeof s.lastAcceptMs !== 'number') s.lastAcceptMs = 0;
  17. return s;
  18. };
  19. const saveState = (s) => writeJson(statePath, s);
  20. const wishMutualsOnly = () => getConfig().wish === 'mutuals';
  21. const pmMutualsOnly = () => getConfig().pmVisibility === 'mutuals';
  22. const isFrictionActive = () => wishMutualsOnly() || pmMutualsOnly();
  23. const listPending = () => loadState().pending;
  24. const enqueuePending = (followerId, extra = {}) => {
  25. if (!followerId) return false;
  26. const s = loadState();
  27. if (s.pending.some(x => x.followerId === followerId)) return false;
  28. s.pending.push({ followerId, at: new Date().toISOString(), ...extra });
  29. saveState(s);
  30. return true;
  31. };
  32. const removePending = (followerId) => {
  33. const s = loadState();
  34. s.pending = s.pending.filter(x => x.followerId !== followerId);
  35. saveState(s);
  36. };
  37. const loadAccepted = () => loadState().accepted;
  38. const isAccepted = (followerId) => loadState().accepted.includes(followerId);
  39. const addAccepted = (followerId) => {
  40. if (!followerId) return;
  41. const s = loadState();
  42. if (!s.accepted.includes(followerId)) { s.accepted.push(followerId); saveState(s); }
  43. };
  44. const removeAccepted = (followerId) => {
  45. const s = loadState();
  46. s.accepted = s.accepted.filter(x => x !== followerId);
  47. saveState(s);
  48. };
  49. const canAutoAcceptNow = () => (Date.now() - loadState().lastAcceptMs) >= COOLDOWN_MS;
  50. const markAutoAccept = () => {
  51. const s = loadState();
  52. s.lastAcceptMs = Date.now();
  53. saveState(s);
  54. };
  55. const makeMutualCache = (friendModel) => {
  56. const cache = new Map();
  57. const frictionActive = isFrictionActive();
  58. return async (otherId) => {
  59. if (!otherId) return false;
  60. if (cache.has(otherId)) return cache.get(otherId);
  61. try {
  62. const rel = await friendModel.getRelationship(otherId);
  63. const basic = !!(rel && rel.following && rel.followsMe);
  64. const mutual = frictionActive ? (basic && isAccepted(otherId)) : basic;
  65. cache.set(otherId, mutual);
  66. return mutual;
  67. } catch (e) {
  68. cache.set(otherId, false);
  69. return false;
  70. }
  71. };
  72. };
  73. const authorOf = (item) => {
  74. if (!item) return null;
  75. if (item.value && item.value.author) return item.value.author;
  76. if (item.author) return item.author;
  77. if (item.feed) return item.feed;
  78. if (item.id && typeof item.id === 'string' && item.id.startsWith('@')) return item.id;
  79. return null;
  80. };
  81. const applyMutualSupportFilter = async (items, viewerId, friendModel) => {
  82. if (!wishMutualsOnly()) return items;
  83. if (!Array.isArray(items)) return items;
  84. const isMutual = makeMutualCache(friendModel);
  85. const out = [];
  86. for (const it of items) {
  87. const a = authorOf(it);
  88. if (!a || a === viewerId) { out.push(it); continue; }
  89. if (await isMutual(a)) out.push(it);
  90. }
  91. return out;
  92. };
  93. const canSendPmTo = async (viewerId, recipientId, friendModel) => {
  94. if (!pmMutualsOnly()) return { allowed: true };
  95. if (viewerId === recipientId) return { allowed: true };
  96. const isMutual = makeMutualCache(friendModel);
  97. if (await isMutual(recipientId)) return { allowed: true };
  98. return { allowed: false, reason: 'non-mutual' };
  99. };
  100. module.exports = {
  101. COOLDOWN_MS,
  102. wishMutualsOnly,
  103. pmMutualsOnly,
  104. isFrictionActive,
  105. listPending,
  106. enqueuePending,
  107. removePending,
  108. loadAccepted,
  109. isAccepted,
  110. addAccepted,
  111. removeAccepted,
  112. canAutoAcceptNow,
  113. markAutoAccept,
  114. makeMutualCache,
  115. applyMutualSupportFilter,
  116. canSendPmTo,
  117. authorOf,
  118. };