'use strict'; const async = require('async'); const _ = require('lodash'); const db = require('../database'); const meta = require('../meta'); const user = require('../user'); const posts = require('../posts'); const plugins = require('../plugins'); const utils = require('../utils'); module.exports = function (Topics) { Topics.getTeasers = async function (topics, options) { if (!Array.isArray(topics) || !topics.length) { return []; } let uid = options; let { teaserPost } = meta.config; if (typeof options === 'object') { uid = options.uid; teaserPost = options.teaserPost || meta.config.teaserPost; } const counts = []; const teaserPids = []; const tidToPost = {}; topics.forEach((topic) => { counts.push(topic && topic.postcount); if (topic) { if (topic.teaserPid === 'null') { delete topic.teaserPid; } if (teaserPost === 'first') { teaserPids.push(topic.mainPid); } else if (teaserPost === 'last-post') { teaserPids.push(topic.teaserPid || topic.mainPid); } else { // last-reply and everything else uses teaserPid like `last` that was used before teaserPids.push(topic.teaserPid); } } }); let postData = await posts.getPostsFields(teaserPids, ['pid', 'uid', 'timestamp', 'tid', 'content']); postData = postData.filter(post => post && post.pid); postData = await handleBlocks(uid, postData); postData = postData.filter(Boolean); const uids = _.uniq(postData.map(post => post.uid)); const usersData = await user.getUsersFields(uids, ['uid', 'username', 'userslug', 'picture']); const users = {}; usersData.forEach((user) => { users[user.uid] = user; }); postData.forEach((post) => { // If the post author isn't represented in the retrieved users' data, // then it means they were deleted, assume guest. if (!users.hasOwnProperty(post.uid)) { post.uid = 0; } post.user = users[post.uid]; post.timestampISO = utils.toISOString(post.timestamp); tidToPost[post.tid] = post; }); await Promise.all(postData.map(p => posts.parsePost(p))); const { tags } = await plugins.hooks.fire('filter:teasers.configureStripTags', { tags: utils.stripTags.concat(['img']) }); const teasers = topics.map((topic, index) => { if (!topic) { return null; } if (tidToPost[topic.tid]) { tidToPost[topic.tid].index = meta.config.teaserPost === 'first' ? 1 : counts[index]; if (tidToPost[topic.tid].content) { tidToPost[topic.tid].content = utils.stripHTMLTags(replaceImgWithAltText(tidToPost[topic.tid].content), tags); } } return tidToPost[topic.tid]; }); const result = await plugins.hooks.fire('filter:teasers.get', { teasers: teasers, uid: uid }); return result.teasers; }; function replaceImgWithAltText(str) { return String(str).replace(/]*>/gi, '$1'); } async function handleBlocks(uid, teasers) { const blockedUids = await user.blocks.list(uid); if (!blockedUids.length) { return teasers; } return await async.mapSeries(teasers, async (postData) => { if (blockedUids.includes(parseInt(postData.uid, 10))) { return await getPreviousNonBlockedPost(postData, blockedUids); } return postData; }); } async function getPreviousNonBlockedPost(postData, blockedUids) { let isBlocked = false; let prevPost = postData; const postsPerIteration = 5; let start = 0; let stop = start + postsPerIteration - 1; let checkedAllReplies = false; function checkBlocked(post) { const isPostBlocked = blockedUids.includes(parseInt(post.uid, 10)); prevPost = !isPostBlocked ? post : prevPost; return isPostBlocked; } do { /* eslint-disable no-await-in-loop */ let pids = await db.getSortedSetRevRange(`tid:${postData.tid}:posts`, start, stop); if (!pids.length) { checkedAllReplies = true; const mainPid = await Topics.getTopicField(postData.tid, 'mainPid'); pids = [mainPid]; } const prevPosts = await posts.getPostsFields(pids, ['pid', 'uid', 'timestamp', 'tid', 'content']); isBlocked = prevPosts.every(checkBlocked); start += postsPerIteration; stop = start + postsPerIteration - 1; } while (isBlocked && prevPost && prevPost.pid && !checkedAllReplies); return prevPost; } Topics.getTeasersByTids = async function (tids, uid) { if (!Array.isArray(tids) || !tids.length) { return []; } const topics = await Topics.getTopicsFields(tids, ['tid', 'postcount', 'teaserPid', 'mainPid']); return await Topics.getTeasers(topics, uid); }; Topics.getTeaser = async function (tid, uid) { const teasers = await Topics.getTeasersByTids([tid], uid); return Array.isArray(teasers) && teasers.length ? teasers[0] : null; }; Topics.updateTeaser = async function (tid) { let pid = await Topics.getLatestUndeletedReply(tid); pid = pid || null; if (pid) { await Topics.setTopicField(tid, 'teaserPid', pid); } else { await Topics.deleteTopicField(tid, 'teaserPid'); } }; };