Skip to content

Add commands for upper staff to add custom avatars #5515

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 2 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions server/chat-plugins/customavatars.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
//Original plugin from https://github.com/CreaturePhil/Showdown-Boilerplate/blob/master/chat-plugins/customavatar.js.
//Credits to CreaturePhil and the other listed contributors.
//updated for the main server by Maxalexanderpi, with help from Hoeenhero.
'use strict';

/** @type {typeof import('../../lib/fs').FS} */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is in the wrong place, it should be above the importing of 'FS'.

const FS = require(/** @type {any} */('../../.lib-dist/fs')).FS;

const path = require('path');
const fs = require('fs');

/* eslint no-restricted-modules: [0] */
const AVATAR_PATH = 'config/avatars/';

const VALID_EXTENSIONS = ['.png'];

const https = require('https');

function downloadImage(image_url, name, extension) {
let req = https.get(image_url, res => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably be returning a promise

res.setEncoding('binary');
res.on('response', response => {
if (response.statusCode !== 200) return;
let type = response.headers['content-type'].split('/');
if (type[0] !== 'image') return;

response.pipe(FS(AVATAR_PATH + name + extension).createWriteStream());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all the string concatenations in this file can be template strings instead

});
});
req.on('error', e => {
console.error(e);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure this is the correct error handling mechanism

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be better to throw the error to crashlogger in hindsight.

I need to review the plugin as a whole when I have time, haven't done much other than offer advice on a few issues.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error handling and logging seems to be quite badly implemented.

});
req.end();
}
function loadCustomAvatars() {
fs.readdir(AVATAR_PATH, (err, files) => {
if (err) console.log("Error loading custom avatars: " + err);
if (!files) files = [];
files
.filter(file => VALID_EXTENSIONS.includes(path.extname(file)))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably blacklist __proto__ and constructor

.forEach(file => {
let name = path.basename(file, path.extname(file));
Config.customavatars[name] = file;
});
});
}

loadCustomAvatars();

exports.commands = {
customavatar: {
add(target, room, user) {
if (!this.can('avatar')) return false;
let parts = target.split(',').map(param => param.trim());
if (parts.length < 2) return this.parse('/help customavatar');

let name = toID(parts[0]);
let avatarUrl = parts[1];
if (!/^https?:\/\//i.test(avatarUrl)) avatarUrl = 'http://' + avatarUrl;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

URLs don't necessarily end in the extension of the actual file they point at

let ext = path.extname(avatarUrl);

if (!VALID_EXTENSIONS.includes(ext)) {
return this.errorReply("Image url must end in a .png extension.");
}

Config.customavatars[name] = name + ext;

downloadImage(avatarUrl, name, ext);
this.sendReply("|raw|" + name + "'s avatar was successfully set. Avatar:<br /><img src='" + avatarUrl + "' width='80' height='80'>");
Monitor.adminlog(name + "'s avatar was successfully set by " + user.name + ".");
if (Users(name)) Users(name).popup("|html|" + (user.name, 'Upper staff') + " have set your custom avatar.<br /><center><img src='" + avatarUrl + "' width='80' height='80'></center><br /> Refresh your page if you don't see it.");
},

remove(target, room, user) {
if (!this.can('avatar')) return false;

let userid = toID(target);
let image = Config.customavatars[userid];

if (!image) return this.errorReply(target + " does not have a custom avatar.");

delete Config.customavatars[userid];
fs.unlink(AVATAR_PATH + image, err => {
if (err && err.code === 'ENOENT') {
this.errorReply(target + "'s avatar does not exist.");
} else if (err) {
console.error(err);
}

if (Users(userid)) Users(userid).popup("|html|" + (user.name, 'Upper staff') + " have removed your custom avatar.");
this.sendReply(target + "'s avatar has been successfully removed.");
Monitor.adminlog(target + "'s avatar has been successfully removed by " + user.name + ".");
});
},

customavatarhelp: 'help',
help(target, room, user) {
this.parse('/help customavatar');
},
},

customavatarhelp: [
"Commands for /customavatar are:",
"/customavatar add [username], [image link] - Set a user's custom avatar. Requires: & ~",
"/customavatar remove [username] - Delete a user's custom avatar. Requires: & ~"],
};