Skip to content

Commit 20106ce

Browse files
committed
adding roles
1 parent fcd1443 commit 20106ce

File tree

5 files changed

+109
-49
lines changed

5 files changed

+109
-49
lines changed

README.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
- **API server** RedwoodJS API in `/api`
1414
- **(incomplete) Frontend** RedwoodJS web app in `/web`
1515

16-
## Usage
16+
## Fork it & make your own
1717

1818
### Create a new Discord Bot
1919

@@ -39,10 +39,11 @@ cd bot && yarn start
3939

4040
### Add the bot to your server
4141

42-
In the Discord Application, in "General Information", copy the `CLIENT ID`. Insert it in this URL, and send it to the server admin.
42+
In the Discord Application, in "General Information", copy the `CLIENT ID`. Insert it in this URL, and have the server administrator open it.
4343

4444
```
45-
https://discord.com/oauth2/authorize?client_id=<clientID>&scope=bot
45+
# Add the bot with role management permissions
46+
https://discord.com/oauth2/authorize?client_id=<clientID>&scope=bot&permissions=268435456
4647
```
4748

4849
### Going to Production - Heroku
@@ -77,6 +78,10 @@ source .env
7778
docker-compose up -d
7879
```
7980

81+
## Notes
82+
83+
Helpful discord docs for making a "emoji-reaction" menu https://discordjs.guide/popular-topics/reactions.html#awaiting-reactions
84+
8085
## Author
8186

8287
👤 **Patrick Gallagher**

bot/.template.env

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
DISCORD_TOKEN=
2-
INVOCATION_STRING=!verify
3-
REDIS_URL=
4-
REDIS_PASSWORD=
2+
INVOCATION_STRING=!unlock
3+
API_URL=http://localhost:8911/graphql

bot/src/apiMgr.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,13 @@ const { InMemoryCache } = require('apollo-cache-inmemory')
33
const { HttpLink } = require('apollo-link-http')
44
const fetch = require('cross-fetch')
55
const { userByDiscordId } = require('./graphql-operations/queries')
6-
const DEFAULT_URL = 'http://localhost:8911/graphql'
76

87
class ApiMgr {
98
constructor() {
10-
const url = DEFAULT_URL
119
const debug = true
1210
const cache = new InMemoryCache()
1311
const link = new HttpLink({
14-
uri: url,
12+
uri: process.env.API_URL,
1513
fetch,
1614
})
1715
this.client = new ApolloClient({

bot/src/bot.js

Lines changed: 89 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -15,50 +15,105 @@ const {
1515
DISCORD_APPROVE_CONSENT,
1616
DISCORD_DENY_CONSENT,
1717
DISCORD_CONSENT_TIMEOUT,
18+
DISCORD_INVALID_PERMISSIONS,
19+
DISCORD_ALREADY_HAVE_ROLE,
1820
} = require('./textContent')
1921

22+
const UNLOCKED_ROLE = 'Unlocked-Holder'
23+
2024
discordClient.once('ready', async () => {
2125
console.log('Ready!')
2226
})
2327

28+
const checkNftAndAssignRoles = async ({ message, guildMember, roleId }) => {
29+
await message.reply(DISCORD_APPROVE_CONSENT)
30+
console.log(guildMember)
31+
const { nfts, error } = await apiMgr.getNfts(guildMember.id)
32+
if (error) return message.reply(error)
33+
// if (!nfts) return message.reply(DISCORD_FAIL)
34+
guildMember.roles.add(roleId)
35+
await message.reply(DISCORD_SUCCESS)
36+
}
37+
2438
discordClient.on('message', async (message) => {
25-
if (message.content === process.env.INVOCATION_STRING) {
26-
// console.log(message);
27-
message.reply(DISCORD_REPLY)
28-
const sentMessage = await message.author.send(DISCORD_INITIAL_PROMPT)
29-
await sentMessage.react('❌')
30-
await sentMessage.react('✅')
31-
const filter = (reaction, user) => {
32-
return (
33-
['❌', '✅'].includes(reaction.emoji.name) &&
34-
user.id === message.author.id
35-
)
39+
try {
40+
if (message.content === process.env.INVOCATION_STRING) {
41+
// console.log(message)
42+
43+
// Check bot is set up properly
44+
if (!message.guild.me.hasPermission(['MANAGE_ROLES']))
45+
return message.channel.send(DISCORD_INVALID_PERMISSIONS)
46+
47+
// Check user already has role
48+
const guild = message.guild
49+
const guildMember = message.member
50+
const role = guild.roles.cache.find((role) => role.name === UNLOCKED_ROLE)
51+
if (guildMember.roles.cache.has(role.id))
52+
return message.reply(`${DISCORD_ALREADY_HAVE_ROLE} "${UNLOCKED_ROLE}"`)
53+
54+
// Tell user to check DMs
55+
message.reply(DISCORD_REPLY)
56+
57+
// Start DM with user
58+
const sentMessage = await message.author.send(DISCORD_INITIAL_PROMPT)
59+
await sentMessage.react('❌')
60+
await sentMessage.react('✅')
61+
62+
// Wait for Emoji reply
63+
const filter = (reaction, user) => {
64+
return (
65+
['❌', '✅'].includes(reaction.emoji.name) &&
66+
user.id === message.author.id
67+
)
68+
}
69+
sentMessage
70+
.awaitReactions(filter, { max: 1, time: 60000, errors: ['time'] })
71+
.then((collected) => {
72+
const reaction = collected.first()
73+
if (reaction.emoji.name === '✅') {
74+
// Approved consent
75+
checkNftAndAssignRoles({
76+
message: sentMessage,
77+
guildMember,
78+
roleId: role.id,
79+
})
80+
} else {
81+
// Denied consent
82+
sentMessage.reply(DISCORD_DENY_CONSENT)
83+
}
84+
})
85+
.catch((collected) => {
86+
console.log(`Timeout for emoji response ${sentMessage.author}`)
87+
sentMessage.reply(DISCORD_CONSENT_TIMEOUT)
88+
})
3689
}
37-
// APPROVED CONSENT
38-
sentMessage
39-
.awaitReactions(filter, { max: 1, time: 60000, errors: ['time'] })
40-
.then((collected) => {
41-
const reaction = collected.first()
42-
if (reaction.emoji.name === '✅') {
43-
checkNftAndAssignRoles(sentMessage, message.author.id)
44-
} else {
45-
sentMessage.reply(DISCORD_DENY_CONSENT)
46-
}
47-
})
48-
.catch((collected) => {
49-
console.log(`Timeout for emoji response ${sentMessage.author}`)
50-
sentMessage.reply(DISCORD_CONSENT_TIMEOUT)
51-
})
90+
} catch (e) {
91+
console.log(e)
92+
message.reply(DISCORD_SERVER_ERROR)
5293
}
5394
})
5495

55-
const checkNftAndAssignRoles = async (message, authorId) => {
56-
await message.reply(DISCORD_APPROVE_CONSENT)
57-
const { nfts, error } = await apiMgr.getNfts(authorId)
58-
if (error) return message.reply(error)
59-
if (!nfts) return message.reply(DISCORD_FAIL)
60-
// TODO: assign roles in server
61-
await message.reply(DISCORD_SUCCESS)
62-
}
96+
discordClient.on('guildMemberUpdate', (oldMember, newMember) => {
97+
// If the role(s) are present on the old member object but no longer on the new one (i.e role(s) were removed)
98+
const removedRoles = oldMember.roles.cache.filter(
99+
(role) => !newMember.roles.cache.has(role.id)
100+
)
101+
if (removedRoles.size > 0)
102+
console.log(
103+
`The roles ${removedRoles.map((r) => r.name)} were removed from ${
104+
oldMember.displayName
105+
}.`
106+
)
107+
// If the role(s) are present on the new member object but are not on the old one (i.e role(s) were added)
108+
const addedRoles = newMember.roles.cache.filter(
109+
(role) => !oldMember.roles.cache.has(role.id)
110+
)
111+
if (addedRoles.size > 0)
112+
console.log(
113+
`The roles ${addedRoles.map((r) => r.name)} were added to ${
114+
oldMember.displayName
115+
}.`
116+
)
117+
})
63118

64119
discordClient.login(process.env.DISCORD_TOKEN)

bot/src/textContent.js

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
const DISCORD_SERVER_ERROR = 'Whoops... we had an internal issue'
2-
const DISCORD_SUCCESS = `🤩 What fantastic NFTs you have!
3-
I gave you some new roles. Head back to your server to use them. 👋 See ya`
4-
const DISCORD_FAIL = `🤔 We couldn't find the relevant NFTs in your wallet. If you believe this is an error, contact us: https://twitter.com/pi0neerpat`
1+
const DISCORD_SERVER_ERROR = `⛈️ Sorry, something went wrong.`
2+
const DISCORD_SUCCESS = `Success! What fantastic NFTs you have 🤩
3+
I gave you a new role, enjoy!`
4+
const DISCORD_FAIL = `🤔 I couldn't find the relevant NFTs in your wallet. If you believe this is an error, contact us: https://twitter.com/pi0neerpat`
55
const DISCORD_REPLY = `Please check your DMs`
66
const DISCORD_INITIAL_PROMPT = `👋 Hi there! Let's take a look at your NFTs.\n\nIn order to continue, you need to give the following permissions to the Unlock Protocol Bot:
77
\n\`- Fetch your wallet address from your CollabLand account\`
88
\n*Unlock Protocol Bot is a community-maintained project by @pi0neerpat*
99
\nMake your choice by clicking a button:`
1010
const DISCORD_APPROVE_CONSENT = `Searching...`
1111
const DISCORD_DENY_CONSENT = `Ok, no problem. Goodbye!`
12-
const DISCORD_CONSENT_TIMEOUT = `👻 Ghosted! If you'd like to continue, you'll need to start over.
13-
In your server, type "${process.env.INVOCATION_STRING}"`
12+
const DISCORD_CONSENT_TIMEOUT = `👻 Ghosted! If you want to continue, you'll need to start over. In your server, type "${process.env.INVOCATION_STRING}"`
13+
const DISCORD_INVALID_PERMISSIONS = `⛈️ Sorry, I'm powerless. The admin must give me permission to manage roles.`
14+
const DISCORD_ALREADY_HAVE_ROLE = `👍 You already have the role `
1415

1516
module.exports = {
1617
DISCORD_SERVER_ERROR,
@@ -21,4 +22,6 @@ module.exports = {
2122
DISCORD_APPROVE_CONSENT,
2223
DISCORD_DENY_CONSENT,
2324
DISCORD_CONSENT_TIMEOUT,
25+
DISCORD_INVALID_PERMISSIONS,
26+
DISCORD_ALREADY_HAVE_ROLE,
2427
}

0 commit comments

Comments
 (0)