Skip to content

Instantly share code, notes, and snippets.

@Jessidhia
Created July 16, 2018 08:02
Show Gist options
  • Save Jessidhia/97bdbe1345a38f9b578f959a8795a6e2 to your computer and use it in GitHub Desktop.
Save Jessidhia/97bdbe1345a38f9b578f959a8795a6e2 to your computer and use it in GitHub Desktop.
import Discord from 'discord.js'
const messageConfig: Map<string, EmojiRoleMap> = new Map([
// ['messageId', Map<'emoji id', 'role name'>]
[
'468309475808509955',
new Map<string, string>([
// TO-CHECK: discord.js v12 might drop the emoji name from the identifier
// ['emoji id', 'role name']
// this order is the same as the order in https://jp.finalfantasyxiv.com/jobguide/battle/
['PLD:468295205582274580', 'ナイト'],
['WAR:468295205712429076', '戦士'],
['DRK:468295205523554304', '暗黒騎士'],
['WHM:468295205569691658', '白魔道士'],
['SCH:468295205523685386', '学者'],
['AST:468295205510971393', '占星術士'],
['MNK:468295205636800512', 'モンク'],
['DRG:468295205553045513', '竜騎士'],
['NIN:468295205486067732', '忍者'],
['SAM:468295205448318976', '侍'],
['BRD:468295205452382208', '吟遊詩人'],
['MCH:468295205276221441', '機構士'],
['BLM:468295205339136020', '黒魔道士'],
['SMN:468295205418827780', '召喚土'],
['RDM:468295205351850004', '赤魔道士']
])
]
])
// Add token here as a string
main({
token: null,
messageConfig
})
async function main(config: Config) {
const client = makeClient(config)
await client.login(config.token)
return client
}
function makeClient(config: Config) {
const client = new Discord.Client()
client.on('raw', handleRawMessage)
client.on('ready', handleReady)
return client
async function handleReady() {
await Promise.all([
// if there's any additional setup to be done, add their promises to this array
setupReactions(config.messageConfig)
])
await client.user.setPresence({
game: {
name: 'discord.js'
},
status: 'idle'
})
async function setupReactions(messageConfig: Map<string, EmojiRoleMap>) {
const roleMessages = ([] as Discord.Message[]).concat(
...(await Promise.all(
client.channels.map(async channel => {
// some channels are not `text` channels and can't fetchMessage in them
if (!('fetchMessage' in channel)) {
return []
}
return allFilterRejects(
Array.from(messageConfig.keys(), messageId =>
(channel as Discord.TextChannel).fetchMessage(messageId)
)
)
})
))
)
for (const roleMessage of roleMessages) {
// NOTE: awaits in a loop to ensure reactions are added in the right order
for (const emoji of messageConfig.get(roleMessage.id)!.keys()) {
const existingEmoji = roleMessage.reactions.find('emoji', emoji)
if (!existingEmoji || !existingEmoji.users.has(client.user.id)) {
await roleMessage.react(emoji)
}
}
}
}
}
// the native messageReactionAdd / messageReactionRemove events only work for messages sent after
// the bot is started; so we need to handle the raw events
async function handleRawMessage(event: RawEvent) {
if (
event.t !== DiscordEventType.ReactionAdd &&
event.t !== DiscordEventType.ReactionRemove
) {
return
}
const { d: data } = event
const emojiRoleMap = config.messageConfig.get(data.message_id)
if (emojiRoleMap === undefined) {
return
}
if (data.user_id === client.user.id) {
return
}
const user = client.users.get(data.user_id)
if (user === undefined) {
console.log(`Could not retrieve ${data.user_id}`)
return
}
if (user.id === client.user.id) {
return
}
// only messages in a TextChannel can have reactions added to them
const channel = client.channels.get(data.channel_id) as
| Discord.TextChannel
| undefined
if (channel === undefined) {
console.log(`Could not retrieve channel ${data.channel_id}`)
return
}
const message = await channel.fetchMessage(data.message_id)
if (message === undefined) {
console.log(`Could not retrieve message ${data.message_id}`)
return
}
// NOTE: v12 will change to use only `data.emoji.name` or `data.emoji.id`
const emojiKey =
data.emoji.id === null
? data.emoji.name
: `${data.emoji.name}:${data.emoji.id}`
const emoji = message.reactions.get(emojiKey)
if (emoji === undefined) {
console.log(`Could not retrieve emoji ${emojiKey}`)
return
}
const roleName = emojiRoleMap.get(emoji.emoji.identifier)
if (roleName === undefined) {
console.log(`Could not get role name for ${emoji.emoji.identifier}`)
return
}
const role = message.guild.roles.find('name', roleName)
const member = await message.guild.fetchMember(user)
if (event.t === DiscordEventType.ReactionAdd) {
console.log(`Adding role ${roleName} to user ${member.displayName}`)
await member.addRole(role, 'リアクション')
} else {
console.log(`Removing role ${roleName} from user ${member.displayName}`)
await member.removeRole(role, 'リアクション')
}
}
}
async function allFilterRejects<T>(
promises: Iterable<Promise<T>>
): Promise<T[]> {
const rejectValue = Symbol()
const promisesArray = Array.from(promises, p => p.catch(() => rejectValue))
const result = await Promise.all(promisesArray)
return result.filter((entry): entry is T => entry !== rejectValue)
}
interface Config {
token: string
messageConfig: Map<string, EmojiRoleMap>
}
type EmojiRoleMap = Map<string, string>
enum DiscordEventType {
ReactionAdd = 'MESSAGE_REACTION_ADD',
ReactionRemove = 'MESSAGE_REACTION_REMOVE'
}
interface RawEvent {
t: DiscordEventType | null
d: {
user_id: string
message_id: string
emoji: { name: string; id: string | null; animated: boolean }
channel_id: string
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment