Skip to content

Commit

Permalink
v1.6.2: allow message edits, code restructure, and bug fixes (#33)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
naeruru and Mazmol committed Jun 8, 2023
1 parent 33fac0c commit f80df00
Show file tree
Hide file tree
Showing 7 changed files with 149 additions and 106 deletions.
2 changes: 1 addition & 1 deletion LICENSE.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2018 - present @Rushnett
Copyright (c) 2018 - present @naeruru

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
6 changes: 3 additions & 3 deletions PRIVACYPOLICY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Smugboard Privacy Policy
Last updated May 5th, 2021
Last updated June 8th, 2023

## Introduction
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").
Expand Down Expand Up @@ -37,8 +37,8 @@ Your information will never be sold to advertisers or third parties for any reas


## Options regarding your information
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").
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").


## Contact
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).
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).
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ After creating your bot and cloning the repository, the only setup that needs to
"threshold": 15,
"hexcolor": "00AE86",
"dateCutoff": 3,
"fetchLimit": 100
"fetchLimit": 100,
"editMsgGracePeriod": 300
}
```

Expand All @@ -34,7 +35,7 @@ After creating your bot and cloning the repository, the only setup that needs to
| **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). |
| **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. |
| **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. |

| **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. |

### Running the Project
Use `npm install` to download dependencies. Finally, you can run the bot with `npm start`. I recommend using pm2 for continuous uptime.
Expand Down
3 changes: 2 additions & 1 deletion config/settings.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
"threshold": 15,
"hexcolor": "00AE86",
"dateCutoff": 3,
"fetchLimit": 100
"fetchLimit": 100,
"editMsgGracePeriod": 300
}
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "starboard",
"version": "1.6.0",
"version": "1.6.2",
"description": "discord bot for creating a starboard in a server",
"main": "src/index.js",
"scripts": {
Expand Down
235 changes: 138 additions & 97 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ let settings
let db
let guildID = ''
let smugboardID = ''
let postChannel
const messagePosted = {}
let loading = true

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

// construct json object for embed fields
async function buildEmbedFields(reaction) {
const msg = reaction.message

// create content data
const data = {
content: msg.content,
contentInfo: '',
avatarURL: msg.author.displayAvatarURL({ dynamic: true }),
// imageURL: '',
imageURLs: [],
footer: `${reaction.count} ${settings.embedEmoji} (${msg.id})`
}

// add msg origin info to content prop
const msgLink = `https://discordapp.com/channels/${msg.guild.id}/${msg.channel.id}/${msg.id}`
const threadTypes = [ChannelType.AnnouncementThread, ChannelType.PublicThread, ChannelType.PrivateThread]
const channelLink = (threadTypes.includes(msg.channel.type)) ? `<#${msg.channel.parent.id}>/<#${msg.channel.id}>` : `<#${msg.channel.id}>`
data.contentInfo += `\n\n→ [original message](${msgLink}) in ${channelLink}`

// resolve reply message
if (msg.reference && msg.reference.messageId) {
await msg.channel.messages.fetch(msg.reference.messageId).then(message => {
// construct reply comment
let replyContent = (!message.content && message.attachments.size) ? message.attachments.first().name : message.content.replace(/\n/g, ' ')
replyContent = (replyContent.length > 300) ? `${replyContent.substring(0, 300)}...` : replyContent
data.content = (msg.content) ? `\n\n${data.content}`: data.content
data.content = `> ${msg.mentions.repliedUser}: ${replyContent}${data.content}`
}).catch(err => {
console.error(`error getting reply msg: ${msg.reference.messageId} (for ${msg.id})\n${err}`)
})
}

// resolve any embeds and images
if (msg.embeds.length) {
const imgs = msg.embeds
.filter(embed => embed.thumbnail || embed.image)
.map(embed => (embed.thumbnail) ? embed.thumbnail.url : embed.image.url)

if (imgs.length) {
// data.imageURL = imgs[0]
data.imageURLs = imgs

// site specific gif fixes
data.imageURLs.forEach((url, i) => {
data.imageURLs[i] = data.imageURLs[i].replace(/(^https:\/\/media.tenor.com\/.*)(AAAAD\/)(.*)(\.png|\.jpg)/, "$1AAAAC/$3.gif")
data.imageURLs[i] = data.imageURLs[i].replace(/(^https:\/\/thumbs.gfycat.com\/.*-)(poster\.jpg)/, "$1size_restricted.gif")
})
// data.imageURL = data.imageURL.replace(/(^https:\/\/media.tenor.com\/.*)(AAAAD\/)(.*)(\.png|\.jpg)/, "$1AAAAC/$3.gif")
// data.imageURL = data.imageURL.replace(/(^https:\/\/thumbs.gfycat.com\/.*-)(poster\.jpg)/, "$1size_restricted.gif")

// twitch clip check
const videoEmbed = msg.embeds.filter(embed => embed.data.type === 'video')[0]
if (videoEmbed && videoEmbed.data.video.url.includes("clips.twitch.tv")) {
data.contentInfo += `\n⬇️ [download clip](${videoEmbed.data.thumbnail.url.replace("-social-preview.jpg", ".mp4")})`
}
}

// message is entirely an embed (bot msg)
if (msg.content === '') {
const embed = msg.embeds[0]
if (embed.description) {
data.content += embed.description
} else if (embed.fields && embed.fields[0].value) {
data.content += embed.fields[0].value
}
}
}
if (msg.attachments.size) {
// data.imageURL = msg.attachments.first().url
msg.attachments.each(attachment => {
data.imageURLs.push(attachment.url)
data.contentInfo += `\n📎 [${attachment.name}](${attachment.url})`
})
}

// max length message
if (data.content.length > MAXLENGTH - data.contentInfo.length)
data.content = `${data.content.substring(0, MAXLENGTH - data.contentInfo.length)}...`

return data
}

// update embed
function editEmbed(reaction, editableMessageID, forceUpdate=false) {
if (reaction.count) console.log(`updating count of message with ID ${editableMessageID}. reaction count: ${reaction.count}`)
postChannel.messages.fetch(editableMessageID).then(async message => {
// rebuild embeds
const origEmbed = message.embeds[0]
if (!origEmbed) throw `original embed could not be fetched`

const messageFooter = (reaction.count) ? `${reaction.count} ${settings.embedEmoji} (${reaction.message.id})` : origEmbed.footer.text

let updatedEmbeds = [
EmbedBuilder.from(origEmbed)
.setFooter({ text: messageFooter, iconURL: null })
]
if (forceUpdate) {
const data = await buildEmbedFields(reaction)
const first_image = (data.imageURLs.length) ? data.imageURLs.shift() : null
updatedEmbeds[0]
.setDescription(data.content + data.contentInfo)
.setURL(first_image)
.setImage(first_image)
data.imageURLs.forEach(url => {
updatedEmbeds.push(new EmbedBuilder().setURL(first_image).setImage(url))
})
} else {
updatedEmbeds = updatedEmbeds.concat(message.embeds.slice(1))
}

message.edit({ embeds: updatedEmbeds }).then(starMessage => {
// if db
if (db)
db.updatePost(starMessage, reaction.message, reaction.count, starMessage.embeds[0].image)
})

}).catch(err => {
console.error(`error updating post: ${editableMessageID}\noriginal message: ${reaction.message.id}\n${err}`)
})
}

// manage the message board on reaction add/remove
async function manageBoard (reaction) {

const msg = reaction.message
const postChannel = client.guilds.cache.get(guildID).channels.cache.get(smugboardID)
// const postChannel = client.guilds.cache.get(guildID).channels.cache.get(smugboardID)

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

console.log(`updating count of message with ID ${editableMessageID}. reaction count: ${reaction.count}`)
const messageFooter = `${reaction.count} ${settings.embedEmoji} (${msg.id})`
postChannel.messages.fetch(editableMessageID).then(message => {
// rebuild embeds
const origEmbed = message.embeds[0]
if (!origEmbed) throw `original embed could not be fetched`
const updatedEmbeds = [
EmbedBuilder.from(origEmbed)
.setFooter({ text: messageFooter, iconURL: null })
]

message.edit({ embeds: updatedEmbeds.concat(message.embeds.slice(1)) }).then(starMessage => {
// if db
if (db)
db.updatePost(starMessage, msg, reaction.count, starMessage.embeds[0].image)
})

}).catch(err => {
console.error(`error updating post: ${editableMessageID}\noriginal message: ${msg.id}\n${err}`)
})
editEmbed(reaction, editableMessageID, forceUpdate=false)

} else {
console.log(`posting message with content ID ${msg.id}. reaction count: ${reaction.count}`)

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

// create content data
const data = {
content: msg.content,
contentInfo: '',
avatarURL: msg.author.displayAvatarURL({ dynamic: true }),
// imageURL: '',
imageURLs: [],
footer: `${reaction.count} ${settings.embedEmoji} (${msg.id})`
}

// add msg origin info to content prop
const msgLink = `https://discordapp.com/channels/${msg.guild.id}/${msg.channel.id}/${msg.id}`
const threadTypes = [ChannelType.AnnouncementThread, ChannelType.PublicThread, ChannelType.PrivateThread]
const channelLink = (threadTypes.includes(msg.channel.type)) ? `<#${msg.channel.parent.id}>/<#${msg.channel.id}>` : `<#${msg.channel.id}>`
data.contentInfo += `\n\n→ [original message](${msgLink}) in ${channelLink}`

// resolve reply message
if (msg.reference && msg.reference.messageId) {
await msg.channel.messages.fetch(msg.reference.messageId).then(message => {
// construct reply comment
let replyContent = (!message.content && message.attachments.size) ? message.attachments.first().name : message.content.replace(/\n/g, ' ')
replyContent = (replyContent.length > 300) ? `${replyContent.substring(0, 300)}...` : replyContent
data.content = (msg.content) ? `\n\n${data.content}`: data.content
data.content = `> ${msg.mentions.repliedUser}: ${replyContent}${data.content}`
}).catch(err => {
console.error(`error getting reply msg: ${msg.reference.messageId} (for ${msg.id})\n${err}`)
})
}

// resolve any embeds and images
if (msg.embeds.length) {
const imgs = msg.embeds
.filter(embed => embed.thumbnail || embed.image)
.map(embed => (embed.thumbnail) ? embed.thumbnail.url : embed.image.url)

if (imgs.length) {
// data.imageURL = imgs[0]
data.imageURLs = imgs

// site specific gif fixes
data.imageURLs.forEach((url, i) => {
data.imageURLs[i] = data.imageURLs[i].replace(/(^https:\/\/media.tenor.com\/.*)(AAAAD\/)(.*)(\.png|\.jpg)/, "$1AAAAC/$3.gif")
data.imageURLs[i] = data.imageURLs[i].replace(/(^https:\/\/thumbs.gfycat.com\/.*-)(poster\.jpg)/, "$1size_restricted.gif")
})
// data.imageURL = data.imageURL.replace(/(^https:\/\/media.tenor.com\/.*)(AAAAD\/)(.*)(\.png|\.jpg)/, "$1AAAAC/$3.gif")
// data.imageURL = data.imageURL.replace(/(^https:\/\/thumbs.gfycat.com\/.*-)(poster\.jpg)/, "$1size_restricted.gif")

// twitch clip check
const videoEmbed = msg.embeds.filter(embed => embed.data.type === 'video')[0]
if (videoEmbed && videoEmbed.data.video.url.includes("clips.twitch.tv")) {
data.contentInfo += `\n⬇️ [download clip](${videoEmbed.data.thumbnail.url.replace("-social-preview.jpg", ".mp4")})`
}
}

// message is entirely an embed (bot msg)
if (msg.content === '') {
const embed = msg.embeds[0]
if (embed.description) {
data.content += embed.description
} else if (embed.fields && embed.fields[0].value) {
data.content += embed.fields[0].value
}
}
}
if (msg.attachments.size) {
// data.imageURL = msg.attachments.first().url
msg.attachments.each(attachment => {
data.imageURLs.push(attachment.url)
data.contentInfo += `\n📎 [${attachment.name}](${attachment.url})`
})
}

// max length message
if (data.content.length > MAXLENGTH - data.contentInfo.length)
data.content = `${data.content.substring(0, MAXLENGTH - data.contentInfo.length)}...`
const data = await buildEmbedFields(reaction)

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

// delete a post
function deletePost (msg) {
const postChannel = client.guilds.cache.get(guildID).channels.cache.get(smugboardID)
// const postChannel = client.guilds.cache.get(guildID).channels.cache.get(smugboardID)
// if posted to channel board before
if (messagePosted[msg.id]) {
const editableMessageID = messagePosted[msg.id]
Expand All @@ -279,6 +310,7 @@ client.on('ready', () => {
console.log(`Logged in as ${client.user.username}!`)
guildID = settings.serverID
smugboardID = settings.channelID
postChannel = client.guilds.cache.get(guildID).channels.cache.get(smugboardID)
// fetch existing posts
loadIntoMemory()
})
Expand Down Expand Up @@ -335,6 +367,15 @@ client.on('messageDelete', (msg) => {
client.on('messageUpdate', (oldMsg, newMsg) => {
if (db && oldMsg.channel.id === smugboardID && oldMsg.embeds.length && !newMsg.embeds.length)
db.setDeleted(newMsg.id)
else if (settings.editMsgGracePeriod && messagePosted[newMsg.id]) {
const dateDiff = (new Date()) - newMsg.reactions.message.createdTimestamp
const dateCutoff = 1000
console.log(Math.floor(dateDiff / dateCutoff))
if (Math.floor(dateDiff / dateCutoff) <= settings.editMsgGracePeriod)
editEmbed(newMsg.reactions, messagePosted[newMsg.id], forceUpdate=true)
else
console.log(`message older than ${settings.editMsgGracePeriod} seconds was edited, ignoring`)
}
})

setup()

0 comments on commit f80df00

Please sign in to comment.