'use strict'; const db = require('../database'); const notifications = require('../notifications'); const privileges = require('../privileges'); const plugins = require('../plugins'); const utils = require('../utils'); module.exports = function (Topics) { Topics.toggleFollow = async function (tid, uid) { const exists = await Topics.exists(tid); if (!exists) { throw new Error('[[error:no-topic]]'); } const isFollowing = await Topics.isFollowing([tid], uid); if (isFollowing[0]) { await Topics.unfollow(tid, uid); } else { await Topics.follow(tid, uid); } return !isFollowing[0]; }; Topics.follow = async function (tid, uid) { await setWatching(follow, unignore, 'action:topic.follow', tid, uid); }; Topics.unfollow = async function (tid, uid) { await setWatching(unfollow, unignore, 'action:topic.unfollow', tid, uid); }; Topics.ignore = async function (tid, uid) { await setWatching(ignore, unfollow, 'action:topic.ignore', tid, uid); }; async function setWatching(method1, method2, hook, tid, uid) { if (parseInt(uid, 10) <= 0) { return; } const exists = await Topics.exists(tid); if (!exists) { throw new Error('[[error:no-topic]]'); } await method1(tid, uid); await method2(tid, uid); plugins.hooks.fire(hook, { uid: uid, tid: tid }); } async function follow(tid, uid) { await addToSets(`tid:${tid}:followers`, `uid:${uid}:followed_tids`, tid, uid); } async function unfollow(tid, uid) { await removeFromSets(`tid:${tid}:followers`, `uid:${uid}:followed_tids`, tid, uid); } async function ignore(tid, uid) { await addToSets(`tid:${tid}:ignorers`, `uid:${uid}:ignored_tids`, tid, uid); } async function unignore(tid, uid) { await removeFromSets(`tid:${tid}:ignorers`, `uid:${uid}:ignored_tids`, tid, uid); } async function addToSets(set1, set2, tid, uid) { await db.setAdd(set1, uid); await db.sortedSetAdd(set2, Date.now(), tid); } async function removeFromSets(set1, set2, tid, uid) { await db.setRemove(set1, uid); await db.sortedSetRemove(set2, tid); } Topics.isFollowing = async function (tids, uid) { return await isIgnoringOrFollowing('followers', tids, uid); }; Topics.isIgnoring = async function (tids, uid) { return await isIgnoringOrFollowing('ignorers', tids, uid); }; Topics.getFollowData = async function (tids, uid) { if (!Array.isArray(tids)) { return; } if (parseInt(uid, 10) <= 0) { return tids.map(() => ({ following: false, ignoring: false })); } const keys = []; tids.forEach(tid => keys.push(`tid:${tid}:followers`, `tid:${tid}:ignorers`)); const data = await db.isMemberOfSets(keys, uid); const followData = []; for (let i = 0; i < data.length; i += 2) { followData.push({ following: data[i], ignoring: data[i + 1], }); } return followData; }; async function isIgnoringOrFollowing(set, tids, uid) { if (!Array.isArray(tids)) { return; } if (parseInt(uid, 10) <= 0) { return tids.map(() => false); } const keys = tids.map(tid => `tid:${tid}:${set}`); return await db.isMemberOfSets(keys, uid); } Topics.getFollowers = async function (tid) { return await db.getSetMembers(`tid:${tid}:followers`); }; Topics.getIgnorers = async function (tid) { return await db.getSetMembers(`tid:${tid}:ignorers`); }; Topics.filterIgnoringUids = async function (tid, uids) { const isIgnoring = await db.isSetMembers(`tid:${tid}:ignorers`, uids); const readingUids = uids.filter((uid, index) => uid && !isIgnoring[index]); return readingUids; }; Topics.filterWatchedTids = async function (tids, uid) { if (parseInt(uid, 10) <= 0) { return []; } const scores = await db.sortedSetScores(`uid:${uid}:followed_tids`, tids); return tids.filter((tid, index) => tid && !!scores[index]); }; Topics.filterNotIgnoredTids = async function (tids, uid) { if (parseInt(uid, 10) <= 0) { return tids; } const scores = await db.sortedSetScores(`uid:${uid}:ignored_tids`, tids); return tids.filter((tid, index) => tid && !scores[index]); }; Topics.notifyFollowers = async function (postData, exceptUid, notifData) { notifData = notifData || {}; let followers = await Topics.getFollowers(postData.topic.tid); const index = followers.indexOf(String(exceptUid)); if (index !== -1) { followers.splice(index, 1); } followers = await privileges.topics.filterUids('topics:read', postData.topic.tid, followers); if (!followers.length) { return; } let { title } = postData.topic; if (title) { title = utils.decodeHTMLEntities(title); } const notification = await notifications.create({ subject: title, bodyLong: postData.content, pid: postData.pid, path: `/post/${postData.pid}`, tid: postData.topic.tid, from: exceptUid, topicTitle: title, ...notifData, }); notifications.push(notification, followers); }; };