diff --git a/Xiao.js b/Xiao.js index 2f5206e5..abd79d43 100644 --- a/Xiao.js +++ b/Xiao.js @@ -126,6 +126,9 @@ client.on('clientReady', async () => { client.user.setActivity(text, { type: activity.type }); }, 60000); + // Interval to check for holidays and change the avatar if needed + client.avatarChanger.setInterval(); + // Set up disabled commands try { const disabled = await client.redis.db.hgetall('disabled'); diff --git a/assets/images/Xiao.png b/assets/images/Xiao.png new file mode 100644 index 00000000..82fc1014 Binary files /dev/null and b/assets/images/Xiao.png differ diff --git a/assets/json/holiday-list.json b/assets/json/holiday-list.json new file mode 100644 index 00000000..e01316df --- /dev/null +++ b/assets/json/holiday-list.json @@ -0,0 +1,56 @@ +[ + { + "name": "New Year's Day", + "hat": "birthday", + "month": 1, + "day": 1 + }, + { + "name": "Xiao's Birthday", + "hat": "birthday", + "month": 2, + "day": 6 + }, + { + "name": "Pokémon Day", + "hat": "ash", + "month": 2, + "day": 27 + }, + { + "name": "Christmas", + "hat": "christmas", + "month": 12, + "day": 25 + }, + { + "name": "Halloween", + "hat": ["witch", "pirate", "devil"], + "month": 10, + "day": 31 + }, + { + "name": "Talk Like a Pirate Day", + "hat": "pirate", + "month": 9, + "day": 19 + }, + { + "name": "April Fools Day", + "hat": "disguise", + "month": 4, + "day": 1 + }, + { + "name": "St. Patrick's Day", + "hat": "leprechaun", + "month": 3, + "day": 17 + }, + { + "name": "test", + "hat": "dunce", + "month": 2, + "day": 17 + } +] diff --git a/package.json b/package.json index 8c87e5d9..6562a3bc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "xiao", - "version": "159.3.5", + "version": "159.4.0", "description": "Your personal server companion.", "main": "Xiao.js", "scripts": { diff --git a/structures/AvatarChanger.js b/structures/AvatarChanger.js new file mode 100644 index 00000000..a1b60553 --- /dev/null +++ b/structures/AvatarChanger.js @@ -0,0 +1,90 @@ +const { createCanvas, loadImage } = require('@napi-rs/canvas'); +const path = require('path'); +const holidayList = require('../assets/json/holiday-list'); + +module.exports = class AvatarChanger { + constructor(client) { + Object.defineProperty(this, 'client', { value: client }); + + this.isWearingHat = false; + } + + async editAvatar(hat) { + const base = await loadImage(path.join(__dirname, '..', 'assets', 'images', 'Xiao.png')); + const hatImg = await loadImage(path.join(__dirname, '..', 'assets', 'images', 'hat', `${hat}.png`)); + const canvas = createCanvas(base.width, base.height); + const ctx = canvas.getContext('2d'); + ctx.drawImage(base, 0, 0); + ctx.drawImage(hatImg, 0, 0, base.width, base.height); + return canvas.toBuffer('image/png'); + } + + async setAvatar(hat) { + if (!hat) { + this.isWearingHat = false; + await this.client.user.setAvatar(path.join(__dirname, '..', 'assets', 'images', 'Xiao.png')); + return; + } + this.isWearingHat = true; + const image = await this.editAvatar(hat); + await this.client.user.setAvatar(image); + return; + } + + setInterval() { + const now = new Date(); + const midnight = new Date(Date.UTC( + now.getUTCFullYear(), + now.getUTCMonth(), + now.getUTCDate() + 1, + 0 ,0, 0, 0 + )); + const msUntilNext = midnight - now; + setTimeout(() => { + const today = new Date(); + const holiday = this.isHoliday(today); + if (holiday) { + let hat = holiday.hat; + if (Array.isArray(hat)) hat = hat[Math.floor(Math.random() * hat.length)]; + this.setAvatar(hat) + .then(() => this.client.logger.info(`[AVATAR] Updated avatar to ${hat}!`)) + .catch(err => this.client.logger.error(`[AVATAR] Failed to update avatar.\n${err.stack}`)); + } else if (this.isWearingHat) { + this.setAvatar() + .then(() => this.client.logger.info('[AVATAR] Reset avatar to default.')) + .catch(err => this.client.logger.error(`[AVATAR] Failed to update avatar.\n${err.stack}`)); + } + this.setInterval(); + }, msUntilNext); + } + + isHoliday(day) { + for (const holiday of holidayList) { + if (day.getUTCDate() === holiday.day && day.getUTCMonth() === holiday.month - 1) return holiday; + } + if (this.isThanksgiving(day)) { + return { + hat: 'pilgrim' + }; + } + return false; + } + + isThanksgiving(day) { + const thanksgiving = this.getThanksgiving(day.getUTCFullYear()); + return ( + day.getUTCFullYear() === thanksgiving.getUTCFullYear() && + day.getUTCMonth() === thanksgiving.getUTCMonth() && + day.getUTCDate() === thanksgiving.getUTCDate() + ); + } + + getThanksgiving(year) { + const date = new Date(Date.UTC(year, 10, 1)); + const day = date.getUTCDay(); + const offset = ((4 - day) + 7) % 7; + const firstThursday = 1 + offset; + const thanksgiving = firstThursday + 21; + return new Date(Date.UTC(year, 10, thanksgiving)); + } +}; diff --git a/structures/Client.js b/structures/Client.js index fb8458a6..dbfef23f 100644 --- a/structures/Client.js +++ b/structures/Client.js @@ -9,6 +9,7 @@ const FontManager = require('./fonts/FontManager'); const PhoneManager = require('./phone/PhoneManager'); const TimerManager = require('./remind/TimerManager'); const PokemonStore = require('./pokemon/PokemonStore'); +const AvatarChanger = require('./AvatarChanger'); const activities = require('./Activity'); const { REPORT_CHANNEL_ID, JOIN_LEAVE_CHANNEL_ID } = process.env; @@ -33,6 +34,7 @@ module.exports = class XiaoClient extends CommandClient { this.phone = new PhoneManager(this); this.tensorflow = new Tensorflow(this); this.activities = activities; + this.avatarChanger = new AvatarChanger(this); this.adultSiteList = null; }