Skip to content

Instantly share code, notes, and snippets.

@SamJakob
Last active June 7, 2023 16:40
Show Gist options
  • Save SamJakob/8726b80f237f5fc1f9de4186b6dcc6cb to your computer and use it in GitHub Desktop.
Save SamJakob/8726b80f237f5fc1f9de4186b6dcc6cb to your computer and use it in GitHub Desktop.
Terrence (a bot to automatically change Discord guild icons)

Terrence 1.0.0

Changes the guild icons of any guilds the bot is a member of at a defined interval.

Usage

The bot has a customizable command prefix, however the default is t.
The default permission to execute commands is the 'Administrator' permission, however this can be changed.

  • t.update: Allows a user to indicate that an update to the Imgur album is available for the bot. Thus, allowing the cached image list from the album to be updated without restarting the bot.
  • t.change: Allows a user to pre-emptively change the Guild icon to another random image from the image list.

Setup

  1. Download this Gist as a ZIP archive and run npm install (or run npm init and manually install the dependencies below.)
  2. Create an application with a Discord Bot on the Discord developers page.
  3. Create an account on imgur.com and Register an Application (The option 'Anonymous usage without user authorization' will work just fine and 'Authorization Callback URL' may be whatever you wish.)
  4. Set DISCORD_API_CLIENT_TOKEN to your Discord Bot's token. (This can be found on the 'Bot' page of your application.)
  5. Set IMGUR_API_CLIENT_ID to the Client ID of your Imgur Application.
  6. Set IMGUR_ALBUM_URL to the URL of the album you want to fetch images from (without a trailing slash - e.g. https://imgur.com/a/Okb6h4E)

Dependencies

  • axios: npm install axios
  • discord.js: npm install discord.js

License (MIT)

Copyright 2020 SamJakob
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// ============================================================================ //
/*
* Terrence v1.0.0
* Written by SamJakob.
*
* Changes the guild icons of any guilds the bot is a member of every N seconds
* as defined by [config.updateImageTimeIntervalSeconds] below.
*/
// ============================================================================ //
// Add permissions if necessary from:
// https://discord.com/developers/docs/topics/permissions#permissions-bitwise-permission-flags
const PERMISSION_ADMINISTRATOR = 0x00000008;
const PERMISSION_MANAGE_GUILD = 0x00000020;
const PERMISSION_MANAGE_MESSAGES = 0x00002000;
// If this wasn't designed to be copy-pasted, I'd use a .env file for this:
const secrets = {
DISCORD_API_CLIENT_TOKEN: "",
IMGUR_API_CLIENT_ID: "",
// Do not include a trailing slash.
IMGUR_ALBUM_URL: ""
};
const config = {
// The time interval at which the Guild icon should be updated.
// (default: 3600 = 1 hour)
updateImageTimeIntervalSeconds: 3600,
// Whether or not the bot should show a 'no permission' message if an
// unauthorized user attempts to run a command.
showNoPermissionMessage: true,
// The prefix that must be prepended to any commands a user wishes to
// run. When checking commands, this is simply concatenated directly
// with the command name, so you must include any symbols you may wish
// to have seperating the command prefix from the command itself.
// e.g. setting this to 't.' would cause 'myCommand' to be t.myCommand
commandPrefix: 't.',
// Requires that any users wishing to execute commands have the below
// permission.
// Set to 0 to disable this check, and to require multiple permissions,
// simply bitwise XOR the permissions.
// e.g. for both Administrator and Manage Guild permissions, one would use:
// commandRequiresPermission: PERMISSION_ADMINISTRATOR | PERMISSION_MANAGE_GUILD
commandRequiresPermission: PERMISSION_ADMINISTRATOR,
};
// ============================================================================ //
// Method to override the Array object's prototype to include a .last method.
//
// Doing something like this isn't highly recommended, but it's quick and pretty
// clean and ought to work without issue for something like this.
if (!Array.prototype.last) {
Array.prototype.last = function() {
return this[this.length - 1];
};
};
// Method to override the Array object's prototype to include a .random method.
//
// See .last's comment for the JavaScript community's view on doing s--- like this.
if (!Array.prototype.random) {
Array.prototype.random = function() {
return this[Math.floor(Math.random() * this.length)];
}
}
// ============================================================================ //
// Naïve approach to extract the album hash from the album URL.
const ALBUM_HASH = secrets.IMGUR_ALBUM_URL.split("/").last();
/**
* Returns the link that can be used to invite the bot to a Discord server with the
* specified permission level.
*
* @param {string} clientId The Discord bot's client ID.
* @param {number} permissionLevel The desired permission level of the bot when invited to the server.
*/
function generateInviteUrl(clientId, permissionLevel) {
return `https://discord.com/oauth2/authorize?client_id=${clientId}&scope=bot&permissions=${permissionLevel}`;
}
/**
* Fetches the latest list of images from the Imgur API.
*
* @param {string} albumHash The hash of the imgur album to acquire the list of images from.
* @returns {Promise<string[]|null>} Either an array of direct links to the images in the album or null if the request failed.
*/
async function acquireImageList(albumHash) {
console.log("Acquiring image list...");
try {
// Fetch the list of items in the album from Imgur's API.
const imgurData = (await axios.get(`https://api.imgur.com/3/album/${ALBUM_HASH}/images`, {
headers: {
Authorization: `Client-ID ${secrets.IMGUR_API_CLIENT_ID}`
}
})).data;
// and map them such that they are only the direct link to each image.
return imgurData.data.map((item) => {
return item.link;
});
} catch(ex) {
console.error("Failed to acquire image list.");
console.error(ex);
return null;
}
}
/**
* Updates the image of ALL the guilds the bot is a member of to be the specified imageUrl.
*
* @param {Discord.Client} client The Discord client that is a member of the guilds that should be updated.
* @param {string} imageUrl The URL of the image that should be made the icon of each guild.
*/
async function applyImage(client, imageUrl) {
for (let cachedGuild of client.guilds.cache) {
cachedCurrentImage = imageUrl;
let guild = await client.guilds.resolve(cachedGuild[0]);
await guild.setIcon(imageUrl);
}
}
/**
* Randomly selects a new image, **OTHER** than the one that is currently in place
* and applies it as the new guild icon for ALL the guilds the bot is a member of.
*
* @param {string} client The Discord client that is a member of the guilds that should be updated.
*/
async function applyRandomNewImage(client) {
// Ensure that the new randomly selected image is not the same as the old.
let newImage = cachedCurrentImage;
do {
newImage = imageList.random();
} while (newImage == cachedCurrentImage);
// Then, apply the new image.
await applyImage(client, newImage);
}
/**
* If config.showNoPermissionMessage is enabled, it will send a response to the
* provided message indicating that the user has no permission to perform a given
* operation.
*
* Otherwise, it will do nothing.
*
* @param {string} message The message to respond to with the no permission message.
*/
async function sendNoPermissionResponse(message) {
if (config.showNoPermissionMessage) {
let hasManageMessagePermission = message.guild.member(client.user).permissions.has(PERMISSION_MANAGE_MESSAGES);
const manageMessageNotice = hasManageMessagePermission ? "\n_(This will be deleted after 10 seconds)_" : "";
let response = await message.reply(`❌ You do not have permission to run \`${message.content}\`!${manageMessageNotice}`);
if (hasManageMessagePermission) setTimeout(async () => {
await response.delete();
}, 10000);
}
}
// ============================================================================ //
const axios = require('axios');
const Discord = require('discord.js');
const client = new Discord.Client();
// The list of images from which to select a guild icon.
let cachedCurrentImage;
let imageList;
client.on('message', async (message) => {
// If the bot has permission to manage messages, it will delete command-related messages after use
// to keep the chat logs clean.
let hasManageMessagePermission = message.guild.member(client.user).permissions.has(PERMISSION_MANAGE_MESSAGES);
const manageMessageNotice = hasManageMessagePermission ? "\n_(This message will be deleted after 10 seconds.)_" : "";
/**
* Command: update
*
* Allows a user to indicate that an update to the Imgur album is available for the bot.
* Thus, allowing the cached image list from the album to be updated without restarting the bot.
*/
if (message.content == `${config.commandPrefix}update`) {
if (message.guild.member(message.author).permissions.has(config.commandRequiresPermission)) {
// Indicate that the command was understood, and if the bot has permission to manage messages
// remove the message with the initial command.
let reply = await message.reply("Updating image list, please wait...");
if (hasManageMessagePermission) await message.delete();
// Acquire the new image list and reply with the status of the operation.
if (imageList = await acquireImageList(ALBUM_HASH)) {
await reply.edit(`The image list has been updated successfully.${manageMessageNotice}`);
} else {
await reply.edit(`An error occurred whilst updating the image list.${manageMessageNotice}`);
}
// Finally, delete the status response.
if (hasManageMessagePermission) setTimeout(async () => {
await reply.delete();
}, 10000);
} else sendNoPermissionResponse(message);
}
/**
* Command: change
*
* Allows a user to pre-emptively change the Guild icon to another random image from
* the image list.
*/
if (message.content == `${config.commandPrefix}change`) {
if (message.guild.member(message.author).permissions.has(config.commandRequiresPermission)) {
// Indicate that the command was understood, and if the bot has permission to manage messages
// remove the message with the initial command.
let reply = await message.reply("Selecting a new image, please wait...");
if (hasManageMessagePermission) await message.delete();
try {
// Randomly apply a new image but make sure it is not the same
// image as the current.
applyRandomNewImage(client);
await reply.edit(`The server icon has been updated successfully.${manageMessageNotice}`);
} catch (ex) {
await reply.edit(`Failed to update the server icon.${manageMessageNotice}`);
}
// Finally, delete the status response.
if (hasManageMessagePermission) setTimeout(async () => {
await reply.delete();
}, 10000);
} else sendNoPermissionResponse(message);
}
});
(async () => {
// Attempt to acquire the initial list of guild icons, or - on failure - simply exit.
if (!(imageList = await acquireImageList(ALBUM_HASH))) {
process.exit(1);
}
console.log(`Found ${imageList.length} images.`);
console.log("");
// Log in to Discord's API.
console.log("Logging in...");
await client.login(secrets.DISCORD_API_CLIENT_TOKEN);
console.log("");
console.log("===============================================");
console.log(`Client User:\t\t${client.user.tag}`);
console.log(`Invite URL:\t\t${ generateInviteUrl(client.user.id, PERMISSION_MANAGE_GUILD | PERMISSION_MANAGE_MESSAGES) }`);
console.log("===============================================");
console.log("");
console.log("The 'Manage Guild' permission is mandatory, however 'Manage Messages' is optional and is simply used to delete command-related messages.");
console.log("");
console.log("Applying an initial image...");
await applyImage(client, imageList.random());
console.log("Applied initial image.");
console.log("");
console.log("Setting up interval...");
setInterval(() => {
// Randomly apply a new image but make sure it is not the same
// image as the current.
applyRandomNewImage(client);
}, config.updateImageTimeIntervalSeconds * 1000);
console.log(`The guilds' icon will be updated every ${config.updateImageTimeIntervalSeconds} seconds starting from ${new Date()}.`);
})();
{
"name": "terrence",
"version": "1.0.0",
"description": "Changes the guild icons of any guilds the bot is a member of at a defined interval.",
"private": true,
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "SamJakob",
"license": "MIT",
"dependencies": {
"axios": "^0.19.2",
"discord.js": "^12.2.0"
}
}
@MysticArt-coder
Copy link

how do download gist?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment