Skip to content

Commit f80df00

Browse files
naeruruMazmol
andauthored
v1.6.2: allow message edits, code restructure, and bug fixes (#33)
* add ability for users to edit their embeds (#29) set "editOnMsgUpdate" to true in settings.json * remove console.log (oops) * change editOnMsgUpdate to editMsgGracePeriod editMsgGracePeriod defines how long (in seconds) one can edit a message and it update on the board * fix msg undefined error * fix issue where custom emojis would not fetch correctly on msg edit * Update README.md (#32) * Update README.md added to setting.json readme - editMsgGracePeriod - editOnMsgUpdate Added to table with explaination (might need to be checked my english is bad. ) * Update README.md Updated with Changes recommended by Naexris * finalize v1.6.2 --------- Co-authored-by: Mazmol <38956955+Mazmol@users.noreply.github.com>
1 parent 33fac0c commit f80df00

File tree

7 files changed

+149
-106
lines changed

7 files changed

+149
-106
lines changed

LICENSE.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2018 - present @Rushnett
3+
Copyright (c) 2018 - present @naeruru
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

PRIVACYPOLICY.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Smugboard Privacy Policy
2-
Last updated May 5th, 2021
2+
Last updated June 8th, 2023
33

44
## Introduction
55
Smugboard ("this bot" or "me" or "we" or "us" or "our") collects a small amount of data when users ("users" or "you") use this bot through Discord. This Privacy Policy explains how it collects, uses, and safeguards this information. Because this is an open source project, it must be stated that this Privacy Policy strictly pertains to Smugboard (bot administered by me). Any other bots that use this repository may differ slightly from this Privacy Policy and are in no way connected with me. Please read this privacy policy carefully. If you do not agree with the terms, please do not access Discord servers with this bot ("participating servers").
@@ -37,8 +37,8 @@ Your information will never be sold to advertisers or third parties for any reas
3737

3838

3939
## Options regarding your information
40-
You may at any time request deletion of your stored information. You may do this by messaging me (Rushnett) on participating servers and requesting deletion of your information. You may also create an Issue on this repository ([https://github.com/Rushnett/starboard/issues](https://github.com/Rushnett/starboard/issues)). At the time of requesting, your Discord ID will be collected in order to look up and delete your stored content on our database (searchable by said Discord ID). However, some information will be retained in the participating Discord server (commonly known as the "posting channel").
40+
You may at any time request deletion of your stored information. You may do this by messaging me (Rushnett) on participating servers and requesting deletion of your information. You may also create an Issue on this repository ([https://github.com/naeruru/starboard/issues](https://github.com/naeruru/starboard/issues)). At the time of requesting, your Discord ID will be collected in order to look up and delete your stored content on our database (searchable by said Discord ID). However, some information will be retained in the participating Discord server (commonly known as the "posting channel").
4141

4242

4343
## Contact
44-
if you have any questions or comments about this Privacy Policy, you can create an issue on this repository at [https://github.com/Rushnett/starboard/issues](https://github.com/Rushnett/starboard/issues).
44+
if you have any questions or comments about this Privacy Policy, you can create an issue on this repository at [https://github.com/naeruru/starboard/issues](https://github.com/naeruru/starboard/issues).

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ After creating your bot and cloning the repository, the only setup that needs to
1919
"threshold": 15,
2020
"hexcolor": "00AE86",
2121
"dateCutoff": 3,
22-
"fetchLimit": 100
22+
"fetchLimit": 100,
23+
"editMsgGracePeriod": 300
2324
}
2425
```
2526

@@ -34,7 +35,7 @@ After creating your bot and cloning the repository, the only setup that needs to
3435
| **hexcolor** | String | the color of the embed in hex. if null, this value is generated from an incoming message's channel ID (unique color code per channel). |
3536
| **dateCutoff** | Integer | how old a message can be, in days, and still be tracked by the bot. if you don't want really old messages getting posted, then keep this number low. |
3637
| **fetchLimit** | Integer | how many messages from the starboard channel will be loaded in memory. This lets the script know what messages have already been posted. It's recommended to change this with respect to `dateCutoff` and how big your server is. Anything that isn't tracked has the possibility of getting double posted. |
37-
38+
| **editMsgGracePeriod** | Integer | how long, in seconds, a message can to be edited to update its board post. disables this feature if set to null or 0. |
3839

3940
### Running the Project
4041
Use `npm install` to download dependencies. Finally, you can run the bot with `npm start`. I recommend using pm2 for continuous uptime.

config/settings.json.example

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@
77
"threshold": 15,
88
"hexcolor": "00AE86",
99
"dateCutoff": 3,
10-
"fetchLimit": 100
10+
"fetchLimit": 100,
11+
"editMsgGracePeriod": 300
1112
}

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "starboard",
3-
"version": "1.6.0",
3+
"version": "1.6.2",
44
"description": "discord bot for creating a starboard in a server",
55
"main": "src/index.js",
66
"scripts": {

src/index.js

Lines changed: 138 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ let settings
1515
let db
1616
let guildID = ''
1717
let smugboardID = ''
18+
let postChannel
1819
const messagePosted = {}
1920
let loading = true
2021

@@ -99,11 +100,133 @@ async function loadIntoMemory () {
99100
console.log(`\nLoaded ${Object.keys(messagePosted).length} previous posts in ${settings.reactionEmoji} channel!`)
100101
}
101102

103+
// construct json object for embed fields
104+
async function buildEmbedFields(reaction) {
105+
const msg = reaction.message
106+
107+
// create content data
108+
const data = {
109+
content: msg.content,
110+
contentInfo: '',
111+
avatarURL: msg.author.displayAvatarURL({ dynamic: true }),
112+
// imageURL: '',
113+
imageURLs: [],
114+
footer: `${reaction.count} ${settings.embedEmoji} (${msg.id})`
115+
}
116+
117+
// add msg origin info to content prop
118+
const msgLink = `https://discordapp.com/channels/${msg.guild.id}/${msg.channel.id}/${msg.id}`
119+
const threadTypes = [ChannelType.AnnouncementThread, ChannelType.PublicThread, ChannelType.PrivateThread]
120+
const channelLink = (threadTypes.includes(msg.channel.type)) ? `<#${msg.channel.parent.id}>/<#${msg.channel.id}>` : `<#${msg.channel.id}>`
121+
data.contentInfo += `\n\n→ [original message](${msgLink}) in ${channelLink}`
122+
123+
// resolve reply message
124+
if (msg.reference && msg.reference.messageId) {
125+
await msg.channel.messages.fetch(msg.reference.messageId).then(message => {
126+
// construct reply comment
127+
let replyContent = (!message.content && message.attachments.size) ? message.attachments.first().name : message.content.replace(/\n/g, ' ')
128+
replyContent = (replyContent.length > 300) ? `${replyContent.substring(0, 300)}...` : replyContent
129+
data.content = (msg.content) ? `\n\n${data.content}`: data.content
130+
data.content = `> ${msg.mentions.repliedUser}: ${replyContent}${data.content}`
131+
}).catch(err => {
132+
console.error(`error getting reply msg: ${msg.reference.messageId} (for ${msg.id})\n${err}`)
133+
})
134+
}
135+
136+
// resolve any embeds and images
137+
if (msg.embeds.length) {
138+
const imgs = msg.embeds
139+
.filter(embed => embed.thumbnail || embed.image)
140+
.map(embed => (embed.thumbnail) ? embed.thumbnail.url : embed.image.url)
141+
142+
if (imgs.length) {
143+
// data.imageURL = imgs[0]
144+
data.imageURLs = imgs
145+
146+
// site specific gif fixes
147+
data.imageURLs.forEach((url, i) => {
148+
data.imageURLs[i] = data.imageURLs[i].replace(/(^https:\/\/media.tenor.com\/.*)(AAAAD\/)(.*)(\.png|\.jpg)/, "$1AAAAC/$3.gif")
149+
data.imageURLs[i] = data.imageURLs[i].replace(/(^https:\/\/thumbs.gfycat.com\/.*-)(poster\.jpg)/, "$1size_restricted.gif")
150+
})
151+
// data.imageURL = data.imageURL.replace(/(^https:\/\/media.tenor.com\/.*)(AAAAD\/)(.*)(\.png|\.jpg)/, "$1AAAAC/$3.gif")
152+
// data.imageURL = data.imageURL.replace(/(^https:\/\/thumbs.gfycat.com\/.*-)(poster\.jpg)/, "$1size_restricted.gif")
153+
154+
// twitch clip check
155+
const videoEmbed = msg.embeds.filter(embed => embed.data.type === 'video')[0]
156+
if (videoEmbed && videoEmbed.data.video.url.includes("clips.twitch.tv")) {
157+
data.contentInfo += `\n⬇️ [download clip](${videoEmbed.data.thumbnail.url.replace("-social-preview.jpg", ".mp4")})`
158+
}
159+
}
160+
161+
// message is entirely an embed (bot msg)
162+
if (msg.content === '') {
163+
const embed = msg.embeds[0]
164+
if (embed.description) {
165+
data.content += embed.description
166+
} else if (embed.fields && embed.fields[0].value) {
167+
data.content += embed.fields[0].value
168+
}
169+
}
170+
}
171+
if (msg.attachments.size) {
172+
// data.imageURL = msg.attachments.first().url
173+
msg.attachments.each(attachment => {
174+
data.imageURLs.push(attachment.url)
175+
data.contentInfo += `\n📎 [${attachment.name}](${attachment.url})`
176+
})
177+
}
178+
179+
// max length message
180+
if (data.content.length > MAXLENGTH - data.contentInfo.length)
181+
data.content = `${data.content.substring(0, MAXLENGTH - data.contentInfo.length)}...`
182+
183+
return data
184+
}
185+
186+
// update embed
187+
function editEmbed(reaction, editableMessageID, forceUpdate=false) {
188+
if (reaction.count) console.log(`updating count of message with ID ${editableMessageID}. reaction count: ${reaction.count}`)
189+
postChannel.messages.fetch(editableMessageID).then(async message => {
190+
// rebuild embeds
191+
const origEmbed = message.embeds[0]
192+
if (!origEmbed) throw `original embed could not be fetched`
193+
194+
const messageFooter = (reaction.count) ? `${reaction.count} ${settings.embedEmoji} (${reaction.message.id})` : origEmbed.footer.text
195+
196+
let updatedEmbeds = [
197+
EmbedBuilder.from(origEmbed)
198+
.setFooter({ text: messageFooter, iconURL: null })
199+
]
200+
if (forceUpdate) {
201+
const data = await buildEmbedFields(reaction)
202+
const first_image = (data.imageURLs.length) ? data.imageURLs.shift() : null
203+
updatedEmbeds[0]
204+
.setDescription(data.content + data.contentInfo)
205+
.setURL(first_image)
206+
.setImage(first_image)
207+
data.imageURLs.forEach(url => {
208+
updatedEmbeds.push(new EmbedBuilder().setURL(first_image).setImage(url))
209+
})
210+
} else {
211+
updatedEmbeds = updatedEmbeds.concat(message.embeds.slice(1))
212+
}
213+
214+
message.edit({ embeds: updatedEmbeds }).then(starMessage => {
215+
// if db
216+
if (db)
217+
db.updatePost(starMessage, reaction.message, reaction.count, starMessage.embeds[0].image)
218+
})
219+
220+
}).catch(err => {
221+
console.error(`error updating post: ${editableMessageID}\noriginal message: ${reaction.message.id}\n${err}`)
222+
})
223+
}
224+
102225
// manage the message board on reaction add/remove
103226
async function manageBoard (reaction) {
104227

105228
const msg = reaction.message
106-
const postChannel = client.guilds.cache.get(guildID).channels.cache.get(smugboardID)
229+
// const postChannel = client.guilds.cache.get(guildID).channels.cache.get(smugboardID)
107230

108231
// if message is older than set amount
109232
const dateDiff = (new Date()) - msg.createdAt
@@ -122,107 +245,15 @@ async function manageBoard (reaction) {
122245
const editableMessageID = messagePosted[msg.id]
123246
if (editableMessageID === true) return // message not yet posted (too fast)
124247

125-
console.log(`updating count of message with ID ${editableMessageID}. reaction count: ${reaction.count}`)
126-
const messageFooter = `${reaction.count} ${settings.embedEmoji} (${msg.id})`
127-
postChannel.messages.fetch(editableMessageID).then(message => {
128-
// rebuild embeds
129-
const origEmbed = message.embeds[0]
130-
if (!origEmbed) throw `original embed could not be fetched`
131-
const updatedEmbeds = [
132-
EmbedBuilder.from(origEmbed)
133-
.setFooter({ text: messageFooter, iconURL: null })
134-
]
135-
136-
message.edit({ embeds: updatedEmbeds.concat(message.embeds.slice(1)) }).then(starMessage => {
137-
// if db
138-
if (db)
139-
db.updatePost(starMessage, msg, reaction.count, starMessage.embeds[0].image)
140-
})
141-
142-
}).catch(err => {
143-
console.error(`error updating post: ${editableMessageID}\noriginal message: ${msg.id}\n${err}`)
144-
})
248+
editEmbed(reaction, editableMessageID, forceUpdate=false)
249+
145250
} else {
146251
console.log(`posting message with content ID ${msg.id}. reaction count: ${reaction.count}`)
147252

148253
// add message to ongoing object in memory
149254
messagePosted[msg.id] = true
150255

151-
// create content data
152-
const data = {
153-
content: msg.content,
154-
contentInfo: '',
155-
avatarURL: msg.author.displayAvatarURL({ dynamic: true }),
156-
// imageURL: '',
157-
imageURLs: [],
158-
footer: `${reaction.count} ${settings.embedEmoji} (${msg.id})`
159-
}
160-
161-
// add msg origin info to content prop
162-
const msgLink = `https://discordapp.com/channels/${msg.guild.id}/${msg.channel.id}/${msg.id}`
163-
const threadTypes = [ChannelType.AnnouncementThread, ChannelType.PublicThread, ChannelType.PrivateThread]
164-
const channelLink = (threadTypes.includes(msg.channel.type)) ? `<#${msg.channel.parent.id}>/<#${msg.channel.id}>` : `<#${msg.channel.id}>`
165-
data.contentInfo += `\n\n→ [original message](${msgLink}) in ${channelLink}`
166-
167-
// resolve reply message
168-
if (msg.reference && msg.reference.messageId) {
169-
await msg.channel.messages.fetch(msg.reference.messageId).then(message => {
170-
// construct reply comment
171-
let replyContent = (!message.content && message.attachments.size) ? message.attachments.first().name : message.content.replace(/\n/g, ' ')
172-
replyContent = (replyContent.length > 300) ? `${replyContent.substring(0, 300)}...` : replyContent
173-
data.content = (msg.content) ? `\n\n${data.content}`: data.content
174-
data.content = `> ${msg.mentions.repliedUser}: ${replyContent}${data.content}`
175-
}).catch(err => {
176-
console.error(`error getting reply msg: ${msg.reference.messageId} (for ${msg.id})\n${err}`)
177-
})
178-
}
179-
180-
// resolve any embeds and images
181-
if (msg.embeds.length) {
182-
const imgs = msg.embeds
183-
.filter(embed => embed.thumbnail || embed.image)
184-
.map(embed => (embed.thumbnail) ? embed.thumbnail.url : embed.image.url)
185-
186-
if (imgs.length) {
187-
// data.imageURL = imgs[0]
188-
data.imageURLs = imgs
189-
190-
// site specific gif fixes
191-
data.imageURLs.forEach((url, i) => {
192-
data.imageURLs[i] = data.imageURLs[i].replace(/(^https:\/\/media.tenor.com\/.*)(AAAAD\/)(.*)(\.png|\.jpg)/, "$1AAAAC/$3.gif")
193-
data.imageURLs[i] = data.imageURLs[i].replace(/(^https:\/\/thumbs.gfycat.com\/.*-)(poster\.jpg)/, "$1size_restricted.gif")
194-
})
195-
// data.imageURL = data.imageURL.replace(/(^https:\/\/media.tenor.com\/.*)(AAAAD\/)(.*)(\.png|\.jpg)/, "$1AAAAC/$3.gif")
196-
// data.imageURL = data.imageURL.replace(/(^https:\/\/thumbs.gfycat.com\/.*-)(poster\.jpg)/, "$1size_restricted.gif")
197-
198-
// twitch clip check
199-
const videoEmbed = msg.embeds.filter(embed => embed.data.type === 'video')[0]
200-
if (videoEmbed && videoEmbed.data.video.url.includes("clips.twitch.tv")) {
201-
data.contentInfo += `\n⬇️ [download clip](${videoEmbed.data.thumbnail.url.replace("-social-preview.jpg", ".mp4")})`
202-
}
203-
}
204-
205-
// message is entirely an embed (bot msg)
206-
if (msg.content === '') {
207-
const embed = msg.embeds[0]
208-
if (embed.description) {
209-
data.content += embed.description
210-
} else if (embed.fields && embed.fields[0].value) {
211-
data.content += embed.fields[0].value
212-
}
213-
}
214-
}
215-
if (msg.attachments.size) {
216-
// data.imageURL = msg.attachments.first().url
217-
msg.attachments.each(attachment => {
218-
data.imageURLs.push(attachment.url)
219-
data.contentInfo += `\n📎 [${attachment.name}](${attachment.url})`
220-
})
221-
}
222-
223-
// max length message
224-
if (data.content.length > MAXLENGTH - data.contentInfo.length)
225-
data.content = `${data.content.substring(0, MAXLENGTH - data.contentInfo.length)}...`
256+
const data = await buildEmbedFields(reaction)
226257

227258
// set first image
228259
const first_image = (data.imageURLs.length) ? data.imageURLs.shift() : null
@@ -258,7 +289,7 @@ async function manageBoard (reaction) {
258289

259290
// delete a post
260291
function deletePost (msg) {
261-
const postChannel = client.guilds.cache.get(guildID).channels.cache.get(smugboardID)
292+
// const postChannel = client.guilds.cache.get(guildID).channels.cache.get(smugboardID)
262293
// if posted to channel board before
263294
if (messagePosted[msg.id]) {
264295
const editableMessageID = messagePosted[msg.id]
@@ -279,6 +310,7 @@ client.on('ready', () => {
279310
console.log(`Logged in as ${client.user.username}!`)
280311
guildID = settings.serverID
281312
smugboardID = settings.channelID
313+
postChannel = client.guilds.cache.get(guildID).channels.cache.get(smugboardID)
282314
// fetch existing posts
283315
loadIntoMemory()
284316
})
@@ -335,6 +367,15 @@ client.on('messageDelete', (msg) => {
335367
client.on('messageUpdate', (oldMsg, newMsg) => {
336368
if (db && oldMsg.channel.id === smugboardID && oldMsg.embeds.length && !newMsg.embeds.length)
337369
db.setDeleted(newMsg.id)
370+
else if (settings.editMsgGracePeriod && messagePosted[newMsg.id]) {
371+
const dateDiff = (new Date()) - newMsg.reactions.message.createdTimestamp
372+
const dateCutoff = 1000
373+
console.log(Math.floor(dateDiff / dateCutoff))
374+
if (Math.floor(dateDiff / dateCutoff) <= settings.editMsgGracePeriod)
375+
editEmbed(newMsg.reactions, messagePosted[newMsg.id], forceUpdate=true)
376+
else
377+
console.log(`message older than ${settings.editMsgGracePeriod} seconds was edited, ignoring`)
378+
}
338379
})
339380

340381
setup()

0 commit comments

Comments
 (0)