Skip to content

Commit 042d8af

Browse files
authored
🚀 RELEASE: v0.7.0
Merge pull request #44 from thirdweb-dev/dev
2 parents c39d4c3 + 2156fe5 commit 042d8af

File tree

8 files changed

+351
-192
lines changed

8 files changed

+351
-192
lines changed

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@
1111
"license": "GPL-3.0",
1212
"private": true,
1313
"dependencies": {
14-
"discord.js": "^14.6.0",
14+
"discord.js": "^14.10.2",
1515
"dotenv": "^16.0.3",
16+
"fs-extra": "^11.1.1",
1617
"google-spreadsheet": "^3.3.0",
1718
"moment": "^2.29.4"
1819
},

src/bot.js

Lines changed: 105 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
const {
2-
ActivityType,
1+
const fs = require('node:fs');
2+
const path = require('node:path');
3+
const {
34
Client,
45
ChannelType,
56
GatewayIntentBits,
6-
Partials,
7-
EmbedBuilder } = require('discord.js');
8-
const { GoogleSpreadsheet } = require('google-spreadsheet');
9-
const config = require(`${__dirname}/config.json`);
10-
const moment = require('moment');
7+
Partials } = require('discord.js');
8+
const config = require('./config.json');
9+
const { sendEmbedMessage, formatTime } = require('./utils/core');
10+
const { sendData } = require('./utils/database');
1111

1212
require('dotenv').config();
1313

@@ -33,29 +33,6 @@ const client = new Client({
3333
]
3434
});
3535

36-
// load spreadsheet
37-
const doc = new GoogleSpreadsheet(process.env.GOOGLE_SPREADSHEET_ID);
38-
39-
/**
40-
* send embed message
41-
* @param {string} message
42-
* @returns pre-defined embed style
43-
*/
44-
const sendEmbedMessage = (message) => {
45-
return new EmbedBuilder()
46-
.setDescription(message)
47-
.setColor(`#f213a4`);
48-
}
49-
50-
/**
51-
* get username from ownerid
52-
* @param {number} id
53-
* @returns
54-
*/
55-
const getUsernameFromId = async (id) => {
56-
return (await client.users.fetch(id)).username;
57-
}
58-
5936
// listen to post messages
6037
client.on('messageCreate', async (message) => {
6138
if (message.author.bot) return;
@@ -89,15 +66,12 @@ client.on('messageCreate', async (message) => {
8966
if (typeof post.availableTags !== 'undefined') {
9067
// filter the tags to get the resolution tag name ID
9168
const resolutionTag = post.availableTags.filter((item) => { return item.name == config.tag_name_resolve });
69+
const closeTag = post.availableTags.filter((item) => { return item.name == config.tag_name_close });
9270
// get the existing tags of the post
9371
const postTags = message.channel.appliedTags;
94-
95-
// collect tags
96-
let initialTags = [resolutionTag[0].id,...postTags];
97-
let tags = [...new Set(initialTags)];
9872

9973
// check if the command has the prefix and includes "resolve"
100-
if (message.content.startsWith(config.command_prefix) && message.content.includes(config.command_resolve)) {
74+
if (message.content.startsWith(config.command_prefix)) {
10175
await message.delete(); // delete the commmand message
10276

10377
// check if the message is in the forum post and from the support role
@@ -106,39 +80,91 @@ client.on('messageCreate', async (message) => {
10680
// check if the post has fewer tags
10781
if (postTags.length < 5) {
10882

109-
// send embed message before resolving the post
110-
await message.channel.send({ embeds: [
111-
sendEmbedMessage(`${config.reminder_resolve}`)
112-
],
113-
content: `🔔 <@${message.channel.ownerId}>`
114-
})
115-
116-
// then archive and lock it
117-
message.channel.edit({
118-
appliedTags: tags,
119-
archived: true
120-
});
121-
12283
// gather data
12384
const postId = message.channel.id;
12485
const resolutionTime = formatTime(message.createdTimestamp);
12586
const resolvedBy = member.user.username;
12687

127-
// check if there's a mentioned user
128-
if (mention.users.first()) {
129-
// send the data, use the mentioned user as resolvedBy
130-
sendData({
131-
post_id: postId,
132-
resolution_time: resolutionTime,
133-
resolved_by: mention.users.first().username,
134-
}, config.datasheet_resolve);
135-
} else {
136-
// send the data with the one who sends the command
137-
sendData({
138-
post_id: postId,
139-
resolution_time: resolutionTime,
140-
resolved_by: resolvedBy
141-
}, config.datasheet_resolve);
88+
// functions for resolve command
89+
if (message.content.includes(config.command_resolve)) {
90+
91+
// data for resolve command
92+
// collect tags
93+
let initialTags = [resolutionTag[0].id,...postTags];
94+
let tags = [...new Set(initialTags)];
95+
96+
// send embed message upon executing the resolve command
97+
await message.channel.send({
98+
embeds: [
99+
sendEmbedMessage(`${config.reminder_resolve}`)
100+
],
101+
content: `🔔 <@${message.channel.ownerId}>`
102+
});
103+
104+
// then archive and lock it
105+
message.channel.edit({
106+
appliedTags: tags,
107+
archived: true
108+
});
109+
110+
// check if there's a mentioned user
111+
if (mention.users.first()) {
112+
// send the data, use the mentioned user as resolvedBy
113+
sendData({
114+
post_id: postId,
115+
resolution_time: resolutionTime,
116+
resolved_by: mention.users.first().username,
117+
}, config.datasheet_resolve);
118+
} else {
119+
// send the data with the one who sends the command
120+
sendData({
121+
post_id: postId,
122+
resolution_time: resolutionTime,
123+
resolved_by: resolvedBy
124+
}, config.datasheet_resolve);
125+
}
126+
127+
}
128+
129+
// functions for close command
130+
if (message.content.includes(config.command_close)) {
131+
132+
// data for resolve command
133+
// collect tags
134+
let initialTags = [closeTag[0].id,...postTags];
135+
let tags = [...new Set(initialTags)];
136+
137+
// send embed message upon executing the close command
138+
await message.channel.send({
139+
embeds: [
140+
sendEmbedMessage(`${config.reminder_close}`)
141+
],
142+
content: `🔔 <@${message.channel.ownerId}>`
143+
});
144+
145+
// then archive and lock it
146+
message.channel.edit({
147+
appliedTags: tags,
148+
archived: true
149+
});
150+
151+
// check if there's a mentioned user
152+
if (mention.users.first()) {
153+
// send the data, use the mentioned user as resolvedBy
154+
sendData({
155+
post_id: postId,
156+
close_time: resolutionTime,
157+
closed_by: mention.users.first().username,
158+
}, config.datasheet_close);
159+
} else {
160+
// send the data with the one who sends the command
161+
sendData({
162+
post_id: postId,
163+
close_time: resolutionTime,
164+
closed_by: resolvedBy
165+
}, config.datasheet_close);
166+
}
167+
142168
}
143169

144170
} else {
@@ -234,8 +260,10 @@ client.on('threadCreate', async post => {
234260
const tags = forumTags.join(', ');
235261
const firstResponse = `=IFERROR(VLOOKUP(A2:A,${config.datasheet_response}!A2:B,2,0))`;
236262
const resolutionTime = `=IFERROR(VLOOKUP(A2:A,${config.datasheet_resolve}!A2:B,2,0))`;
263+
const closeTime = `=IFERROR(VLOOKUP(A2:A,${config.datasheet_close}!A2:B,2,0))`;
237264
const responder = `=IFERROR(VLOOKUP(A2:A,{${config.datasheet_response}!A2:A,${config.datasheet_response}!C2:C},2,0))`;
238265
const resolvedBy = `=IFERROR(VLOOKUP(A2:A,{${config.datasheet_resolve}!A2:A,${config.datasheet_resolve}!C2:C},2,0))`;
266+
const closedBy = `=IFERROR(VLOOKUP(A2:A,{${config.datasheet_close}!A2:A,${config.datasheet_close}!C2:C},2,0))`;
239267

240268
// send the data
241269
sendData({
@@ -248,66 +276,25 @@ client.on('threadCreate', async post => {
248276
responder: responder,
249277
first_response: firstResponse,
250278
resolution_time: resolutionTime,
251-
resolved_by: resolvedBy
279+
resolved_by: resolvedBy,
280+
close_time: closeTime,
281+
closed_by: closedBy
252282
}, config.datasheet_init);
253283
});
254284

255-
/**
256-
* sends data to the spreadsheet
257-
* @param {object} data - data being added as row in the spreadsheet
258-
* @param {string} datasheet - name of sheet where data being sent e.g. init, response, resolve
259-
*/
260-
const sendData = async (data, datasheet) => {
261-
// authenticate
262-
await doc.useServiceAccountAuth({
263-
client_email: process.env.GOOGLE_SERVICE_ACCOUNT_EMAIL,
264-
private_key: process.env.GOOGLE_PRIVATE_KEY.replace(/\\n/g, "\n"),
265-
});
266-
// load the "initial" sheet
267-
await doc.loadInfo();
268-
const sheet = doc.sheetsByTitle[datasheet];
269-
270-
// check if the data will be send to init sheet
271-
if (datasheet === config.datasheet_init) {
272-
await sheet.addRow(data);
273-
};
274-
275-
// check if the data will be send to response sheet
276-
if (datasheet === config.datasheet_response) {
277-
await sheet.addRow(data);
285+
// reading events file
286+
const eventsPath = path.join(__dirname, 'events');
287+
const eventFiles = fs.readdirSync(eventsPath).filter(file => file.endsWith('.js'));
288+
289+
for (const file of eventFiles) {
290+
const filePath = path.join(eventsPath, file);
291+
const event = require(filePath);
292+
if (event.once) {
293+
client.once(event.name, (...args) => event.execute(...args));
294+
} else {
295+
client.on(event.name, (...args) => event.execute(...args));
278296
}
279-
280-
// check if the data will be send to resolve sheet
281-
if (datasheet === config.datasheet_resolve) {
282-
await sheet.addRow(data);
283-
};
284-
}
285-
286-
/**
287-
* format time according to UTC
288-
* @param {number} date - epoch timestamp
289-
* @returns time and date format
290-
*/
291-
const formatTime = (date) => {
292-
return moment.utc(date).utcOffset(config.utc_offset).format('M/DD/YYYY HH:mm:ss');
293297
}
294298

295-
// discord error log event
296-
client.on('error', (err) => {
297-
console.log(err);
298-
});
299-
300-
// discord log event
301-
client.once('ready', bot => {
302-
client.user?.setPresence({
303-
activities: [{
304-
name: 'for support.',
305-
type: ActivityType.Watching
306-
}]
307-
});
308-
309-
console.log(`Ready! Logged in as ${bot.user.tag}`);
310-
});
311-
312299
// log in to Discord with your client's token
313300
client.login(token);

src/config.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,16 @@
33
"command_resolve": "resolve",
44
"command_close": "close",
55
"command_escalate": "escalate",
6+
"command_reopen": "reopen",
67
"tag_name_resolve": "Solved",
78
"tag_name_close": "Closed",
89
"tag_name_escalate": "Escalated",
910
"datasheet_init": "init",
1011
"datasheet_response": "response",
1112
"datasheet_resolve": "resolve",
13+
"datasheet_close": "close",
14+
"datasheet_escalate": "escalate",
15+
"datasheet_reopen": "reopen",
1216
"utc_offset": -8,
1317
"reminder_mention": "Hey! If you need help, please read the information in <#1074862134284005396> and post your questions or issues in the <#1029543258822553680> channel. Our team and community members are always ready to help you out. Thank you for building with us!",
1418
"reminder_max_tags": "Max tags (5) exceeded. Please update the original post with fewer tags and try again.",

src/events/error.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
const { Events } = require('discord.js');
2+
3+
module.exports = {
4+
name: Events.Error,
5+
once: false,
6+
execute(error) {
7+
console.log(`Not ready due to ${error}`);
8+
},
9+
};

src/events/ready.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
const { Events, ActivityType } = require('discord.js');
2+
3+
module.exports = {
4+
name: Events.ClientReady,
5+
once: true,
6+
execute(bot) {
7+
bot.user?.setPresence({
8+
activities: [{
9+
name: 'for support.',
10+
type: ActivityType.Watching
11+
}]
12+
});
13+
14+
console.log(`Ready! Logged in as ${bot.user.tag}`);
15+
},
16+
};

src/utils/core.js

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
const { EmbedBuilder } = require('discord.js');
2+
const moment = require('moment');
3+
const config = require('../config.json');
4+
5+
/**
6+
* send embed message
7+
* @param {string} message
8+
* @returns pre-defined embed style
9+
*/
10+
const sendEmbedMessage = (message) => {
11+
return new EmbedBuilder()
12+
.setDescription(message)
13+
.setColor(`#f213a4`);
14+
}
15+
16+
/**
17+
* format time according to UTC
18+
* @param {number} date - epoch timestamp
19+
* @returns time and date format
20+
*/
21+
const formatTime = (date) => {
22+
return moment.utc(date).utcOffset(config.utc_offset).format('M/DD/YYYY HH:mm:ss');
23+
}
24+
25+
/**
26+
* get username from ownerid/author.id
27+
* @param {number} id user's id
28+
* @returns
29+
*/
30+
const getUsernameFromId = async (id) => {
31+
return (await client.users.fetch(id)).username;
32+
}
33+
34+
module.exports = {
35+
sendEmbedMessage,
36+
formatTime,
37+
getUsernameFromId
38+
}

0 commit comments

Comments
 (0)