-
Notifications
You must be signed in to change notification settings - Fork 2.7k
/
digest.js
201 lines (179 loc) · 6 KB
/
digest.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
'use strict';
const async = require('async');
const winston = require('winston');
const nconf = require('nconf');
const db = require('../database');
const batch = require('../batch');
const meta = require('../meta');
const user = require('./index');
const topics = require('../topics');
const plugins = require('../plugins');
const emailer = require('../emailer');
const utils = require('../utils');
const Digest = module.exports;
Digest.execute = async function (payload) {
const digestsDisabled = meta.config.disableEmailSubscriptions === 1;
if (digestsDisabled) {
winston.info('[user/jobs] Did not send digests (' + payload.interval + ') because subscription system is disabled.');
return;
}
let subscribers = payload.subscribers;
if (!subscribers) {
subscribers = await Digest.getSubscribers(payload.interval);
}
if (!subscribers.length) {
return;
}
try {
winston.info('[user/jobs] Digest (' + payload.interval + ') scheduling completed. Sending emails; this may take some time...');
await Digest.send({
interval: payload.interval,
subscribers: subscribers,
});
winston.info('[user/jobs] Digest (' + payload.interval + ') complete.');
} catch (err) {
winston.error('[user/jobs] Could not send digests (' + payload.interval + ')\n' + err.stack);
throw err;
}
};
Digest.getUsersInterval = async (uids) => {
// Checks whether user specifies digest setting, or null/false for system default setting
let single = false;
if (!Array.isArray(uids) && !isNaN(parseInt(uids, 10))) {
uids = [uids];
single = true;
}
const settings = await Promise.all([
db.isSortedSetMembers('digest:day:uids', uids),
db.isSortedSetMembers('digest:week:uids', uids),
db.isSortedSetMembers('digest:month:uids', uids),
]);
const interval = uids.map((uid, index) => {
if (settings[0][index]) {
return 'day';
} else if (settings[1][index]) {
return 'week';
} else if (settings[2][index]) {
return 'month';
}
return false;
});
return single ? interval[0] : interval;
};
Digest.getSubscribers = async function (interval) {
var subscribers = [];
await batch.processSortedSet('users:joindate', async function (uids) {
const settings = await user.getMultipleUserSettings(uids);
let subUids = [];
settings.forEach(function (hash) {
if (hash.dailyDigestFreq === interval) {
subUids.push(hash.uid);
}
});
subUids = await user.bans.filterBanned(subUids);
subscribers = subscribers.concat(subUids);
}, {
interval: 1000,
batch: 500,
});
const results = await plugins.fireHook('filter:digest.subscribers', {
interval: interval,
subscribers: subscribers,
});
return results.subscribers;
};
Digest.send = async function (data) {
let emailsSent = 0;
if (!data || !data.subscribers || !data.subscribers.length) {
return emailsSent;
}
await async.eachLimit(data.subscribers, 100, async function (uid) {
const userObj = await user.getUserFields(uid, ['uid', 'username', 'userslug', 'lastonline']);
let [notifications, topicsData] = await Promise.all([
user.notifications.getUnreadInterval(userObj.uid, data.interval),
getTermTopics(data.interval, userObj.uid, 0, 9),
]);
notifications = notifications.filter(Boolean);
// If there are no notifications and no new topics, don't bother sending a digest
if (!notifications.length && !topicsData.length) {
return;
}
notifications.forEach(function (n) {
if (n.image && !n.image.startsWith('http')) {
n.image = nconf.get('base_url') + n.image;
}
if (n.path) {
n.notification_url = n.path.startsWith('http') ? n.path : nconf.get('base_url') + n.path;
}
});
// Fix relative paths in topic data
topicsData = topicsData.map(function (topicObj) {
const user = topicObj.hasOwnProperty('teaser') && topicObj.teaser && topicObj.teaser.user ? topicObj.teaser.user : topicObj.user;
if (user && user.picture && utils.isRelativeUrl(user.picture)) {
user.picture = nconf.get('base_url') + user.picture;
}
return topicObj;
});
emailsSent += 1;
const now = new Date();
try {
await emailer.send('digest', userObj.uid, {
subject: '[[email:digest.subject, ' + (now.getFullYear() + '/' + (now.getMonth() + 1) + '/' + now.getDate()) + ']]',
username: userObj.username,
userslug: userObj.userslug,
notifications: notifications,
recent: topicsData,
interval: data.interval,
showUnsubscribe: true,
});
} catch (err) {
winston.error('[user/jobs] Could not send digest email\n' + err.stack);
}
if (data.interval !== 'alltime') {
await db.sortedSetAdd('digest:delivery', now.getTime(), userObj.uid);
}
});
winston.info('[user/jobs] Digest (' + data.interval + ') sending completed. ' + emailsSent + ' emails sent.');
};
Digest.getDeliveryTimes = async (start, stop) => {
const count = await db.sortedSetCard('users:joindate');
const uids = await user.getUidsFromSet('users:joindate', start, stop);
if (!uids) {
return [];
}
// Grab the last time a digest was successfully delivered to these uids
const scores = await db.sortedSetScores('digest:delivery', uids);
// Get users' digest settings
const settings = await Digest.getUsersInterval(uids);
// Populate user data
let userData = await user.getUsersFields(uids, ['username', 'picture']);
userData = userData.map((user, idx) => {
user.lastDelivery = scores[idx] ? new Date(scores[idx]).toISOString() : '[[admin/manage/digest:null]]';
user.setting = settings[idx];
return user;
});
return {
users: userData,
count: count,
};
};
async function getTermTopics(term, uid, start, stop) {
const options = {
uid: uid,
start: start,
stop: stop,
term: term,
sort: 'posts',
teaserPost: 'last-post',
};
let data = await topics.getSortedTopics(options);
if (!data.topics.length) {
data = await topics.getLatestTopics(options);
}
data.topics.forEach(function (topicObj) {
if (topicObj && topicObj.teaser && topicObj.teaser.content && topicObj.teaser.content.length > 255) {
topicObj.teaser.content = topicObj.teaser.content.slice(0, 255) + '...';
}
});
return data.topics.filter(topic => topic && !topic.deleted);
}