Repository navigation
Expand file tree
/
Copy pathdiscordPrivacy.js
More file actions
80 lines (75 loc) · 4.69 KB
/
Copy pathdiscordPrivacy.js
File metadata and controls
80 lines (75 loc) · 4.69 KB
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
const { ChannelType, PermissionFlagsBits } = require('discord.js');
function has(permissionSet, flag) {
if (typeof permissionSet?.has !== 'function') throw new Error('Unknown ACL');
const value = permissionSet.has(flag, false);
if (typeof value !== 'boolean') throw new Error('Unknown ACL');
return value;
}
function authority(permissionSet) {
return has(permissionSet, PermissionFlagsBits.Administrator) || has(permissionSet, PermissionFlagsBits.ManageThreads);
}
async function validateStaffDestination(channel, { botUserId, staffUsers = [], staffRoleIds = [] } = {}) {
const reject = (code) => ({ ok: false, code });
try {
const guild = channel?.guild;
if (!guild?.id || guild.available === false || !botUserId) return reject('guild_acl_unavailable');
if (![ChannelType.GuildText, ChannelType.GuildAnnouncement].includes(channel.type) || channel.isThread?.()) return reject('unsupported_staff_channel');
const roles = guild.roles?.cache;
const overwrites = channel.permissionOverwrites?.cache;
if (!roles?.values || !overwrites?.values || typeof channel.permissionsFor !== 'function') return reject('guild_acl_unavailable');
const everyone = roles.get(String(guild.id));
if (!everyone || !everyone.permissions) return reject('guild_acl_unavailable');
if (has(channel.permissionsFor(everyone), PermissionFlagsBits.ViewChannel)) return reject('everyone_can_view');
const users = new Set(staffUsers.map(String));
const staffRoles = new Set(staffRoleIds.map(String));
const trustedRole = (role) => staffRoles.has(String(role.id)) || authority(role.permissions) ||
(role.managed === true && String(role.tags?.botId || '') === String(botUserId));
for (const role of roles.values()) {
if (String(role.id) === String(guild.id)) continue;
const effective = channel.permissionsFor(role);
if (has(effective, PermissionFlagsBits.ViewChannel) && !trustedRole(role)) return reject('untrusted_role_can_view');
}
for (const overwrite of overwrites.values()) {
if (overwrite.type === 0) {
if (!roles.has(String(overwrite.id))) return reject('role_acl_unavailable');
continue;
}
if (overwrite.type !== 1) return reject('overwrite_acl_unavailable');
const id = String(overwrite.id);
if (!has(overwrite.allow, PermissionFlagsBits.ViewChannel)) continue;
if (id === String(botUserId) || id === String(guild.ownerId || '') || users.has(id)) continue;
// Fetch only this direct grantee over REST. Never trust stale cached
// moderation roles, request all members, or enable a privileged intent.
if (typeof guild.members?.fetch !== 'function') return reject('member_acl_unverifiable');
let member;
try { member = await guild.members.fetch({ user: id, force: true, cache: false }); }
catch { return reject('member_acl_unverifiable'); }
const memberRoles = member?.roles?.cache;
if (!member || String(member.id) !== id || String(member.guild?.id || '') !== String(guild.id) ||
member.partial || !memberRoles?.values) return reject('member_acl_unverifiable');
const resolvedRoles = [...memberRoles.values()];
if (resolvedRoles.some((role) => !roles.has(String(role.id)))) return reject('member_acl_unverifiable');
if (!resolvedRoles.some((role) => staffRoles.has(String(role.id)) || authority(role.permissions))) return reject('untrusted_member_can_view');
}
const bot = guild.members?.me;
if (!bot || String(bot.id) !== String(botUserId)) return reject('bot_acl_unverifiable');
const botPermissions = channel.permissionsFor(bot);
if (!has(botPermissions, PermissionFlagsBits.ViewChannel) || !has(botPermissions, PermissionFlagsBits.SendMessages)) return reject('bot_cannot_send');
return { ok: true, code: 'private_staff_destination' };
} catch {
return reject('guild_acl_unavailable');
}
}
async function checkConfiguredStaffDestination(client, options = {}) {
const channelId = String(options.channelId ?? process.env.STAFF_ALERT_CHANNEL_ID ?? '');
if (!channelId) return { ok: false, code: 'staff_destination_missing' };
try {
if (typeof client?.channels?.fetch !== 'function') return { ok: false, code: 'staff_channel_unavailable' };
const channel = await client.channels.fetch(channelId, { force: true });
if (!channel || String(channel.id) !== channelId) return { ok: false, code: 'staff_channel_unavailable' };
return await validateStaffDestination(channel, {
botUserId: client.user?.id, staffUsers: options.staffUsers || [], staffRoleIds: options.staffRoleIds || [],
});
} catch { return { ok: false, code: 'staff_channel_unavailable' }; }
}
module.exports = { validateStaffDestination, checkConfiguredStaffDestination };