├── .gitignore ├── LICENSE ├── README.md ├── example-config.json ├── package.json ├── src ├── commands │ ├── debug │ │ ├── Base36ToInt.ts │ │ ├── EmDash.ts │ │ ├── IntToBase36.ts │ │ ├── Ping.ts │ │ └── Stats.ts │ ├── dev │ │ ├── AddBits.ts │ │ ├── ButtonTest.ts │ │ ├── CheckCardImages.ts │ │ ├── CreateBadge.ts │ │ ├── DevGiveItem.ts │ │ ├── Eval.ts │ │ ├── ForceDrop.ts │ │ ├── ForceFrame.ts │ │ ├── GetLevel.ts │ │ ├── RecacheCards.ts │ │ ├── RefreshStickers.ts │ │ ├── ReloadCommand.ts │ │ ├── RemoveBits.ts │ │ ├── ResetClaimTimer.ts │ │ ├── ResetDropTimer.ts │ │ ├── ScanCards.ts │ │ ├── SetCreationDate.ts │ │ ├── SetFlag.ts │ │ ├── SetPatron.ts │ │ ├── ShowFlags.ts │ │ ├── SimulateCards.ts │ │ ├── SimulateVote.ts │ │ ├── disable │ │ │ ├── DisableCommand.ts │ │ │ └── SetDisableMessage.ts │ │ ├── maintenance │ │ │ ├── SetMaintenanceHeader.ts │ │ │ ├── SetMaintenanceMessage.ts │ │ │ └── SetMaintenanceMode.ts │ │ └── poll │ │ │ ├── ActivatePoll.ts │ │ │ ├── CreatePoll.ts │ │ │ └── DeactivatePoll.ts │ ├── game │ │ ├── album │ │ │ ├── AddCardToAlbum.ts │ │ │ ├── ClearAlbum.ts │ │ │ ├── RemoveCardFromAlbum.ts │ │ │ ├── RenameAlbum.ts │ │ │ ├── ViewAlbum.ts │ │ │ └── ViewAlbums.ts │ │ ├── badges │ │ │ ├── DeleteBadge.ts │ │ │ ├── GrantBadge.ts │ │ │ ├── ShowBadges.ts │ │ │ ├── UngrantBadge.ts │ │ │ └── ViewBadge.ts │ │ ├── card │ │ │ ├── Burn.ts │ │ │ ├── CardInfo.ts │ │ │ ├── CardLookup.ts │ │ │ ├── DyeCard.ts │ │ │ ├── GiftCard.ts │ │ │ ├── PreviewSticker.ts │ │ │ ├── ResetFrame.ts │ │ │ ├── UpgradeCard.ts │ │ │ └── ViewUserCard.ts │ │ ├── club │ │ │ ├── AbandonClub.ts │ │ │ ├── ClubMembers.ts │ │ │ ├── ClubSettings.ts │ │ │ ├── CreateClub.ts │ │ │ ├── DemoteMember.ts │ │ │ ├── JoinClub.ts │ │ │ ├── KickFromClub.ts │ │ │ ├── LeaveClub.ts │ │ │ ├── MyClubs.ts │ │ │ ├── PromoteMember.ts │ │ │ └── ViewClub.ts │ │ ├── items │ │ │ ├── CraftItem.ts │ │ │ ├── ItemInfo.ts │ │ │ ├── ShowRecipes.ts │ │ │ └── UseItem.ts │ │ ├── leaderboard │ │ │ ├── Leaderboards.ts │ │ │ ├── TopGroups.ts │ │ │ └── TopWishlist.ts │ │ ├── player │ │ │ ├── Balance.ts │ │ │ ├── CardInventory.ts │ │ │ ├── ClaimMonthlyPatronReward.ts │ │ │ ├── Cooldowns.ts │ │ │ ├── DailyReward.ts │ │ │ ├── DropCards.ts │ │ │ ├── ItemInventory.ts │ │ │ ├── MultiTrade.ts │ │ │ ├── Pay.ts │ │ │ ├── Reminders.ts │ │ │ ├── SetActiveCard.ts │ │ │ ├── SetBlurb.ts │ │ │ ├── TogglePrivate.ts │ │ │ ├── Trade.ts │ │ │ ├── UserInfo.ts │ │ │ ├── ViewDye.ts │ │ │ ├── ViewDyes.ts │ │ │ ├── ViewProfile.ts │ │ │ ├── vault │ │ │ │ ├── ShowVault.ts │ │ │ │ ├── VaultAdd.ts │ │ │ │ ├── VaultCards.ts │ │ │ │ └── VaultRemove.ts │ │ │ └── wishlist │ │ │ │ ├── AddWishlist.ts │ │ │ │ ├── ClearWishlist.ts │ │ │ │ ├── RemoveWishlist.ts │ │ │ │ └── ViewWishlist.ts │ │ ├── poll │ │ │ ├── AnswerPoll.ts │ │ │ └── ViewPoll.ts │ │ ├── quest │ │ │ └── ShowQuests.ts │ │ ├── shop │ │ │ ├── ItemShop.ts │ │ │ ├── Shop.ts │ │ │ ├── StickerShop.ts │ │ │ └── ViewStickerPack.ts │ │ ├── tags │ │ │ ├── BurnTag.ts │ │ │ ├── BurnUntagged.ts │ │ │ ├── CreateTag.ts │ │ │ ├── DeleteTag.ts │ │ │ ├── EditTag.ts │ │ │ ├── TagCard.ts │ │ │ ├── UntagCard.ts │ │ │ ├── ViewTags.ts │ │ │ └── autotag │ │ │ │ ├── ChangePriority.ts │ │ │ │ ├── CreateAutotag.ts │ │ │ │ ├── DeleteAutotag.ts │ │ │ │ └── ViewAutotags.ts │ │ └── wheel │ │ │ └── OpenMysteryBox.ts │ ├── guild │ │ ├── Prefix.ts │ │ └── SetChannel.ts │ ├── info │ │ ├── Birthdays.ts │ │ ├── GroupList.ts │ │ ├── Help.ts │ │ ├── Invite.ts │ │ ├── Patreon.ts │ │ ├── Rules.ts │ │ └── Vote.ts │ ├── mod │ │ ├── blacklist │ │ │ ├── Blacklist.ts │ │ │ ├── QuashCase.ts │ │ │ ├── ViewCase.ts │ │ │ └── ViewCases.ts │ │ └── club │ │ │ ├── BanClubName.ts │ │ │ ├── ForceAbandon.ts │ │ │ └── UnbanClubName.ts │ └── orpheus │ │ ├── OrpheusGift.ts │ │ ├── OrpheusGifts.ts │ │ ├── OrpheusSearch.ts │ │ ├── OrpheusTotalGiftsReceived.ts │ │ ├── OrpheusTotalGiftsSent.ts │ │ ├── OrpheusUser.ts │ │ └── OrpheusUser2.ts ├── index.ts ├── lib │ ├── CardSpawner.ts │ ├── DMHandler.ts │ ├── FontLoader.ts │ ├── IssueHandler.ts │ ├── StatsD.ts │ ├── ZephyrUtils.ts │ ├── command │ │ ├── CommandLib.ts │ │ └── multitrade │ │ │ ├── ProcessItems.ts │ │ │ ├── ProcessLogs.ts │ │ │ ├── RenderInventory.ts │ │ │ ├── TransferItems.ts │ │ │ ├── VerifyItems.ts │ │ │ └── typeguards │ │ │ └── MultitradeTypeguards.ts │ ├── cosmetics │ │ ├── Backgrounds.ts │ │ ├── Frames.ts │ │ └── Stickers.ts │ ├── database │ │ ├── index.ts │ │ ├── services │ │ │ ├── game │ │ │ │ ├── AlbumService.ts │ │ │ │ ├── AutotagService.ts │ │ │ │ ├── BadgeService.ts │ │ │ │ ├── CardService.ts │ │ │ │ ├── LeaderboardService.ts │ │ │ │ └── ProfileService.ts │ │ │ ├── guild │ │ │ │ └── GuildService.ts │ │ │ ├── meta │ │ │ │ ├── AnticheatService.ts │ │ │ │ ├── BlacklistService.ts │ │ │ │ ├── PatreonService.ts │ │ │ │ └── PollService.ts │ │ │ └── orpheus │ │ │ │ └── OrpheusService.ts │ │ └── sql │ │ │ ├── Filters.ts │ │ │ ├── game │ │ │ ├── album │ │ │ │ ├── AlbumGet.ts │ │ │ │ └── AlbumSet.ts │ │ │ ├── autotag │ │ │ │ ├── AutotagGet.ts │ │ │ │ └── AutotagSet.ts │ │ │ ├── badge │ │ │ │ ├── BadgeGet.ts │ │ │ │ └── BadgeSet.ts │ │ │ ├── card │ │ │ │ ├── CardGet.ts │ │ │ │ └── CardSet.ts │ │ │ ├── club │ │ │ │ ├── ClubGetter.ts │ │ │ │ └── ClubSetter.ts │ │ │ ├── leaderboard │ │ │ │ └── LeaderboardGet.ts │ │ │ ├── profile │ │ │ │ ├── ProfileGet.ts │ │ │ │ └── ProfileSetter.ts │ │ │ ├── quest │ │ │ │ ├── QuestGetter.ts │ │ │ │ └── QuestSetter.ts │ │ │ └── shop │ │ │ │ ├── CosmeticGetter.ts │ │ │ │ └── ShopGetter.ts │ │ │ ├── guild │ │ │ ├── GuildGet.ts │ │ │ └── GuildSet.ts │ │ │ ├── meta │ │ │ ├── anticheat │ │ │ │ ├── ACGet.ts │ │ │ │ └── ACSet.ts │ │ │ ├── blacklist │ │ │ │ ├── BlacklistGet.ts │ │ │ │ └── BlacklistSet.ts │ │ │ ├── patreon │ │ │ │ ├── PatreonGet.ts │ │ │ │ └── PatreonSet.ts │ │ │ └── poll │ │ │ │ ├── PollGet.ts │ │ │ │ └── PollSet.ts │ │ │ └── orpheus │ │ │ └── OrpheusGet.ts │ ├── discord │ │ ├── Retry.ts │ │ ├── message │ │ │ ├── addReaction.ts │ │ │ ├── createMessage.ts │ │ │ ├── deleteMessage.ts │ │ │ └── editMessage.ts │ │ └── readme.md │ ├── items │ │ ├── RemoveSticker.ts │ │ ├── UseBackground.ts │ │ ├── UseFrame.ts │ │ ├── UseSticker.ts │ │ └── UseStickerPack.ts │ ├── logger │ │ └── Logger.ts │ ├── quest │ │ └── Quest.ts │ ├── shop │ │ └── Shop.ts │ └── utility │ │ ├── color │ │ └── ColorUtils.ts │ │ ├── text │ │ └── TextUtils.ts │ │ └── time │ │ └── TimeUtils.ts ├── structures │ ├── client │ │ ├── ErisFile.ts │ │ ├── RichEmbed.ts │ │ ├── Zephyr.ts │ │ └── commands │ │ │ └── HelloCommand.ts │ ├── command │ │ └── Command.ts │ ├── error │ │ ├── AutotagError.ts │ │ ├── BaseError.ts │ │ ├── ClubError.ts │ │ ├── ShopError.ts │ │ ├── VaultError.ts │ │ ├── WishlistError.ts │ │ └── ZephyrError.ts │ ├── game │ │ ├── Album.ts │ │ ├── Autotag.ts │ │ ├── Badge.ts │ │ ├── BaseCard.ts │ │ ├── Dust.ts │ │ ├── Dye.ts │ │ ├── Frame.ts │ │ ├── Idol.ts │ │ ├── Item.ts │ │ ├── Profile.ts │ │ ├── Recipe.ts │ │ ├── Sticker.ts │ │ ├── Tag.ts │ │ ├── UserCard.ts │ │ ├── Wishlist.ts │ │ ├── blacklist │ │ │ └── Blacklist.ts │ │ ├── club │ │ │ └── database │ │ │ │ ├── DBClub.ts │ │ │ │ └── DBClubMember.ts │ │ └── quest │ │ │ ├── BaseQuest.ts │ │ │ ├── QuestObjective.ts │ │ │ ├── QuestProgression.ts │ │ │ ├── QuestReward.ts │ │ │ └── database │ │ │ └── DBQuest.ts │ ├── item │ │ └── PrefabItem.ts │ ├── meta │ │ └── Patron.ts │ ├── orpheus │ │ ├── OrpheusBitTransaction.ts │ │ ├── OrpheusClaim.ts │ │ ├── OrpheusCommand.ts │ │ ├── OrpheusGift.ts │ │ ├── OrpheusMultitrade.ts │ │ └── OrpheusTrade.ts │ ├── poll │ │ └── Poll.ts │ └── shop │ │ ├── Shop.ts │ │ └── StickerPack.ts └── webhook │ └── index.ts ├── tsconfig.json ├── types ├── eris-collector │ └── index.d.ts ├── eris │ └── index.d.ts ├── global.d.ts └── nearest-color │ └── index.d.ts └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # Orpheus 2 | src/orpheus/ 3 | 4 | # Discord 5 | config.json 6 | src/assets/ 7 | cache/ 8 | setup.sql 9 | 10 | # VSCode 11 | .VSCodeCounter/ 12 | .vscode 13 | 14 | cache/ 15 | 16 | envision* 17 | 18 | .DS_Store 19 | src/.DS_Store 20 | 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | lerna-debug.log* 28 | 29 | # Diagnostic reports (https://nodejs.org/api/report.html) 30 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | *.pid.lock 37 | 38 | # Directory for instrumented libs generated by jscoverage/JSCover 39 | lib-cov 40 | 41 | # Coverage directory used by tools like istanbul 42 | coverage 43 | *.lcov 44 | 45 | # nyc test coverage 46 | .nyc_output 47 | 48 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 49 | .grunt 50 | 51 | # Bower dependency directory (https://bower.io/) 52 | bower_components 53 | 54 | # node-waf configuration 55 | .lock-wscript 56 | 57 | # Compiled binary addons (https://nodejs.org/api/addons.html) 58 | build/Release 59 | 60 | # Dependency directories 61 | node_modules/ 62 | jspm_packages/ 63 | 64 | # TypeScript v1 declaration files 65 | typings/ 66 | 67 | # TypeScript cache 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | .npm 72 | 73 | # Optional eslint cache 74 | .eslintcache 75 | 76 | # Microbundle cache 77 | .rpt2_cache/ 78 | .rts2_cache_cjs/ 79 | .rts2_cache_es/ 80 | .rts2_cache_umd/ 81 | 82 | # Optional REPL history 83 | .node_repl_history 84 | 85 | # Output of 'npm pack' 86 | *.tgz 87 | 88 | # Yarn Integrity file 89 | .yarn-integrity 90 | 91 | # dotenv environment variables file 92 | .env 93 | .env.test 94 | 95 | # parcel-bundler cache (https://parceljs.org/) 96 | .cache 97 | 98 | # Next.js build output 99 | .next 100 | 101 | # Nuxt.js build / generate output 102 | .nuxt 103 | dist 104 | 105 | # Gatsby files 106 | .cache/ 107 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 108 | # https://nextjs.org/blog/next-9-1#public-directory-support 109 | # public 110 | 111 | # vuepress build output 112 | .vuepress/dist 113 | 114 | # Serverless directories 115 | .serverless/ 116 | 117 | # FuseBox cache 118 | .fusebox/ 119 | 120 | # DynamoDB Local files 121 | .dynamodb/ 122 | 123 | # TernJS port file 124 | .tern-port 125 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Zephyr 1.0 2 | 3 | This repository is home to the now-long-outdated code that ran Zephyr 1.0. This repo only serves as an archive and the code likely does not work anymore. 4 | 5 | If you'd like to keep up with the ongoing development of Zephyr 2.0, please join the [Discord](https://discord.gg/zephyrlabs) and check out the [website](https://zephyr.bot) and [organization](https://github.com/mittens-cc)! 6 | -------------------------------------------------------------------------------- /example-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "developers": ["USER_ID"], 3 | "moderators": ["USER_ID"], 4 | "statsdEnabled": false, 5 | "discord": { 6 | "logChannel": "TEXT_CHANNEL_ID", 7 | "defaultPrefix": ".", 8 | "token": "YOUR_BOT_TOKEN", 9 | "secondaryChannels": ["TEXT_CHANNEL_ID", "TEXT_CHANNEL_ID", "TEXT_CHANNEL_ID"], 10 | "main": "TEXT_CHANNEL_ID", 11 | "emoji": { 12 | "bits": "<:emoji:EMOJI_ID>", 13 | "bank": "<:emoji:EMOJI_ID>", 14 | "clock": "<:emoji:EMOJI_ID>", 15 | "star":"<:emoji:EMOJI_ID>", 16 | "warn":"<:emoji:EMOJI_ID>", 17 | "check":"<:emoji:EMOJI_ID>", 18 | "blank": "<:emoji:EMOJI_ID>" 19 | }, 20 | "emojiId": { 21 | "bits": "EMOJI_ID", 22 | "bank": "EMOJI_ID", 23 | "clock": "EMOJI_ID", 24 | "star":"EMOJI_ID", 25 | "warn":"EMOJI_ID", 26 | "check":"EMOJI_ID", 27 | "blank": "EMOJI_ID" 28 | }, 29 | "shardNames": [ 30 | "Allium", 31 | "Aster", 32 | "Camellia", 33 | "Catmint", 34 | "Chrysanthemum", 35 | "Cosmos", 36 | "Dahlia", 37 | "Delphinium", 38 | "Foxglove", 39 | "Hollyhock", 40 | "Hyacinth", 41 | "Hydrangea", 42 | "Iris", 43 | "Lavender", 44 | "Lily", 45 | "Moonflower", 46 | "Peony", 47 | "Poppy", 48 | "Primrose", 49 | "Rhododendron", 50 | "Salvia", 51 | "Snapdragon", 52 | "Snowdrop", 53 | "Tulip", 54 | "Wisteria" 55 | ] 56 | }, 57 | "mariadb": { 58 | "host": "HOST", 59 | "port": 3306, 60 | "user": "USERNAME", 61 | "password": "PASSWORD", 62 | "database": "DATABASE", 63 | "charset": "utf8mb4" 64 | }, 65 | "topgg": { 66 | "enabled": false, 67 | "postEnabled": false, 68 | "token": "TOP.GG_TOKEN", 69 | "webhook": { 70 | "port": 3000, 71 | "auth": "TOP.GG_AUTH" 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@types/chance": "^1.1.0", 4 | "@types/express": "^4.17.9", 5 | "@types/glob": "^7.1.3", 6 | "@types/gm": "^1.18.9", 7 | "@types/mysql": "^2.15.15", 8 | "@types/node-emoji": "^1.8.1", 9 | "@types/node-statsd": "^0.1.2", 10 | "@types/ws": "^7.4.4", 11 | "body-parser": "^1.19.0", 12 | "canvas": "^2.6.1", 13 | "chalk": "^4.1.0", 14 | "chance": "^1.1.7", 15 | "chroma-js": "^2.1.0", 16 | "color-name-list": "^7.27.0", 17 | "dayjs": "^1.9.6", 18 | "dblapi.js": "^2.4.1", 19 | "eris": "bsian03/eris#components", 20 | "eris-collector": "^1.0.1", 21 | "express": "^4.17.1", 22 | "ffmpeg": "^0.0.4", 23 | "glob": "^7.1.6", 24 | "gm": "^1.23.1", 25 | "madge": "^4.0.2", 26 | "mysql": "^2.18.1", 27 | "nearest-color": "^0.4.4", 28 | "node-emoji": "^1.10.0", 29 | "node-statsd": "^0.1.1", 30 | "nodemon": "^2.0.6", 31 | "slash-create": "^3.2.1", 32 | "strip-ansi": "^6.0.0", 33 | "typescript": "^4.1.2", 34 | "winston": "^3.3.3", 35 | "ws": "^7.4.6" 36 | }, 37 | "scripts": { 38 | "start": "yarn node dist/src/index.js", 39 | "build:watch": "tsc -w", 40 | "start:watch": "yarn nodemon dist/src/index.js", 41 | "start:prof": "yarn node --prof dist/src/index.js" 42 | }, 43 | "devDependencies": { 44 | "@types/chroma-js": "^2.1.2" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/commands/debug/Base36ToInt.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../structures/command/Command"; 3 | import { GameProfile } from "../../structures/game/Profile"; 4 | 5 | export default class Base36ToInt extends BaseCommand { 6 | names = ["b36ti"]; 7 | description = "Developer command"; 8 | developerOnly = true; 9 | 10 | async exec( 11 | msg: Message, 12 | _profile: GameProfile, 13 | options: string[] 14 | ): Promise { 15 | const num = parseInt(options[0], 36); 16 | 17 | await this.send(msg.channel, `${options[0]} -> ${num}`); 18 | return; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/commands/debug/EmDash.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../structures/command/Command"; 3 | 4 | export default class EmDash extends BaseCommand { 5 | names = ["em"]; 6 | description = "Sends an em dash for convenience."; 7 | developerOnly = true; 8 | 9 | async exec(msg: Message): Promise { 10 | await this.send(msg.channel, "Here's your em dash: `—`"); 11 | return; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/commands/debug/IntToBase36.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../structures/command/Command"; 3 | import { GameProfile } from "../../structures/game/Profile"; 4 | 5 | export default class IntToBase36 extends BaseCommand { 6 | names = ["itb36"]; 7 | description = "Developer command"; 8 | developerOnly = true; 9 | 10 | async exec( 11 | msg: Message, 12 | _profile: GameProfile, 13 | options: string[] 14 | ): Promise { 15 | const num = parseInt(options[0], 10); 16 | await this.send(msg.channel, `${num} -> ${num.toString(36)}`); 17 | return; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/debug/Ping.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 3 | import { BaseCommand } from "../../structures/command/Command"; 4 | 5 | export default class Ping extends BaseCommand { 6 | id = `mayhem`; 7 | names = [`ping`]; 8 | description = `ping`; 9 | allowDm = true; 10 | 11 | async exec(msg: Message): Promise { 12 | const responseTime = Date.now() - msg.createdAt; 13 | const embed = new MessageEmbed(`Ping`, msg.author).setDescription( 14 | `:satellite: Response time: ${responseTime}ms (inaccurate)` 15 | ); 16 | 17 | await msg.channel.createMessage({ 18 | embed, 19 | }); 20 | 21 | return; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/commands/debug/Stats.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | import { Message } from "eris"; 3 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 4 | import { Zephyr } from "../../structures/client/Zephyr"; 5 | import { BaseCommand } from "../../structures/command/Command"; 6 | 7 | export default class Stats extends BaseCommand { 8 | id = `necklace`; 9 | names = [`stats`]; 10 | description = `Shows the bot's memory usage.`; 11 | allowDm = true; 12 | 13 | async exec(msg: Message): Promise { 14 | const usedMb = process.memoryUsage().heapUsed / 1000 / 1000; 15 | const usedPct = (usedMb / 7983) * 100; 16 | let ramStatusEmoji: string; 17 | if (usedPct < 25) { 18 | ramStatusEmoji = ":smile:"; 19 | } else if (usedPct < 50) { 20 | ramStatusEmoji = ":slight_smile:"; 21 | } else if (usedPct < 75) { 22 | ramStatusEmoji = ":worried:"; 23 | } else { 24 | ramStatusEmoji = ":hot_face:"; 25 | } 26 | 27 | const shard = Zephyr.guildShardMap[msg.guildID!] || 0; 28 | 29 | const rtfl = await Zephyr.fetchUser(`197186779843919877`); 30 | 31 | const startup = dayjs().subtract(Zephyr.uptime, `millisecond`); 32 | const now = dayjs(); 33 | 34 | const days = now.diff(startup, `day`); 35 | const hoursRaw = now.diff(startup, `hour`); 36 | const hours = hoursRaw - days * 24; 37 | const minutesRaw = now.diff(startup, `minute`); 38 | const minutes = minutesRaw - hoursRaw * 60; 39 | const seconds = now.diff(startup, `second`) - minutesRaw * 60; 40 | 41 | const embed = new MessageEmbed(`Stats`, msg.author) 42 | .setDescription( 43 | `Shard - **${Zephyr.config.discord.shardNames[shard]}** (${ 44 | Zephyr.shards.size 45 | } total)\nUptime - **${days > 0 ? `${days}d ` : ``}${ 46 | hours > 0 ? `${hours}h ` : `` 47 | }${minutes > 0 ? `${minutes}m ` : ``}${ 48 | seconds > 0 ? `${seconds}s` : `` 49 | }**\nCache Size - **${Zephyr.users.size}** users / **${ 50 | Zephyr.guilds.size 51 | }** guilds\nGame - **${Zephyr.getCards().length}** cards` 52 | ) 53 | .addField({ 54 | name: `Memory Usage`, 55 | value: `— ${usedPct.toFixed(2)}% ${ramStatusEmoji}`, 56 | inline: true, 57 | }) 58 | .setFooter( 59 | `Made with ❤️ by ${rtfl?.tag || `Luca`}`, 60 | rtfl ? rtfl.dynamicAvatarURL("png") : "" 61 | ); 62 | 63 | await this.send(msg.channel, embed); 64 | return; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/commands/dev/AddBits.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { ProfileService } from "../../lib/database/services/game/ProfileService"; 3 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 4 | import { Zephyr } from "../../structures/client/Zephyr"; 5 | import { BaseCommand } from "../../structures/command/Command"; 6 | import * as ZephyrError from "../../structures/error/ZephyrError"; 7 | 8 | export default class AddBits extends BaseCommand { 9 | names = ["addbits"]; 10 | description = `Adds bits to a user's balance.`; 11 | developerOnly = true; 12 | 13 | async exec(msg: Message): Promise { 14 | if (!msg.mentions[0]) throw new ZephyrError.InvalidMentionError(); 15 | const amountRaw = msg.content 16 | .split(" ") 17 | .filter((c) => !isNaN(parseInt(c, 10))); 18 | if (!amountRaw[0]) throw new ZephyrError.InvalidAmountOfBitsError(); 19 | 20 | let targetUser = msg.mentions[0]; 21 | let target = await ProfileService.getProfile(targetUser.id); 22 | const amount = parseInt(amountRaw[0], 10); 23 | 24 | const _target = await ProfileService.addBitsToProfile(target, amount); 25 | const embed = new MessageEmbed(`Add Bits`, msg.author) 26 | .setDescription( 27 | `Gave ${Zephyr.config.discord.emoji.bits}**${amount}** to **${targetUser.tag}**.` 28 | ) 29 | .setFooter(`New balance: ${_target.bits.toLocaleString()}`); 30 | 31 | await this.send(msg.channel, embed); 32 | return; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/commands/dev/ButtonTest.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../structures/command/Command"; 3 | 4 | export default class ButtonTest extends BaseCommand { 5 | names = ["buttontest"]; 6 | description = `Button Test.`; 7 | developerOnly = true; 8 | 9 | async exec(msg: Message): Promise { 10 | await msg.channel.createMessage({ 11 | components: [ 12 | { type: 1, components: [{ type: 2, custom_id: `test`, style: 1 }] }, 13 | ], 14 | }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/commands/dev/CheckCardImages.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 3 | import { BaseCommand } from "../../structures/command/Command"; 4 | import fs from "fs/promises"; 5 | import { Logger } from "../../lib/logger/Logger"; 6 | import { Zephyr } from "../../structures/client/Zephyr"; 7 | 8 | export default class CheckCardImages extends BaseCommand { 9 | names = ["cci"]; 10 | description = `Scans card images for errors.`; 11 | developerOnly = true; 12 | 13 | async exec(msg: Message): Promise { 14 | const images = Zephyr.getCards().map((c) => c.image); 15 | 16 | const failed = []; 17 | for (let i of images) { 18 | try { 19 | await fs.readFile(i); 20 | } catch { 21 | failed.push(i); 22 | } 23 | } 24 | 25 | const embed = new MessageEmbed(`Check Card Images`, msg.author); 26 | 27 | if (failed.length === 0) { 28 | embed.setDescription(`:ok_hand: All files were scanned successfully.`); 29 | } else { 30 | embed.setDescription( 31 | `**${failed.length}** file${ 32 | failed.length === 1 ? `` : `s` 33 | } failed. See console for further information.` 34 | ); 35 | 36 | Logger.warn(failed); 37 | } 38 | 39 | await this.send(msg.channel, embed); 40 | return; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/commands/dev/CreateBadge.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BadgeService } from "../../lib/database/services/game/BadgeService"; 3 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 4 | import { BaseCommand } from "../../structures/command/Command"; 5 | import * as ZephyrError from "../../structures/error/ZephyrError"; 6 | import { GameProfile } from "../../structures/game/Profile"; 7 | 8 | export default class CreateBadge extends BaseCommand { 9 | names = ["createbadge"]; 10 | description = `Creates a badge.`; 11 | usage = [`$CMD$ `]; 12 | developerOnly = true; 13 | 14 | async exec( 15 | msg: Message, 16 | _profile: GameProfile, 17 | options: string[] 18 | ): Promise { 19 | const emoji = options[0]; 20 | 21 | if (!emoji) throw new ZephyrError.InvalidBadgeEmojiError(); 22 | 23 | const badgeName = options.slice(1).join(` `); 24 | 25 | if (!badgeName) throw new ZephyrError.InvalidBadgeNameError(); 26 | 27 | const badgeNameExists = await BadgeService.getBadgeByName(badgeName); 28 | if (badgeNameExists) throw new ZephyrError.DuplicateBadgeNameError(); 29 | 30 | const badgeEmojiExists = await BadgeService.getBadgeByEmoji(emoji); 31 | if (badgeEmojiExists) await BadgeService.getBadgeByEmoji(emoji); 32 | 33 | const newBadge = await BadgeService.createBadge(badgeName, emoji); 34 | 35 | const embed = new MessageEmbed(`Create Badge`, msg.author).setDescription( 36 | `:white_check_mark: You created a new badge...\n${newBadge.badgeEmoji} **${newBadge.badgeName}**` 37 | ); 38 | 39 | await this.send(msg.channel, embed); 40 | return; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/commands/dev/DevGiveItem.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 3 | import { BaseCommand } from "../../structures/command/Command"; 4 | import { GameProfile } from "../../structures/game/Profile"; 5 | import * as ZephyrError from "../../structures/error/ZephyrError"; 6 | import { ProfileService } from "../../lib/database/services/game/ProfileService"; 7 | import { getItemByName } from "../../assets/Items"; 8 | 9 | export default class DevUserCard extends BaseCommand { 10 | names = ["dgi"]; 11 | description = `Gives someone an item.`; 12 | developerOnly = true; 13 | 14 | async exec( 15 | msg: Message, 16 | _profile: GameProfile, 17 | options: string[] 18 | ): Promise { 19 | const targetUser = msg.mentions[0]; 20 | if (!targetUser) throw new ZephyrError.InvalidMentionError(); 21 | 22 | const targetItem = getItemByName(options.slice(1).join(" ")); 23 | if (!targetItem) throw new ZephyrError.InvalidItemError(); 24 | 25 | const targetProfile = await ProfileService.getProfile(targetUser.id); 26 | 27 | await ProfileService.addItems(targetProfile, [ 28 | { item: targetItem, count: 1 }, 29 | ]); 30 | 31 | const embed = new MessageEmbed(`Item Giver`, msg.author).setDescription( 32 | `Gave **1x** \`${targetItem.names[0]}\` to **${targetUser.tag}**` 33 | ); 34 | 35 | await this.send(msg.channel, embed); 36 | return; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/commands/dev/Eval.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../structures/command/Command"; 3 | import { GameProfile } from "../../structures/game/Profile"; 4 | import { inspect } from "util"; 5 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 6 | 7 | export default class DevEval extends BaseCommand { 8 | names = ["eval"]; 9 | description = `Evaluates a script.` + `Be careful with this!`; 10 | developerOnly = true; 11 | 12 | private clean(text: any): string { 13 | if (typeof text === "string") 14 | return text 15 | .replace(/`/g, "`" + String.fromCharCode(8203)) 16 | .replace(/@/g, "@" + String.fromCharCode(8203)); 17 | return text; 18 | } 19 | async exec( 20 | msg: Message, 21 | _profile: GameProfile, 22 | options: string[] 23 | ): Promise { 24 | try { 25 | const code = options.join(" "); 26 | let evaled = await eval(code); 27 | 28 | if (typeof evaled !== "string") { 29 | evaled = inspect(evaled); 30 | } 31 | 32 | const embed = new MessageEmbed(`Eval`, msg.author).setDescription( 33 | `Evaluation complete — ${Date.now() - msg.createdAt}ms` + 34 | `\n\`\`\`xl` + 35 | `\n${this.clean(evaled).slice(0, 1000)}` + 36 | `\n\`\`\`` 37 | ); 38 | await this.send(msg.channel, embed); 39 | } catch (e) { 40 | const embed = new MessageEmbed(`Eval`, msg.author).setDescription( 41 | `Error — ${Date.now() - msg.createdAt}ms` + 42 | `\n\`\`\`xl` + 43 | `\n${this.clean(e)}` + 44 | `\n\`\`\`` 45 | ); 46 | await this.send(msg.channel, embed); 47 | } 48 | return; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/commands/dev/ForceDrop.ts: -------------------------------------------------------------------------------- 1 | import { Message, TextChannel } from "eris"; 2 | import { BaseCommand } from "../../structures/command/Command"; 3 | import { CardSpawner } from "../../lib/CardSpawner"; 4 | import { Zephyr } from "../../structures/client/Zephyr"; 5 | 6 | export default class ForceFrame extends BaseCommand { 7 | names = ["forcedrop", "fd"]; 8 | description = `Forcibly drops cards (as if by server activity).`; 9 | developerOnly = true; 10 | 11 | async exec(msg: Message): Promise { 12 | const cards = Zephyr.getRandomCards(3); 13 | await CardSpawner.forceDrop(msg.channel as TextChannel, cards); 14 | return; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/commands/dev/ForceFrame.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../structures/command/Command"; 3 | import { GameProfile } from "../../structures/game/Profile"; 4 | import { CardService } from "../../lib/database/services/game/CardService"; 5 | import * as ZephyrError from "../../structures/error/ZephyrError"; 6 | 7 | export default class ForceFrame extends BaseCommand { 8 | names = ["forceframe"]; 9 | description = `Force changes a card's frame.`; 10 | developerOnly = true; 11 | 12 | async exec( 13 | msg: Message, 14 | _profile: GameProfile, 15 | options: string[] 16 | ): Promise { 17 | const identifier = options[0]; 18 | if (!identifier) throw new ZephyrError.InvalidCardReferenceError(); 19 | 20 | const userCard = await CardService.getUserCardByIdentifier(identifier); 21 | 22 | const frameId = parseInt(options[1]); 23 | 24 | const pic = await CardService.changeCardFrame(userCard, frameId); 25 | 26 | await this.send(msg.channel, "OK", { 27 | files: [{ file: pic, name: "card.png" }], 28 | }); 29 | 30 | return; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/commands/dev/GetLevel.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../structures/command/Command"; 3 | import { GameProfile } from "../../structures/game/Profile"; 4 | import { CardService } from "../../lib/database/services/game/CardService"; 5 | import * as ZephyrError from "../../structures/error/ZephyrError"; 6 | 7 | export default class GetLevel extends BaseCommand { 8 | names = ["getlevel"]; 9 | description = `Gets a card's level.`; 10 | developerOnly = true; 11 | 12 | async exec( 13 | msg: Message, 14 | _profile: GameProfile, 15 | options: string[] 16 | ): Promise { 17 | const identifier = options[0]; 18 | if (!identifier) throw new ZephyrError.InvalidCardReferenceError(); 19 | 20 | const userCard = await CardService.getUserCardByIdentifier(identifier); 21 | 22 | const lvl = CardService.getLevel(userCard); 23 | 24 | await this.send(msg.channel, `Level: ${lvl} / ${userCard.experience} exp`); 25 | 26 | return; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/commands/dev/RecacheCards.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { Zephyr } from "../../structures/client/Zephyr"; 3 | import { BaseCommand } from "../../structures/command/Command"; 4 | 5 | export default class RecacheCards extends BaseCommand { 6 | names = ["recachecards", "rcc"]; 7 | description = `Forces a refresh of the bot's card cache.`; 8 | developerOnly = true; 9 | 10 | async exec(msg: Message): Promise { 11 | await Zephyr.cacheCards(); 12 | 13 | await this.send( 14 | msg.channel, 15 | `:white_check_mark: Cached **${Zephyr.getCards().length}** cards.` 16 | ); 17 | return; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/commands/dev/RefreshStickers.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { Stickers } from "../../lib/cosmetics/Stickers"; 3 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 4 | import { BaseCommand } from "../../structures/command/Command"; 5 | 6 | export default class RefreshStickers extends BaseCommand { 7 | names = [`refreshstickers`]; 8 | description = `Reloads all stickers.`; 9 | developerOnly = true; 10 | allowDm = true; 11 | 12 | async exec(msg: Message): Promise { 13 | await Stickers.loadStickers(); 14 | 15 | const embed = new MessageEmbed( 16 | `Refresh Stickers`, 17 | msg.author 18 | ).setDescription( 19 | `:white_check_mark: Successfully refreshed the sticker cache.` 20 | ); 21 | 22 | await this.send(msg.channel, embed); 23 | return; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/dev/ReloadCommand.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { Zephyr } from "../../structures/client/Zephyr"; 3 | import { BaseCommand } from "../../structures/command/Command"; 4 | import { GameProfile } from "../../structures/game/Profile"; 5 | 6 | export default class ReloadCommand extends BaseCommand { 7 | names = [`reloadcommand`, `rcmd`]; 8 | description = `Re-imports a command.`; 9 | developerOnly = true; 10 | allowDm = true; 11 | 12 | async exec( 13 | msg: Message, 14 | _profile: GameProfile, 15 | options: string[] 16 | ): Promise { 17 | const commandLib = Zephyr.commandLib; 18 | 19 | if (!options[0]) { 20 | await this.send(msg.channel, `Invalid command name or alias.`); 21 | return; 22 | } 23 | 24 | const command = commandLib.getCommand(options[0].toLowerCase()); 25 | 26 | if (!command) { 27 | await this.send(msg.channel, `Invalid command name or alias.`); 28 | return; 29 | } 30 | 31 | commandLib.reloadCommand(command); 32 | 33 | await this.send(msg.channel, `Command reloaded successfully.`); 34 | return; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/dev/RemoveBits.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { ProfileService } from "../../lib/database/services/game/ProfileService"; 3 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 4 | import { Zephyr } from "../../structures/client/Zephyr"; 5 | import { BaseCommand } from "../../structures/command/Command"; 6 | import * as ZephyrError from "../../structures/error/ZephyrError"; 7 | 8 | export default class RemoveBits extends BaseCommand { 9 | names = ["removebits"]; 10 | description = `Removes bits from a user's balance.`; 11 | developerOnly = true; 12 | 13 | async exec(msg: Message): Promise { 14 | if (!msg.mentions[0]) throw new ZephyrError.InvalidMentionError(); 15 | const amountRaw = msg.content 16 | .split(" ") 17 | .filter((c) => !isNaN(parseInt(c, 10))); 18 | if (!amountRaw[0]) throw new ZephyrError.InvalidAmountOfBitsError(); 19 | 20 | let targetUser = msg.mentions[0]; 21 | let target = await ProfileService.getProfile(targetUser.id); 22 | let amount = parseInt(amountRaw[0], 10); 23 | 24 | if (target.bits - amount < 0) amount = target.bits; 25 | 26 | const _target = await ProfileService.removeBitsFromProfile(target, amount); 27 | const embed = new MessageEmbed(`Remove Bits`, msg.author) 28 | .setDescription( 29 | `Took ${Zephyr.config.discord.emoji.bits}**${amount}** from **${targetUser.tag}**.` 30 | ) 31 | .setFooter(`New balance: ${_target.bits.toLocaleString()}`); 32 | 33 | await this.send(msg.channel, embed); 34 | return; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/dev/ResetClaimTimer.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { ProfileService } from "../../lib/database/services/game/ProfileService"; 3 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 4 | import { BaseCommand } from "../../structures/command/Command"; 5 | import * as ZephyrError from "../../structures/error/ZephyrError"; 6 | 7 | export default class ResetClaimTimer extends BaseCommand { 8 | names = ["rct"]; 9 | description = `Resets someone's claim timer.`; 10 | developerOnly = true; 11 | 12 | async exec(msg: Message): Promise { 13 | if (!msg.mentions[0]) throw new ZephyrError.InvalidMentionError(); 14 | 15 | const target = await ProfileService.getProfile(msg.mentions[0].id); 16 | await ProfileService.setClaimTimestamp(target, "1970-01-01 00:00:00"); 17 | 18 | const embed = new MessageEmbed( 19 | `Reset Claim Timer`, 20 | msg.author 21 | ).setDescription(`Reset **${msg.mentions[0].tag}**'s claim timer.`); 22 | 23 | await this.send(msg.channel, embed); 24 | return; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/commands/dev/ResetDropTimer.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { ProfileService } from "../../lib/database/services/game/ProfileService"; 3 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 4 | import { BaseCommand } from "../../structures/command/Command"; 5 | import * as ZephyrError from "../../structures/error/ZephyrError"; 6 | 7 | export default class ResetDropTimer extends BaseCommand { 8 | names = ["rdt"]; 9 | description = `Resets someone's drop timer.`; 10 | developerOnly = true; 11 | 12 | async exec(msg: Message): Promise { 13 | if (!msg.mentions[0]) throw new ZephyrError.InvalidMentionError(); 14 | 15 | const target = await ProfileService.getProfile(msg.mentions[0].id); 16 | await ProfileService.setDropTimestamp(target, "1970-01-01 00:00:00"); 17 | 18 | const embed = new MessageEmbed( 19 | `Reset Drop Timer`, 20 | msg.author 21 | ).setDescription(`Reset **${msg.mentions[0].tag}**'s drop timer.`); 22 | 23 | await this.send(msg.channel, embed); 24 | return; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/commands/dev/ScanCards.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../structures/command/Command"; 3 | import { Logger } from "../../lib/logger/Logger"; 4 | import { Zephyr } from "../../structures/client/Zephyr"; 5 | 6 | export default class ScanCards extends BaseCommand { 7 | names = ["scancards"]; 8 | description = `Scans base card objects for errors.`; 9 | developerOnly = true; 10 | 11 | async exec(msg: Message): Promise { 12 | const cards = Zephyr.getCards(); 13 | 14 | for (let card of cards) { 15 | if (!card.groupId) { 16 | Logger.debug( 17 | `Card ${card.id} (${card.name}) was loaded without a group - possible bug?` 18 | ); 19 | } 20 | if (!card.subgroupId) { 21 | Logger.debug( 22 | `Card ${card.id} (${card.name}) was loaded without a subgroup - possible bug?` 23 | ); 24 | } 25 | if (!card.image) { 26 | Logger.debug( 27 | `Card ${card.id} (${card.name}) was loaded without an image - possible bug?` 28 | ); 29 | } 30 | } 31 | 32 | await this.send(msg.channel, `Check console for info.`); 33 | return; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/commands/dev/SetCreationDate.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../structures/command/Command"; 3 | import { GameProfile } from "../../structures/game/Profile"; 4 | import * as ZephyrError from "../../structures/error/ZephyrError"; 5 | import { ProfileService } from "../../lib/database/services/game/ProfileService"; 6 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 7 | 8 | export default class SetCreationDate extends BaseCommand { 9 | names = ["scd"]; 10 | description = `Sets a user's account creation date.`; 11 | developerOnly = true; 12 | 13 | async exec( 14 | msg: Message, 15 | _profile: GameProfile, 16 | options: string[] 17 | ): Promise { 18 | const targetUser = msg.mentions[0]; 19 | 20 | if (!targetUser) throw new ZephyrError.InvalidMentionError(); 21 | 22 | const targetProfile = await ProfileService.getProfile(targetUser.id); 23 | 24 | const date = options[1]; 25 | 26 | await ProfileService.setProfileCreationDate(targetProfile, date); 27 | 28 | const embed = new MessageEmbed( 29 | `Set Creation Date`, 30 | msg.author 31 | ).setDescription( 32 | `**${targetUser.tag}**'s account creation date has been set to **${date}**.` 33 | ); 34 | 35 | await this.send(msg.channel, embed); 36 | return; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/commands/dev/SetFlag.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../structures/command/Command"; 3 | import { GameProfile } from "../../structures/game/Profile"; 4 | import * as ZephyrError from "../../structures/error/ZephyrError"; 5 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 6 | import { Zephyr } from "../../structures/client/Zephyr"; 7 | 8 | export default class SetFlag extends BaseCommand { 9 | names = [`setflag`]; 10 | description = `Changes the status of a flag.`; 11 | usage = [`$CMD$ `]; 12 | developerOnly = true; 13 | 14 | async exec( 15 | msg: Message, 16 | _profile: GameProfile, 17 | options: string[] 18 | ): Promise { 19 | if (!options[0]) throw new ZephyrError.InvalidFlagNameError(); 20 | if (!options[1] || ![`true`, `false`].includes(options[1].toLowerCase())) 21 | throw new ZephyrError.InvalidFlagValueError(); 22 | 23 | const flags = Zephyr.flags; 24 | 25 | const flagName = options[0]; 26 | const flagValue = options[1].toLowerCase(); 27 | 28 | const flagExists = Object.keys(flags).find((k) => k === flagName); 29 | 30 | if (!flagExists) throw new ZephyrError.FlagNotFoundError(); 31 | 32 | const value = flagValue === `true`; 33 | 34 | flags[flagName as keyof typeof flags] = value; 35 | 36 | const embed = new MessageEmbed(`Set Flag`, msg.author).setDescription( 37 | `The flag \`${flagName}\` is now \`${value}\`.` 38 | ); 39 | 40 | await this.send(msg.channel, embed); 41 | return; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/commands/dev/SetPatron.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../structures/command/Command"; 3 | import { GameProfile } from "../../structures/game/Profile"; 4 | import * as ZephyrError from "../../structures/error/ZephyrError"; 5 | import { ProfileService } from "../../lib/database/services/game/ProfileService"; 6 | import { PatreonService } from "../../lib/database/services/meta/PatreonService"; 7 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 8 | 9 | export default class SetPatron extends BaseCommand { 10 | names = ["setpatron"]; 11 | description = `Marks someone as a Patron.`; 12 | developerOnly = true; 13 | 14 | async exec( 15 | msg: Message, 16 | _profile: GameProfile, 17 | options: string[] 18 | ): Promise { 19 | const targetUser = msg.mentions[0]; 20 | if (!targetUser) throw new ZephyrError.InvalidMentionError(); 21 | 22 | const tier = parseInt(options[1], 10); 23 | if (isNaN(tier)) throw new ZephyrError.InvalidPatronTierError(); 24 | 25 | const target = await ProfileService.getProfile(targetUser.id); 26 | 27 | if (tier === 0) { 28 | await ProfileService.setPatronTier(target, 0); 29 | await PatreonService.removePatron(target); 30 | 31 | const embed = new MessageEmbed(`Set Patron`, msg.author).setDescription( 32 | `Unmarked **${targetUser.tag}** as patron.` 33 | ); 34 | 35 | await this.send(msg.channel, embed); 36 | return; 37 | } else { 38 | await ProfileService.setPatronTier(target, tier); 39 | await PatreonService.addPatron(target); 40 | 41 | const embed = new MessageEmbed(`Set Patron`, msg.author).setDescription( 42 | `Marked **${targetUser.tag}** as a Tier **${tier}** patron.` 43 | ); 44 | 45 | await this.send(msg.channel, embed); 46 | return; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/commands/dev/ShowFlags.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../structures/command/Command"; 3 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 4 | import { Zephyr } from "../../structures/client/Zephyr"; 5 | 6 | export default class Flags extends BaseCommand { 7 | names = [`flags`]; 8 | description = `Shows the list of flags and their values.`; 9 | usage = [`$CMD$`]; 10 | developerOnly = true; 11 | 12 | async exec(msg: Message): Promise { 13 | const flags = Object.entries(Zephyr.flags); 14 | 15 | const flagValues = []; 16 | 17 | for (let flag of flags) { 18 | flagValues.push(`**${flag[0]}**: ${flag[1]}`); 19 | } 20 | 21 | const embed = new MessageEmbed(`Flags`, msg.author) 22 | .setTitle(`Flag list`) 23 | .setDescription(flagValues.join(`\n`)); 24 | 25 | await this.send(msg.channel, embed); 26 | return; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/commands/dev/SimulateCards.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../structures/command/Command"; 3 | import { GameProfile } from "../../structures/game/Profile"; 4 | import { Chance } from "chance"; 5 | 6 | export default class SimulateCards extends BaseCommand { 7 | names = ["sim"]; 8 | description = `Removes bits from a user's balance.`; 9 | developerOnly = true; 10 | 11 | async exec( 12 | msg: Message, 13 | _profile: GameProfile, 14 | options: string[] 15 | ): Promise { 16 | const loopTime = parseInt(options[0], 10); 17 | if (isNaN(loopTime)) { 18 | this.send(msg.channel, "Enter a valid number."); 19 | return; 20 | } 21 | const chance = new Chance(); 22 | const now = Date.now(); 23 | const rolls = { 24 | 0: 0, 25 | 1: 0, 26 | 2: 0, 27 | 3: 0, 28 | 4: 0, 29 | 5: 0, 30 | }; 31 | 32 | for (let i = 0; i < loopTime; i++) { 33 | const tier = chance.weighted( 34 | [0, 1, 2, 3, 4, 5], 35 | [10, 25, 30, 23.6, 14.6, 6.6] 36 | ); 37 | rolls[tier as keyof typeof rolls] += 1; 38 | } 39 | 40 | const after = Date.now(); 41 | await this.send( 42 | msg.channel, 43 | `Generated **${( 44 | rolls["0"] + 45 | rolls["1"] + 46 | rolls["2"] + 47 | rolls["3"] + 48 | rolls["4"] + 49 | rolls["5"] 50 | ).toLocaleString()}** cards in **${after - now}ms**. Results:` + 51 | `\n1 — **${rolls["0"].toLocaleString()}**` + 52 | `\n2 — **${rolls["1"].toLocaleString()}**` + 53 | `\n3 — **${rolls["2"].toLocaleString()}**` + 54 | `\n4 — **${rolls["3"].toLocaleString()}**` + 55 | `\n5 — **${rolls["4"].toLocaleString()}**` + 56 | `\n6 — **${rolls["5"].toLocaleString()}**` 57 | ); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/commands/dev/SimulateVote.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../structures/command/Command"; 3 | import { GameProfile } from "../../structures/game/Profile"; 4 | import { Zephyr } from "../../structures/client/Zephyr"; 5 | 6 | export default class SimulateVote extends BaseCommand { 7 | names = [`simvote`]; 8 | description = `Simulates a vote.`; 9 | developerOnly = true; 10 | 11 | async exec( 12 | msg: Message, 13 | _profile: GameProfile, 14 | options: string[] 15 | ): Promise { 16 | if (!msg.mentions[0]) { 17 | await this.send( 18 | msg.channel, 19 | `Please mention someone to force a vote on.` 20 | ); 21 | return; 22 | } 23 | 24 | await Zephyr.handleVote(msg.mentions[0].id, options[0] === "true"); 25 | 26 | await this.send(msg.channel, `Done`); 27 | return; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/commands/dev/disable/DisableCommand.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { Zephyr } from "../../../structures/client/Zephyr"; 3 | import { BaseCommand } from "../../../structures/command/Command"; 4 | import { GameProfile } from "../../../structures/game/Profile"; 5 | 6 | export default class DisableCommand extends BaseCommand { 7 | names = [`disable`]; 8 | description = `Toggles a command's disabled property.`; 9 | usage = [`$CMD$ `]; 10 | developerOnly = true; 11 | 12 | async exec( 13 | msg: Message, 14 | _profile: GameProfile, 15 | options: string[] 16 | ): Promise { 17 | if (!options[0]) { 18 | await this.send(msg.channel, `Invalid command or alias.`); 19 | 20 | return; 21 | } 22 | 23 | const command = Zephyr.commandLib.getCommand(options[0].toLowerCase()); 24 | 25 | if (!command) { 26 | await this.send(msg.channel, `Invalid command or alias.`); 27 | 28 | return; 29 | } 30 | 31 | command.disabled = !command.disabled; 32 | 33 | await this.send( 34 | msg.channel, 35 | `Command \`${command.names[0]}\` (ID \`${command.id}\`) is now **${ 36 | command.disabled ? `disabled` : `enabled` 37 | }**.` 38 | ); 39 | return; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/commands/dev/disable/SetDisableMessage.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { Zephyr } from "../../../structures/client/Zephyr"; 3 | import { BaseCommand } from "../../../structures/command/Command"; 4 | import { GameProfile } from "../../../structures/game/Profile"; 5 | 6 | export default class SetDisableMessage extends BaseCommand { 7 | names = [`setdisablemessage`, `sdm`]; 8 | description = `Sets a command's disable message.`; 9 | usage = [`$CMD$ `]; 10 | developerOnly = true; 11 | 12 | async exec( 13 | msg: Message, 14 | _profile: GameProfile, 15 | options: string[] 16 | ): Promise { 17 | if (!options[0]) { 18 | await this.send(msg.channel, `Invalid command or alias.`); 19 | 20 | return; 21 | } 22 | 23 | const command = Zephyr.commandLib.getCommand(options[0].toLowerCase()); 24 | 25 | if (!command) { 26 | await this.send(msg.channel, `Invalid command or alias.`); 27 | 28 | return; 29 | } 30 | 31 | command.disabledMessage = options.slice(1).join(` `); 32 | 33 | await this.send( 34 | msg.channel, 35 | `Command \`${command.names[0]}\` (ID \`${command.id}\`)'s disabled message was updated to ${command.disabledMessage}` 36 | ); 37 | return; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/commands/dev/maintenance/SetMaintenanceHeader.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { Zephyr } from "../../../structures/client/Zephyr"; 3 | import { BaseCommand } from "../../../structures/command/Command"; 4 | import { GameProfile } from "../../../structures/game/Profile"; 5 | 6 | export default class SetMaintenanceHeader extends BaseCommand { 7 | names = [`setmaintenanceheader`, `smh`]; 8 | description = `Changes the maintenance header.`; 9 | usage = [`$CMD$
`]; 10 | developerOnly = true; 11 | 12 | async exec( 13 | msg: Message, 14 | _profile: GameProfile, 15 | options: string[] 16 | ): Promise { 17 | Zephyr.maintenance.header = options.join(` `) || `Maintenance.`; 18 | 19 | await this.send( 20 | msg.channel, 21 | `Updated the maintenance header to ${options.join(` `)}` 22 | ); 23 | return; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/commands/dev/maintenance/SetMaintenanceMessage.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { Zephyr } from "../../../structures/client/Zephyr"; 3 | import { BaseCommand } from "../../../structures/command/Command"; 4 | import { GameProfile } from "../../../structures/game/Profile"; 5 | 6 | export default class SetMaintenanceMessage extends BaseCommand { 7 | names = [`setmaintenancemessage`, `smmsg`]; 8 | description = `Changes the maintenance message.`; 9 | usage = [`$CMD$ `]; 10 | developerOnly = true; 11 | 12 | async exec( 13 | msg: Message, 14 | _profile: GameProfile, 15 | options: string[] 16 | ): Promise { 17 | Zephyr.maintenance.message = 18 | options.join(` `) || `No maintenance message has been specified.`; 19 | 20 | await this.send( 21 | msg.channel, 22 | `Updated the maintenance message to ${options.join(` `)}` 23 | ); 24 | return; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/commands/dev/maintenance/SetMaintenanceMode.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { Zephyr } from "../../../structures/client/Zephyr"; 3 | import { BaseCommand } from "../../../structures/command/Command"; 4 | 5 | export default class SetMaintenanceMode extends BaseCommand { 6 | names = [`setmaintenancemode`, `smm`, `maint`]; 7 | description = `Toggles maintenance mode.`; 8 | usage = [`$CMD$`]; 9 | developerOnly = true; 10 | 11 | async exec(msg: Message): Promise { 12 | Zephyr.maintenance.enabled = !Zephyr.maintenance.enabled; 13 | 14 | await this.send( 15 | msg.channel, 16 | `Maintenance mode is now **${ 17 | Zephyr.maintenance.enabled ? `enabled` : `disabled` 18 | }**.` 19 | ); 20 | return; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/commands/dev/poll/ActivatePoll.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { PollService } from "../../../lib/database/services/meta/PollService"; 3 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 4 | import { BaseCommand } from "../../../structures/command/Command"; 5 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 6 | import { GameProfile } from "../../../structures/game/Profile"; 7 | 8 | export default class ActivatePoll extends BaseCommand { 9 | names = ["activatepoll"]; 10 | description = `Activates a poll.`; 11 | usage = [`$CMD$ `]; 12 | developerOnly = true; 13 | 14 | async exec( 15 | msg: Message, 16 | _profile: GameProfile, 17 | options: string[] 18 | ): Promise { 19 | const pollId = parseInt(options[0], 10); 20 | 21 | if (isNaN(pollId) || pollId < 1) throw new ZephyrError.InvalidPollIdError(); 22 | 23 | const poll = await PollService.getPollById(pollId); 24 | 25 | if (poll.active) throw new ZephyrError.PollAlreadyActivatedError(poll); 26 | 27 | const _poll = await PollService.activatePoll(poll); 28 | 29 | const embed = new MessageEmbed(`Activate Poll`, msg.author).setDescription( 30 | `Poll **${_poll.title}** has been activated.` 31 | ); 32 | 33 | await this.send(msg.channel, embed); 34 | return; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/dev/poll/CreatePoll.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | import { Message } from "eris"; 3 | import { PollService } from "../../../lib/database/services/meta/PollService"; 4 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 5 | import { BaseCommand } from "../../../structures/command/Command"; 6 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 7 | import { GameProfile } from "../../../structures/game/Profile"; 8 | 9 | export default class CreatePoll extends BaseCommand { 10 | names = ["createpoll"]; 11 | description = `Creates a new poll.`; 12 | usage = [`$CMD$ [|| poll end time]`]; 13 | developerOnly = true; 14 | 15 | async exec( 16 | msg: Message, 17 | _profile: GameProfile, 18 | options: string[] 19 | ): Promise { 20 | const [title, description, end] = options 21 | .join(` `) 22 | .split(`||`) 23 | .map((o) => o.trim()); 24 | 25 | if (!title) throw new ZephyrError.InvalidPollTitleError(); 26 | if (!description) throw new ZephyrError.InvalidPollDescriptionError(); 27 | 28 | let endsAt; 29 | if (end) { 30 | const time = dayjs(end).format(`YYYY-MM-DD HH:mm:ss`); 31 | 32 | if (time !== `Invalid Date`) endsAt = time; 33 | } 34 | 35 | const poll = await PollService.createPoll(title, description, endsAt); 36 | 37 | const embed = new MessageEmbed(`Create Poll`, msg.author) 38 | .setDescription( 39 | `Created a new poll titled **${title}**.\nHas End?: **${ 40 | endsAt || `No` 41 | }**\nDescription:\n${description}` 42 | ) 43 | .setFooter(`Poll ID: ${poll.id} - Don't forget to activate!`); 44 | 45 | await this.send(msg.channel, embed); 46 | return; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/commands/dev/poll/DeactivatePoll.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { PollService } from "../../../lib/database/services/meta/PollService"; 3 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 4 | import { BaseCommand } from "../../../structures/command/Command"; 5 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 6 | import { GameProfile } from "../../../structures/game/Profile"; 7 | 8 | export default class DeactivatePoll extends BaseCommand { 9 | names = ["deactivatepoll"]; 10 | description = `Deactivates a poll.`; 11 | usage = [`$CMD$ `]; 12 | developerOnly = true; 13 | 14 | async exec( 15 | msg: Message, 16 | _profile: GameProfile, 17 | options: string[] 18 | ): Promise { 19 | const pollId = parseInt(options[0], 10); 20 | 21 | if (isNaN(pollId) || pollId < 1) throw new ZephyrError.InvalidPollIdError(); 22 | 23 | const poll = await PollService.getPollById(pollId); 24 | 25 | if (!poll.active) throw new ZephyrError.PollAlreadyDeactivatedError(poll); 26 | 27 | const _poll = await PollService.deactivatePoll(poll); 28 | 29 | const embed = new MessageEmbed( 30 | `Deactivate Poll`, 31 | msg.author 32 | ).setDescription(`Poll **${_poll.title}** has been deactivated.`); 33 | 34 | await this.send(msg.channel, embed); 35 | return; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/commands/game/album/RemoveCardFromAlbum.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 4 | import { GameProfile } from "../../../structures/game/Profile"; 5 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 6 | import { CardService } from "../../../lib/database/services/game/CardService"; 7 | import { AlbumService } from "../../../lib/database/services/game/AlbumService"; 8 | 9 | export default class RemoveCardFromAlbum extends BaseCommand { 10 | id = `fireside`; 11 | names = [`removecardfromalbum`, `rc`]; 12 | usage = [`$CMD$ `]; 13 | description = `Removes a card from an album.`; 14 | allowDm = true; 15 | 16 | async exec( 17 | msg: Message, 18 | _profile: GameProfile, 19 | options: string[] 20 | ): Promise { 21 | if (!options[0]) throw new ZephyrError.InvalidCardReferenceError(); 22 | 23 | const targetCard = await CardService.getUserCardByIdentifier(options[0]); 24 | 25 | if (targetCard.discordId !== msg.author.id) 26 | throw new ZephyrError.NotOwnerOfCardError(targetCard); 27 | 28 | const isInAlbum = await AlbumService.cardIsInAlbum(targetCard); 29 | 30 | if (!isInAlbum) throw new ZephyrError.CardNotInAlbumError(targetCard); 31 | 32 | const albumCards = await AlbumService.getCardsByAlbum(isInAlbum); 33 | const page = Math.max( 34 | 1, 35 | Math.ceil(albumCards.find((c) => c.cardId === targetCard.id)!.slot / 8) 36 | ); 37 | 38 | await AlbumService.removeCardsFromAlbums([targetCard], [isInAlbum]); 39 | 40 | await AlbumService.updateAlbumCache( 41 | isInAlbum, 42 | albumCards.filter( 43 | (c) => 44 | c.cardId !== targetCard.id && 45 | c.slot >= page * 8 - 8 && 46 | c.slot <= page * 8 47 | ), 48 | page 49 | ); 50 | 51 | const embed = new MessageEmbed(`Remove Card`, msg.author).setDescription( 52 | `\`${targetCard.id.toString( 53 | 36 54 | )}\` was removed from \`${isInAlbum.name.toLowerCase()}\`.` 55 | ); 56 | 57 | await this.send(msg.channel, embed); 58 | return; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/commands/game/album/RenameAlbum.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 4 | import { GameProfile } from "../../../structures/game/Profile"; 5 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 6 | import { AlbumService } from "../../../lib/database/services/game/AlbumService"; 7 | 8 | export default class RenameAlbum extends BaseCommand { 9 | id = `nightwalk`; 10 | names = [`renamealbum`, `ra`]; 11 | usage = [`$CMD$ `]; 12 | description = `Changes the name of an album.`; 13 | allowDm = true; 14 | 15 | async exec( 16 | msg: Message, 17 | profile: GameProfile, 18 | options: string[] 19 | ): Promise { 20 | if (!options[0]) throw new ZephyrError.InvalidAlbumNameError(); 21 | 22 | const targetAlbum = await AlbumService.getAlbumByName( 23 | options[0].toLowerCase(), 24 | msg.author, 25 | msg.author 26 | ); 27 | 28 | if (!options[1]) throw new ZephyrError.UnspecifiedNewAlbumNameError(); 29 | 30 | const newName = options[1].toLowerCase().trim(); 31 | 32 | if (newName.length > 12) 33 | throw new ZephyrError.InvalidAlbumNameCreationError(); 34 | 35 | const allAlbums = await AlbumService.getAlbumsByProfile(profile); 36 | 37 | const nameTaken = allAlbums.find((a) => a.name.toLowerCase() === newName); 38 | if (nameTaken) throw new ZephyrError.AlbumNameTakenError(newName); 39 | 40 | const newAlbum = await AlbumService.changeAlbumName(targetAlbum, newName); 41 | 42 | const embed = new MessageEmbed(`Rename Album`, msg.author).setDescription( 43 | `Your album \`${targetAlbum.name}\` was changed to \`${newAlbum.name}\`.` 44 | ); 45 | 46 | await this.send(msg.channel, embed); 47 | 48 | return; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/commands/game/badges/DeleteBadge.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BadgeService } from "../../../lib/database/services/game/BadgeService"; 3 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 4 | import { BaseCommand } from "../../../structures/command/Command"; 5 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 6 | import { GameProfile } from "../../../structures/game/Profile"; 7 | 8 | export default class DeleteBadge extends BaseCommand { 9 | names = ["deletebadge"]; 10 | description = `Deletes a badge.`; 11 | usage = [`$CMD$ `]; 12 | allowDm = true; 13 | developerOnly = true; 14 | 15 | async exec( 16 | msg: Message, 17 | _profile: GameProfile, 18 | options: string[] 19 | ): Promise { 20 | if (!options[0]) throw new ZephyrError.InvalidBadgeNameError(); 21 | 22 | const badgeName = options.join(` `); 23 | 24 | const targetBadge = await BadgeService.getBadgeByName(badgeName); 25 | if (!targetBadge) throw new ZephyrError.BadgeNameNotFoundError(badgeName); 26 | 27 | await BadgeService.deleteBadge(targetBadge); 28 | 29 | const embed = new MessageEmbed(`Delete Badge`, msg.author).setDescription( 30 | `:white_check_mark: ${targetBadge.badgeEmoji} **${targetBadge.badgeName}** was deleted.` 31 | ); 32 | 33 | await this.send(msg.channel, embed); 34 | return; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/game/badges/GrantBadge.ts: -------------------------------------------------------------------------------- 1 | import { Message, User } from "eris"; 2 | import { ProfileService } from "../../../lib/database/services/game/ProfileService"; 3 | import { BaseCommand } from "../../../structures/command/Command"; 4 | import { GameProfile } from "../../../structures/game/Profile"; 5 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 6 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 7 | import { BadgeService } from "../../../lib/database/services/game/BadgeService"; 8 | import { Zephyr } from "../../../structures/client/Zephyr"; 9 | 10 | export default class GrantBadge extends BaseCommand { 11 | names = [`grant`]; 12 | description = `Gives somebody a badge.`; 13 | usage = [`$CMD$ <@mention/user id> `]; 14 | allowDm = true; 15 | developerOnly = true; 16 | 17 | async exec( 18 | msg: Message, 19 | _profile: GameProfile, 20 | options: string[] 21 | ): Promise { 22 | if (!options[0]) throw new ZephyrError.InvalidMentionError(); 23 | if (!options[1]) throw new ZephyrError.InvalidBadgeNameError(); 24 | 25 | let targetUser: User; 26 | let targetProfile: GameProfile | undefined; 27 | 28 | if (msg.mentions[0]) { 29 | targetUser = msg.mentions[0]; 30 | } else if (options[0]) { 31 | if ( 32 | isNaN(parseInt(options[0], 10)) || 33 | options[0].length < 17 || 34 | options[0].length > 18 35 | ) 36 | throw new ZephyrError.InvalidMentionError(); 37 | 38 | const fetchUser = await Zephyr.fetchUser(options[0]); 39 | 40 | if (!fetchUser) throw new ZephyrError.UserNotFoundError(); 41 | 42 | targetUser = fetchUser; 43 | } else throw new ZephyrError.InvalidMentionError(); 44 | 45 | targetProfile = await ProfileService.getProfile(targetUser.id); 46 | 47 | const badgeName = options.slice(1).join(` `); 48 | 49 | const targetBadge = await BadgeService.getBadgeByName(badgeName); 50 | 51 | if (!targetBadge) throw new ZephyrError.BadgeNameNotFoundError(badgeName); 52 | 53 | const profileBadges = await BadgeService.getProfileBadges(targetProfile); 54 | 55 | if (profileBadges.find((b) => b.badgeId === targetBadge.id)) 56 | throw new ZephyrError.DuplicateUserBadgeError(targetUser, targetBadge); 57 | 58 | await BadgeService.createUserBadge(targetProfile, targetBadge); 59 | 60 | const embed = new MessageEmbed(`Grant Badge`, msg.author).setDescription( 61 | `**${targetUser.tag}** was given ${targetBadge.badgeEmoji} **${targetBadge.badgeName}**.` 62 | ); 63 | 64 | await this.send(msg.channel, embed); 65 | return; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/commands/game/badges/ViewBadge.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { GameProfile } from "../../../structures/game/Profile"; 4 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 5 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 6 | import { BadgeService } from "../../../lib/database/services/game/BadgeService"; 7 | import dayjs from "dayjs"; 8 | import { dateTimeDisplay } from "../../../lib/utility/time/TimeUtils"; 9 | 10 | export default class ShowBadges extends BaseCommand { 11 | id = `drone`; 12 | names = [`badge`]; 13 | description = `Shows you information about a badge.`; 14 | usage = [`$CMD$ `]; 15 | allowDm = true; 16 | 17 | async exec( 18 | msg: Message, 19 | profile: GameProfile, 20 | options: string[] 21 | ): Promise { 22 | if (options.length < 1) throw new ZephyrError.InvalidBadgeNameError(); 23 | 24 | const badgeName = options.join(` `); 25 | 26 | const targetBadge = await BadgeService.getBadgeByName(badgeName); 27 | 28 | if (!targetBadge) throw new ZephyrError.BadgeNameNotFoundError(badgeName); 29 | 30 | const grantedCount = await BadgeService.getNumberOfBadgeGranted( 31 | targetBadge 32 | ); 33 | const authorBadges = await BadgeService.getProfileBadges(profile); 34 | 35 | const userHasBadge = authorBadges.find((b) => b.badgeId === targetBadge.id); 36 | 37 | let description = `${targetBadge.badgeEmoji} **${targetBadge.badgeName}**`; 38 | 39 | if (targetBadge.badgeDescription) { 40 | description += `\n*"${targetBadge.badgeDescription}"*`; 41 | } 42 | 43 | description += `\n\n**${grantedCount.toLocaleString()}** ${ 44 | grantedCount === 1 ? `player has` : `players have` 45 | } this badge.`; 46 | 47 | if (userHasBadge) { 48 | description += `\nYou received this badge on **${dateTimeDisplay( 49 | dayjs(userHasBadge.createdAt) 50 | )}**.`; 51 | } 52 | 53 | const embed = new MessageEmbed(`Badge`, msg.author).setDescription( 54 | description 55 | ); 56 | 57 | await this.send(msg.channel, embed); 58 | return; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/commands/game/card/PreviewSticker.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { GameProfile } from "../../../structures/game/Profile"; 4 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 5 | import { createCanvas } from "canvas"; 6 | import { Stickers } from "../../../lib/cosmetics/Stickers"; 7 | 8 | export default class PreviewSticker extends BaseCommand { 9 | id = `dirt`; 10 | names = [`previewsticker`, `ps`]; 11 | description = `Shows you what a sticker looks like before you buy it.`; 12 | usage = [`$CMD$ `]; 13 | allowDm = true; 14 | 15 | async exec( 16 | msg: Message, 17 | _profile: GameProfile, 18 | options: string[] 19 | ): Promise { 20 | const query = options.join(" ").toLowerCase(); 21 | if (!query) throw new ZephyrError.InvalidStickerError(); 22 | 23 | const sticker = Stickers.getStickerByName(query.toLowerCase().trim()); 24 | if (!sticker) throw new ZephyrError.InvalidStickerError(); 25 | 26 | const canvas = createCanvas(120, 120); 27 | const ctx = canvas.getContext("2d"); 28 | 29 | ctx.beginPath(); 30 | ctx.rect(0, 0, 120, 120); 31 | ctx.fillStyle = "#36393E"; 32 | ctx.fill(); 33 | 34 | ctx.drawImage(sticker.image, 0, 0, 120, 120); 35 | 36 | const buffer = canvas.toBuffer("image/jpeg"); 37 | 38 | await this.send( 39 | msg.channel, 40 | `> **${msg.author.tag}** — Previewing **${sticker.name}**`, 41 | { files: [{ file: buffer, name: `sticker.png` }] } 42 | ); 43 | return; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/commands/game/club/ClubMembers.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { GameProfile } from "../../../structures/game/Profile"; 4 | import { ClubError } from "../../../structures/error/ClubError"; 5 | import { 6 | getClubByName, 7 | getClubMembers, 8 | } from "../../../lib/database/sql/game/club/ClubGetter"; 9 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 10 | import { escapeMarkdown } from "../../../lib/utility/text/TextUtils"; 11 | import { Zephyr } from "../../../structures/client/Zephyr"; 12 | 13 | export default class ClubMembers extends BaseCommand { 14 | id = `killer`; 15 | names = [`members`]; 16 | description = `Shows you a list of a club's members. You must be in the club to run this command.`; 17 | usage = [`$CMD$ `]; 18 | allowDm = true; 19 | 20 | async exec( 21 | msg: Message, 22 | _profile: GameProfile, 23 | options: string[] 24 | ): Promise { 25 | const clubName = options.join(` `); 26 | 27 | if (!clubName) throw new ClubError.InvalidClubNameInViewerError(); 28 | 29 | const club = await getClubByName(clubName); 30 | 31 | if (!club) throw new ClubError.NoClubByNameError(); 32 | 33 | const clubMembers = await getClubMembers(club); 34 | 35 | if (!clubMembers.find((m) => m.discordId === msg.author.id)) 36 | throw new ClubError.NotMemberOfClubError(); 37 | 38 | const owners = []; 39 | const moderators = []; 40 | const members = []; 41 | 42 | for (let member of clubMembers) { 43 | const user = await Zephyr.fetchUser(member.discordId); 44 | 45 | const isOwner = member.discordId === club.ownerId; 46 | const isMod = member.isMod; 47 | 48 | let emoji = `bust_in_silhouette`; 49 | if (isOwner) { 50 | emoji = `crown`; 51 | } else if (isMod) { 52 | emoji = `shield`; 53 | } 54 | 55 | const final = 56 | `:${emoji}: **${escapeMarkdown(user?.tag || `Unknown User`)}**` + 57 | (member.discordId === msg.author.id ? ` :point_left:` : ``); 58 | 59 | if (isOwner) { 60 | owners.push(final); 61 | } else if (isMod) { 62 | moderators.push(final); 63 | } else members.push(final); 64 | } 65 | 66 | const embed = new MessageEmbed(`Club Members`, msg.author) 67 | .setTitle( 68 | `Members of ${club.name} (${members.length}/${club.memberLimit})` 69 | ) 70 | .setDescription([...owners, ...moderators, ...members].join(`\n`)); 71 | 72 | await this.send(msg.channel, embed); 73 | 74 | return; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/commands/game/club/JoinClub.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { GameProfile } from "../../../structures/game/Profile"; 4 | import { ClubError } from "../../../structures/error/ClubError"; 5 | import { 6 | getClubByName, 7 | getClubMembers, 8 | getUserClubMembership, 9 | } from "../../../lib/database/sql/game/club/ClubGetter"; 10 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 11 | import { addUserToClub } from "../../../lib/database/sql/game/club/ClubSetter"; 12 | import { escapeMarkdown } from "../../../lib/utility/text/TextUtils"; 13 | import { Zephyr } from "../../../structures/client/Zephyr"; 14 | 15 | export default class JoinClub extends BaseCommand { 16 | id = `jumpsuit`; 17 | names = [`join`]; 18 | description = `Joins a club, if it's open and below the member limit.`; 19 | usage = [`$CMD$ `]; 20 | allowDm = true; 21 | 22 | async exec( 23 | msg: Message, 24 | profile: GameProfile, 25 | options: string[] 26 | ): Promise { 27 | const clubName = options.join(` `); 28 | 29 | if (!clubName) throw new ClubError.InvalidClubNameInViewerError(); 30 | 31 | const club = await getClubByName(clubName); 32 | 33 | if (!club) throw new ClubError.NoClubByNameError(); 34 | 35 | const clubMembers = await getClubMembers(club); 36 | 37 | if (clubMembers.find((m) => m.discordId === msg.author.id)) 38 | throw new ClubError.AlreadyInClubError(); 39 | 40 | if (!club.open) throw new ClubError.ClubClosedError(); 41 | if (clubMembers.length >= club.memberLimit) 42 | throw new ClubError.ClubAtCapacityError(); 43 | 44 | const userMembership = await getUserClubMembership(profile); 45 | 46 | if (userMembership.length > Zephyr.modifiers.userClubMembershipLimit) 47 | throw new ClubError.ClubLimitError(); 48 | 49 | await addUserToClub(club, profile); 50 | 51 | const embed = new MessageEmbed(`Join Club`, msg.author).setDescription( 52 | `:tada: You have joined **${escapeMarkdown(club.name)}**.` 53 | ); 54 | 55 | await this.send(msg.channel, embed); 56 | return; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/commands/game/club/LeaveClub.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { GameProfile } from "../../../structures/game/Profile"; 4 | import { ClubError } from "../../../structures/error/ClubError"; 5 | import { 6 | getClubByName, 7 | getClubMembers, 8 | } from "../../../lib/database/sql/game/club/ClubGetter"; 9 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 10 | import { kickUserFromClub } from "../../../lib/database/sql/game/club/ClubSetter"; 11 | import { escapeMarkdown } from "../../../lib/utility/text/TextUtils"; 12 | import { Zephyr } from "../../../structures/client/Zephyr"; 13 | 14 | export default class LeaveClub extends BaseCommand { 15 | id = `drive`; 16 | names = [`leave`]; 17 | description = `Leaves a club.`; 18 | usage = [`$CMD$ `]; 19 | allowDm = true; 20 | 21 | async exec( 22 | msg: Message, 23 | _profile: GameProfile, 24 | options: string[] 25 | ): Promise { 26 | const clubName = options.join(` `); 27 | 28 | if (!clubName) throw new ClubError.InvalidClubNameInViewerError(); 29 | 30 | const club = await getClubByName(clubName); 31 | 32 | if (!club) throw new ClubError.NoClubByNameError(); 33 | 34 | const clubMembers = await getClubMembers(club); 35 | const membership = clubMembers.find( 36 | (member) => member.discordId === msg.author.id 37 | ); 38 | 39 | if (!membership) throw new ClubError.NotMemberOfClubError(); 40 | 41 | if (club.ownerId === msg.author.id) { 42 | const prefix = Zephyr.getPrefix(msg.guildID); 43 | 44 | throw new ClubError.OwnerCannotLeaveClubError(prefix); 45 | } 46 | 47 | await kickUserFromClub(membership); 48 | 49 | const embed = new MessageEmbed(`Join Club`, msg.author).setDescription( 50 | `:wave: You have left **${escapeMarkdown(club.name)}**.` 51 | ); 52 | 53 | await this.send(msg.channel, embed); 54 | return; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/commands/game/club/MyClubs.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { GameProfile } from "../../../structures/game/Profile"; 4 | import { 5 | getClubMembers, 6 | getUserClubMembership, 7 | } from "../../../lib/database/sql/game/club/ClubGetter"; 8 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 9 | import { escapeMarkdown } from "../../../lib/utility/text/TextUtils"; 10 | import { Zephyr } from "../../../structures/client/Zephyr"; 11 | 12 | export default class MyClubs extends BaseCommand { 13 | id = `basket`; 14 | names = [`myclubs`]; 15 | description = `Shows a list of clubs you're a member of.`; 16 | usage = [`$CMD$`]; 17 | allowDm = true; 18 | 19 | async exec(msg: Message, profile: GameProfile): Promise { 20 | const clubs = await getUserClubMembership(profile); 21 | 22 | const embed = new MessageEmbed(`My Clubs`, msg.author).setDescription( 23 | `${msg.author.tag}'s clubs` 24 | ); 25 | 26 | if (clubs.length === 0) { 27 | const prefix = Zephyr.getPrefix(msg.guildID); 28 | 29 | embed.setDescription( 30 | `**You aren't a member of any clubs!**\nUse \`${prefix}join \` to join one!` 31 | ); 32 | 33 | await this.send(msg.channel, embed); 34 | } 35 | 36 | const owner = []; 37 | const moderator = []; 38 | const member = []; 39 | 40 | for (let club of clubs) { 41 | const isOwner = club.ownerId === msg.author.id; 42 | 43 | const members = await getClubMembers(club); 44 | 45 | const isMod = members.find((m) => m.discordId === msg.author.id)?.isMod; 46 | 47 | if (isOwner) { 48 | owner.push( 49 | `:crown: **${escapeMarkdown(club.name)}** (${members.length}/${ 50 | club.memberLimit 51 | })` 52 | ); 53 | } else if (isMod) { 54 | moderator.push( 55 | `:shield: **${escapeMarkdown(club.name)}** (${members.length}/${ 56 | club.memberLimit 57 | })` 58 | ); 59 | } else { 60 | member.push( 61 | `:bust_in_silhouette: **${escapeMarkdown(club.name)}** (${ 62 | members.length 63 | }/${club.memberLimit})` 64 | ); 65 | } 66 | } 67 | 68 | embed 69 | .setTitle(`${msg.author.tag}'s clubs`) 70 | .setDescription([...owner, ...moderator, ...member].join(`\n`)); 71 | 72 | await this.send(msg.channel, embed); 73 | return; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/commands/game/club/ViewClub.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { GameProfile } from "../../../structures/game/Profile"; 4 | import { ClubError } from "../../../structures/error/ClubError"; 5 | import { 6 | getClubByName, 7 | getClubMembers, 8 | } from "../../../lib/database/sql/game/club/ClubGetter"; 9 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 10 | import { Zephyr } from "../../../structures/client/Zephyr"; 11 | import { escapeMarkdown } from "../../../lib/utility/text/TextUtils"; 12 | 13 | export default class ViewClub extends BaseCommand { 14 | id = `lithium`; 15 | names = [`club`, `c`]; 16 | description = `Shows you information about a club.`; 17 | usage = [`$CMD$ `]; 18 | allowDm = true; 19 | 20 | async exec( 21 | msg: Message, 22 | _profile: GameProfile, 23 | options: string[] 24 | ): Promise { 25 | const clubName = options.join(` `); 26 | 27 | if (!clubName) throw new ClubError.InvalidClubNameInViewerError(); 28 | 29 | const club = await getClubByName(clubName); 30 | 31 | if (!club) throw new ClubError.NoClubByNameError(); 32 | 33 | const owner = await Zephyr.fetchUser(club.ownerId); 34 | const clubMembers = await getClubMembers(club); 35 | 36 | const embed = new MessageEmbed(`Club`, msg.author) 37 | .setTitle(club.name) 38 | .setDescription( 39 | `${club.blurb || `This club has no blurb...`}\n` + 40 | `\n**Owner**: ${escapeMarkdown(owner?.tag || `Unknown User`)}` + 41 | `\n**Membership**: ${clubMembers.length}/${club.memberLimit}` + 42 | `\n**Open?**: ${club.open ? `Yes` : `No`}` 43 | ); 44 | 45 | await this.send(msg.channel, embed); 46 | return; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/commands/game/items/ItemInfo.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { GameProfile } from "../../../structures/game/Profile"; 4 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 5 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 6 | import { getItemByName, getItemIndex } from "../../../assets/Items"; 7 | import { Zephyr } from "../../../structures/client/Zephyr"; 8 | 9 | export default class ItemInfo extends BaseCommand { 10 | id = `nightvision`; 11 | names = ["iteminfo", "ii"]; 12 | usage = ["$CMD$ "]; 13 | description = 14 | "Shows information about an item. Includes aliases and a short description."; 15 | allowDm = true; 16 | 17 | async exec( 18 | msg: Message, 19 | _profile: GameProfile, 20 | options: string[] 21 | ): Promise { 22 | if (!options[0]) throw new ZephyrError.UnspecifiedItemError(); 23 | 24 | const itemQuery = options 25 | .filter((i) => i.toLowerCase() !== "--dev") 26 | .join(" ") 27 | .toLowerCase(); 28 | const targetItem = getItemByName(itemQuery); 29 | 30 | if (!targetItem) throw new ZephyrError.InvalidItemError(); 31 | 32 | const prefix = Zephyr.getPrefix(msg.guildID); 33 | const embed = new MessageEmbed(`Item Info`, msg.author).setDescription( 34 | `**${targetItem.names[0]}**` + 35 | (targetItem.names.length > 1 36 | ? `\n*also known as:* \`${targetItem.names 37 | .slice(1) 38 | .join(`\`, \``)}\`\n` 39 | : ``) + 40 | (targetItem.description ? `\n*${targetItem.description}*` : ``) + 41 | (targetItem.usage 42 | ? `\n\n**Usage**: \`${prefix}${targetItem.usage.replace( 43 | `$BASE$`, 44 | `use ${targetItem.names[0]}` 45 | )}\`` 46 | : ``) 47 | ); 48 | 49 | if (options.map((o) => o.toLowerCase()).includes("--dev")) 50 | embed.setFooter( 51 | `id ${targetItem.id} / use?: ${!!targetItem.use} / idx ${getItemIndex( 52 | targetItem 53 | )}` 54 | ); 55 | 56 | await this.send(msg.channel, embed); 57 | 58 | return; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/commands/game/items/ShowRecipes.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import recipes from "../../../assets/recipes.json"; 4 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 5 | import { renderRecipe } from "../../../lib/utility/text/TextUtils"; 6 | import { Zephyr } from "../../../structures/client/Zephyr"; 7 | 8 | export default class ShowRecipes extends BaseCommand { 9 | id = `voyager`; 10 | names = ["recipes"]; 11 | description = "Shows recipes available to craft."; 12 | allowDm = true; 13 | 14 | async exec(msg: Message): Promise { 15 | const embed = new MessageEmbed(`Recipes`, msg.author); 16 | 17 | const prefix = Zephyr.getPrefix(msg.guildID!); 18 | 19 | for (let recipe of recipes) { 20 | embed.addField({ 21 | name: recipe.name, 22 | value: `\`${prefix}craft ${recipe.query}\`\n` + renderRecipe(recipe), 23 | inline: true, 24 | }); 25 | } 26 | 27 | await this.send(msg.channel, embed); 28 | return; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/commands/game/player/Balance.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { ProfileService } from "../../../lib/database/services/game/ProfileService"; 3 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 4 | import { BaseCommand } from "../../../structures/command/Command"; 5 | import { GameProfile } from "../../../structures/game/Profile"; 6 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 7 | import { Zephyr } from "../../../structures/client/Zephyr"; 8 | 9 | export default class Balance extends BaseCommand { 10 | id = `spirit`; 11 | names = ["balance", "bits", "$", "bal", "cubits"]; 12 | description = "Shows you your balance."; 13 | allowDm = true; 14 | 15 | async exec( 16 | msg: Message, 17 | profile: GameProfile, 18 | options: string[] 19 | ): Promise { 20 | let targetUser, targetProfile; 21 | if (msg.mentions[0]) { 22 | targetUser = msg.mentions[0]; 23 | targetProfile = await ProfileService.getProfile(targetUser.id); 24 | } else if (!isNaN(parseInt(options[0]))) { 25 | if (options[0].length < 17) throw new ZephyrError.InvalidSnowflakeError(); 26 | 27 | targetUser = await Zephyr.fetchUser(options[0]); 28 | if (!targetUser) throw new ZephyrError.InvalidSnowflakeError(); 29 | 30 | targetProfile = await ProfileService.getProfile(targetUser.id); 31 | } else { 32 | targetUser = msg.author; 33 | targetProfile = profile; 34 | } 35 | 36 | if (targetProfile.private && targetProfile.discordId !== msg.author.id) 37 | throw new ZephyrError.PrivateProfileError(targetUser.tag); 38 | 39 | const embed = new MessageEmbed(`Balance`, msg.author) 40 | .setTitle(`${targetUser.tag}'s balance`) 41 | .addFields([ 42 | { 43 | name: `${Zephyr.config.discord.emoji.blank} Bits`, 44 | value: `${ 45 | Zephyr.config.discord.emoji.bits 46 | } \`${targetProfile.bits.toLocaleString()}\``, 47 | inline: true, 48 | }, 49 | { 50 | name: `${Zephyr.config.discord.emoji.blank} Cubits`, 51 | value: `:package: \`${targetProfile.cubits.toLocaleString()}\``, 52 | inline: true, 53 | }, 54 | ]); 55 | 56 | await this.send(msg.channel, embed); 57 | return; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/commands/game/player/Cooldowns.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | import { Message } from "eris"; 3 | import { ProfileService } from "../../../lib/database/services/game/ProfileService"; 4 | import { getTimeUntil } from "../../../lib/utility/time/TimeUtils"; 5 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 6 | import { Zephyr } from "../../../structures/client/Zephyr"; 7 | import { BaseCommand } from "../../../structures/command/Command"; 8 | import { GameProfile } from "../../../structures/game/Profile"; 9 | export default class Cooldowns extends BaseCommand { 10 | id = `absolution`; 11 | names = ["cooldowns", "cd", "timer", "timers", "t"]; 12 | description = "Shows the status of various timers."; 13 | allowDm = true; 14 | 15 | async exec(msg: Message, profile: GameProfile): Promise { 16 | const prefix = Zephyr.getPrefix(msg.guildID); 17 | 18 | const today = dayjs(Date.now()); 19 | const todayFormat = today.format(`YYYY-MM-DD`); 20 | const dailyLast = dayjs(profile.dailyLast).format(`YYYY-MM-DD`); 21 | 22 | const now = dayjs(Date.now()); 23 | 24 | let timerString = `\`${prefix}daily\` **Daily Reward**: __${ 25 | todayFormat === dailyLast 26 | ? getTimeUntil( 27 | now, 28 | dayjs(Date.now()).add(1, "day").startOf("day") || "Now" 29 | ) 30 | : `Now` 31 | }__\n\` ${prefix}vote\` **Vote**: __${ 32 | getTimeUntil(now, dayjs(profile.voteLast || 0).add(12, "hour")) || "Now" 33 | }__\n\` ${prefix}drop\` **Drop**: __${ 34 | getTimeUntil(now, dayjs(profile.dropNext)) || "Now" 35 | }__\n\`\` **Claim**: __${ 36 | getTimeUntil(now, dayjs(profile.claimNext)) || "Now" 37 | }__ 38 | `; 39 | 40 | if (profile.boosterGroup && profile.boosterUses) { 41 | if (profile.boosterUses === 0) { 42 | await ProfileService.clearBooster(profile); 43 | } else { 44 | const group = Zephyr.getGroupById(profile.boosterGroup); 45 | 46 | timerString += `\nYou have __${profile.boosterUses} uses__ remaining on your boost for **${group}**.`; 47 | } 48 | } 49 | 50 | const embed = new MessageEmbed(`Cooldowns`, msg.author) 51 | .setTitle(`${msg.author.tag}'s Cooldowns`) 52 | .setDescription(timerString); 53 | 54 | await this.send(msg.channel, embed); 55 | return; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/commands/game/player/SetActiveCard.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { GameProfile } from "../../../structures/game/Profile"; 4 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 5 | import { CardService } from "../../../lib/database/services/game/CardService"; 6 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 7 | import { ProfileSetter } from "../../../lib/database/sql/game/profile/ProfileSetter"; 8 | import { GameUserCard } from "../../../structures/game/UserCard"; 9 | 10 | export default class SetActiveCard extends BaseCommand { 11 | id = `humility`; 12 | names = [`setactive`, `sa`]; 13 | description = `Marks a card as your **Active**, enabling experience gain.`; 14 | usage = [`$CMD$ `]; 15 | allowDm = true; 16 | 17 | async exec( 18 | msg: Message, 19 | profile: GameProfile, 20 | options: string[] 21 | ): Promise { 22 | let targetCard: GameUserCard; 23 | 24 | if (!options[0]) { 25 | const lastCard = await CardService.getLastCard(profile); 26 | 27 | targetCard = lastCard; 28 | } else { 29 | targetCard = await CardService.getUserCardByIdentifier(options[0]); 30 | } 31 | 32 | if (targetCard.discordId !== msg.author.id) 33 | throw new ZephyrError.NotOwnerOfCardError(targetCard); 34 | 35 | await ProfileSetter.setActiveCard(profile, targetCard); 36 | 37 | const embed = new MessageEmbed(`Active Card`, msg.author).setDescription( 38 | `:white_check_mark: Your **Active** card was set to \`${targetCard.id.toString( 39 | 36 40 | )}\`.` 41 | ); 42 | await this.send(msg.channel, embed); 43 | 44 | return; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/commands/game/player/SetBlurb.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { ProfileService } from "../../../lib/database/services/game/ProfileService"; 3 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 4 | import { BaseCommand } from "../../../structures/command/Command"; 5 | import { GameProfile } from "../../../structures/game/Profile"; 6 | 7 | export default class SetBlurb extends BaseCommand { 8 | id = `genesis`; 9 | names = ["blurb", "setblurb", "desc", "setdesc"]; 10 | description = "Changes the blurb on your profile."; 11 | usage = ["$CMD$ "]; 12 | allowDm = true; 13 | 14 | async exec( 15 | msg: Message, 16 | profile: GameProfile, 17 | options: string[] 18 | ): Promise { 19 | const originalLength = options.join(` `).length; 20 | const blurb = options.join(" ").slice(0, 500); 21 | await ProfileService.setProfileBlurb(profile, blurb); 22 | 23 | const embed = new MessageEmbed(`Blurb`, msg.author).setDescription( 24 | (originalLength > 500 25 | ? `:warning: Your blurb has been shortened to 500 characters.\n` 26 | : ``) + 27 | `Updated your blurb to:` + 28 | `\n\`\`\`` + 29 | `\n${blurb || "empty blurb"}` + 30 | `\n\`\`\`` 31 | ); 32 | 33 | await this.send(msg.channel, embed); 34 | return; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/commands/game/player/TogglePrivate.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { ProfileService } from "../../../lib/database/services/game/ProfileService"; 3 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 4 | import { BaseCommand } from "../../../structures/command/Command"; 5 | import { GameProfile } from "../../../structures/game/Profile"; 6 | 7 | export default class TogglePrivate extends BaseCommand { 8 | id = `bible`; 9 | names = ["private", "priv", "public", "pub"]; 10 | description = "Toggles your account between private and public."; 11 | allowDm = true; 12 | 13 | async exec(msg: Message, profile: GameProfile): Promise { 14 | const newProfile = await ProfileService.togglePrivateProfile(profile); 15 | const embed = new MessageEmbed(`Profile`, msg.author).setDescription( 16 | `Your profile is now ${newProfile.private ? `private` : `public`}.` 17 | ); 18 | 19 | await this.send(msg.channel, embed); 20 | return; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/commands/game/player/ViewDye.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { ProfileService } from "../../../lib/database/services/game/ProfileService"; 3 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 4 | import { BaseCommand } from "../../../structures/command/Command"; 5 | import { GameProfile } from "../../../structures/game/Profile"; 6 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 7 | import { rgbToHex } from "../../../lib/utility/color/ColorUtils"; 8 | import { createCanvas } from "canvas"; 9 | import { Zephyr } from "../../../structures/client/Zephyr"; 10 | 11 | export default class ViewDye extends BaseCommand { 12 | id = `faith`; 13 | names = ["viewdye", "vd"]; 14 | description = "Shows you a dye you own."; 15 | allowDm = true; 16 | 17 | async exec( 18 | msg: Message, 19 | _profile: GameProfile, 20 | options: string[] 21 | ): Promise { 22 | if (!options[0] || !options[0].startsWith("$")) 23 | throw new ZephyrError.InvalidDyeIdentifierError(); 24 | 25 | const dyeId = options[0]?.toLowerCase(); 26 | const dyeTarget = await ProfileService.getDyeByIdentifier(dyeId); 27 | 28 | const dyeOwner = await Zephyr.fetchUser(dyeTarget.discordId); 29 | const dyeOwnerProfile = await ProfileService.getProfile( 30 | dyeOwner?.id || dyeTarget.discordId 31 | ); 32 | 33 | const dyeHex = rgbToHex(dyeTarget.dyeR, dyeTarget.dyeG, dyeTarget.dyeB); 34 | 35 | const canvas = createCanvas(100, 100); 36 | const ctx = canvas.getContext("2d"); 37 | 38 | ctx.fillStyle = dyeHex; 39 | ctx.fillRect(0, 0, 100, 100); 40 | 41 | const buffer = canvas.toBuffer("image/jpeg"); 42 | 43 | const embed = new MessageEmbed(`View Dye`, msg.author) 44 | .setDescription( 45 | `Viewing dye \`$${dyeTarget.id.toString(36)}\` **${ 46 | dyeTarget.name 47 | }**...` + 48 | `\n— Owned by ${ 49 | dyeOwnerProfile.private && 50 | dyeOwnerProfile.discordId !== msg.author.id 51 | ? `*Private User*` 52 | : dyeOwner 53 | ? `**${dyeOwner.tag}**` 54 | : `*Unknown User*` 55 | }` + 56 | `\n— Charges: \`${dyeTarget.charges.toLocaleString()}\`` + 57 | `\n— Hex: **${dyeHex.toUpperCase()}**` 58 | ) 59 | .setThumbnail(`attachment://dyepreview.png`) 60 | .setColor(dyeHex); 61 | 62 | await this.send(msg.channel, embed, { 63 | files: [{ file: buffer, name: `dyepreview.png` }], 64 | }); 65 | return; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/commands/game/poll/AnswerPoll.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | import { Message } from "eris"; 3 | import { PollService } from "../../../lib/database/services/meta/PollService"; 4 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 5 | import { BaseCommand } from "../../../structures/command/Command"; 6 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 7 | import { GameProfile } from "../../../structures/game/Profile"; 8 | 9 | export default class AnswerPoll extends BaseCommand { 10 | id = `butane`; 11 | names = [`answer`]; 12 | description = `Answers a poll if it's active.`; 13 | usage = [`$CMD$ `]; 14 | allowDm = true; 15 | 16 | async exec( 17 | msg: Message, 18 | profile: GameProfile, 19 | options: string[] 20 | ): Promise { 21 | const pollId = parseInt(options[0]); 22 | if (!pollId || pollId < 1) throw new ZephyrError.InvalidPollIdError(); 23 | 24 | const poll = await PollService.getPollById(pollId); 25 | 26 | if (poll.endsAt) { 27 | const now = dayjs(); 28 | const end = dayjs(poll.endsAt); 29 | 30 | if (now >= end) throw new ZephyrError.PollInactiveError(poll); 31 | } 32 | 33 | if (!poll.active) throw new ZephyrError.PollInactiveError(poll); 34 | 35 | const answered = await PollService.userAnsweredPoll(poll, profile); 36 | if (answered) throw new ZephyrError.PollAnsweredError(poll); 37 | 38 | const answer = options[1]?.toLowerCase(); 39 | 40 | if (![`yes`, `no`].includes(answer)) 41 | throw new ZephyrError.InvalidPollAnswerError(); 42 | 43 | await PollService.answerPoll(poll, profile, answer as `yes` | `no`); 44 | 45 | const embed = new MessageEmbed(`Answer Poll`, msg.author) 46 | .setTitle(`Poll #${poll.id}: ${poll.title}`) 47 | .setDescription( 48 | `You voted **${answer}** on this poll. Thanks for voting!` 49 | ); 50 | await this.send(msg.channel, embed); 51 | 52 | return; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/commands/game/poll/ViewPoll.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | import { Message } from "eris"; 3 | import { PollService } from "../../../lib/database/services/meta/PollService"; 4 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 5 | import { Zephyr } from "../../../structures/client/Zephyr"; 6 | import { BaseCommand } from "../../../structures/command/Command"; 7 | import { GameProfile } from "../../../structures/game/Profile"; 8 | 9 | export default class ViewPoll extends BaseCommand { 10 | id = `xxok`; 11 | names = [`poll`]; 12 | description = `Shows you the current active poll if there is one.`; 13 | usage = [`$CMD$`]; 14 | allowDm = true; 15 | 16 | async exec(msg: Message, profile: GameProfile): Promise { 17 | const polls = await PollService.getActivePolls(); 18 | const poll = polls[0]; 19 | 20 | if (!poll) { 21 | const embed = new MessageEmbed(`Poll`, msg.author).setDescription( 22 | `:confused: There are currently no active polls.\n\nCheck back later, or join [Zephyr Community](https://discord.gg/zephyr) to be notified when they open!` 23 | ); 24 | 25 | await this.send(msg.channel, embed); 26 | return; 27 | } 28 | 29 | // const standings = await PollService.getPollStandings(poll); 30 | const userVoted = await PollService.userAnsweredPoll(poll, profile); 31 | 32 | let footer; 33 | if (poll.endsAt) { 34 | const timestamp = dayjs(poll.endsAt).format(`MMMM D, YYYY [at] HH:mm:ss`); 35 | footer = `This poll will end at ${timestamp} UTC.`; 36 | } else footer = `This poll has no end date set.`; 37 | 38 | const prefix = Zephyr.getPrefix(msg.guildID); 39 | const embed = new MessageEmbed(`Poll`, msg.author) 40 | .setTitle(`Poll #${poll.id}: ${poll.title}`) 41 | .setDescription( 42 | `${poll.body}\n\n${ 43 | !userVoted 44 | ? `:ballot_box: To vote, use \`${prefix}answer ${poll.id} yes/no\`!` 45 | : `__You have already voted for this poll.__` 46 | }` 47 | ) 48 | .setFooter(footer); 49 | 50 | await this.send(msg.channel, embed); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/commands/game/shop/Shop.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 3 | import { Zephyr } from "../../../structures/client/Zephyr"; 4 | import { BaseCommand } from "../../../structures/command/Command"; 5 | 6 | export default class Shop extends BaseCommand { 7 | id = `dreamer`; 8 | names = [`shop`]; 9 | description = `The shop has been split into two different commands! This command will show you where to find them.`; 10 | allowDm = true; 11 | 12 | async exec(msg: Message): Promise { 13 | const prefix = Zephyr.getPrefix(msg.guildID); 14 | 15 | const embed = new MessageEmbed(`Shop`, msg.author).setDescription( 16 | `**The shops have moved to new locations!**\n— You can use \`${prefix}stickershop\` to view the sticker shop.\n— You can use \`${prefix}itemshop\` to view the item shop.` 17 | ); 18 | 19 | await this.send(msg.channel, embed); 20 | return; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/commands/game/tags/DeleteTag.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { GameProfile } from "../../../structures/game/Profile"; 4 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 5 | import { ProfileService } from "../../../lib/database/services/game/ProfileService"; 6 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 7 | 8 | export default class DeleteTag extends BaseCommand { 9 | id = `acdc`; 10 | names = [`deletetag`, `dt`]; 11 | description = `Deletes a tag and removes it from all of your cards.`; 12 | usage = [`$CMD$ `]; 13 | allowDm = true; 14 | 15 | async exec( 16 | msg: Message, 17 | profile: GameProfile, 18 | options: string[] 19 | ): Promise { 20 | if (!options[0]) throw new ZephyrError.UnspecifiedTagError(); 21 | 22 | const userTags = await ProfileService.getTags(profile); 23 | 24 | const tagName = options[0].toLowerCase(); 25 | const tag = userTags.find((t) => t.name === tagName); 26 | 27 | if (!tag) throw new ZephyrError.TagNotFoundError(tagName); 28 | 29 | await ProfileService.deleteTag(tag); 30 | 31 | const embed = new MessageEmbed(`Delete Tag`, msg.author).setDescription( 32 | `The tag ${tag.emoji} **${tag.name}** was deleted.` 33 | ); 34 | 35 | await this.send(msg.channel, embed); 36 | return; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/commands/game/tags/UntagCard.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { GameProfile } from "../../../structures/game/Profile"; 4 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 5 | import { CardService } from "../../../lib/database/services/game/CardService"; 6 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 7 | 8 | export default class UntagCard extends BaseCommand { 9 | id = `mango`; 10 | names = [`untag`, `ut`]; 11 | description = `Removes the tag from a card, if it has one.`; 12 | usage = [`$CMD$ `]; 13 | allowDm = true; 14 | 15 | async exec( 16 | msg: Message, 17 | _profile: GameProfile, 18 | options: string[] 19 | ): Promise { 20 | if (!options[0]) throw new ZephyrError.InvalidCardReferenceError(); 21 | 22 | const cardsRaw = []; 23 | const identifiers = options; 24 | 25 | for (let i of identifiers) { 26 | const card = await CardService.getUserCardByIdentifier(i); 27 | 28 | if (card.discordId !== msg.author.id) 29 | throw new ZephyrError.NotOwnerOfCardError(card); 30 | 31 | if (!card.tagId) continue; 32 | 33 | cardsRaw.push(card); 34 | } 35 | 36 | const cards = cardsRaw.filter((c) => c.tagId); 37 | 38 | if (cards.length === 0) 39 | throw new ZephyrError.CardsNotTaggedError(cardsRaw.length > 1); 40 | 41 | await CardService.unsetCardsTag(cards); 42 | 43 | const embed = new MessageEmbed(`Untag`, msg.author).setDescription( 44 | `Removed tags from **${cards.length}** card${ 45 | cards.length > 1 ? `s` : `` 46 | }.` 47 | ); 48 | 49 | await this.send(msg.channel, embed); 50 | return; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/commands/game/tags/ViewTags.ts: -------------------------------------------------------------------------------- 1 | import { Message, User } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { GameProfile } from "../../../structures/game/Profile"; 4 | import { ProfileService } from "../../../lib/database/services/game/ProfileService"; 5 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 6 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 7 | import { Zephyr } from "../../../structures/client/Zephyr"; 8 | 9 | export default class ViewTags extends BaseCommand { 10 | id = `campfire`; 11 | names = [`viewtags`, `tags`, `vt`]; 12 | description = `Shows you a list of a user's tags.`; 13 | usage = [`$CMD$ [@mention/user id]`]; 14 | allowDm = true; 15 | 16 | async exec( 17 | msg: Message, 18 | profile: GameProfile, 19 | options: string[] 20 | ): Promise { 21 | let target: GameProfile | undefined; 22 | let targetUser: User | undefined; 23 | 24 | if (msg.mentions[0]) { 25 | targetUser = msg.mentions[0]; 26 | } else if (!isNaN(parseInt(options[0]))) { 27 | if (options[0].length < 17 || options[0].length > 18) 28 | throw new ZephyrError.InvalidSnowflakeError(); 29 | 30 | const fetchUser = await Zephyr.fetchUser(options[0]); 31 | if (!fetchUser) throw new ZephyrError.UserNotFoundError(); 32 | 33 | targetUser = fetchUser; 34 | } else { 35 | targetUser = msg.author; 36 | target = profile; 37 | } 38 | 39 | if (!target) target = await ProfileService.getProfile(targetUser.id); 40 | 41 | if (target.private && target.discordId !== msg.author.id) 42 | throw new ZephyrError.PrivateProfileError(targetUser.tag); 43 | 44 | const userTags = await ProfileService.getTags(target); 45 | userTags.sort((a, b) => (a.name > b.name ? 1 : -1)); 46 | 47 | const embed = new MessageEmbed(`Tags`, msg.author).setTitle( 48 | `${targetUser.tag}'s tags` 49 | ); 50 | 51 | if (userTags.length === 0) { 52 | if (targetUser.id === msg.author.id) { 53 | const prefix = Zephyr.getPrefix(msg.guildID); 54 | embed.setDescription( 55 | `You have no tags!\nUse \`${prefix}createtag \` to make one.` 56 | ); 57 | } else { 58 | embed.setDescription(`**${targetUser.tag}** has no tags.`); 59 | } 60 | } else { 61 | embed.setDescription( 62 | userTags.map((t) => `${t.emoji} — \`${t.name}\``).join(`\n`) 63 | ); 64 | } 65 | 66 | await this.send(msg.channel, embed); 67 | return; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/commands/guild/Prefix.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { GuildService } from "../../lib/database/services/guild/GuildService"; 3 | import { BaseCommand } from "../../structures/command/Command"; 4 | import { GameProfile } from "../../structures/game/Profile"; 5 | import * as ZephyrError from "../../structures/error/ZephyrError"; 6 | import { Zephyr } from "../../structures/client/Zephyr"; 7 | 8 | export default class Prefix extends BaseCommand { 9 | id = `echo`; 10 | names = ["prefix"]; 11 | description = 12 | `Changes the prefix of the bot.` + 13 | `\nRequires the **Manage Channels** permission.`; 14 | 15 | async exec( 16 | msg: Message, 17 | _profile: GameProfile, 18 | options: string[] 19 | ): Promise { 20 | const prefix = options[0]?.toLowerCase(); 21 | if (prefix?.length > 8) throw new ZephyrError.PrefixTooLongError(); 22 | 23 | const guild = Zephyr.guilds.get(msg.guildID!); 24 | const author = guild?.members.get(msg.author.id)!; 25 | if (!prefix) { 26 | const currentPrefix = Zephyr.getPrefix(guild!.id); 27 | await this.send( 28 | msg.channel, 29 | `The prefix ${ 30 | guild ? `for **${guild.name}** ` : `` 31 | }is \`${currentPrefix}\`.` 32 | ); 33 | return; 34 | } 35 | if (!author?.permission.json["manageChannels"]) return; 36 | 37 | await GuildService.setPrefix(guild!.id!, prefix); 38 | Zephyr.setPrefix(guild!.id!, prefix); 39 | await this.send(msg.channel, `Set the prefix to \`${prefix}\`.`); 40 | return; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/commands/guild/SetChannel.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { GuildService } from "../../lib/database/services/guild/GuildService"; 3 | import { Zephyr } from "../../structures/client/Zephyr"; 4 | import { BaseCommand } from "../../structures/command/Command"; 5 | 6 | export default class SetChannel extends BaseCommand { 7 | id = `ashley`; 8 | names = ["setchannel", "sch"]; 9 | description = 10 | `Sets the dedicated Zephyr channel, for drops and other things.` + 11 | `\nRequires the **Manage Channels** permission.`; 12 | 13 | async exec(msg: Message): Promise { 14 | const guild = Zephyr.guilds.get(msg.guildID!); 15 | const author = guild?.members.get(msg.author.id)!; 16 | 17 | if (!author?.permission.json["manageChannels"]) return; 18 | 19 | let channelClean = msg.channel.id; 20 | await GuildService.setDropChannel(msg.guildID!, channelClean); 21 | 22 | await this.send( 23 | msg.channel, 24 | `Set the Zephyr channel to <#${channelClean}>` 25 | ); 26 | return; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/commands/info/Birthdays.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | import { Message } from "eris"; 3 | import { getGroupsByIdolId } from "../../lib/utility/text/TextUtils"; 4 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 5 | import { Zephyr } from "../../structures/client/Zephyr"; 6 | import { BaseCommand } from "../../structures/command/Command"; 7 | import { GameBaseCard } from "../../structures/game/BaseCard"; 8 | 9 | export default class Birthdays extends BaseCommand { 10 | id = `ice`; 11 | names = [`birthdays`, `bd`, `bday`]; 12 | description = `Shows a list of birthdays today.`; 13 | usage = ["$CMD$"]; 14 | allowDm = true; 15 | 16 | async exec(msg: Message): Promise { 17 | const today = dayjs().format(`MM-DD`); 18 | const birthdays: GameBaseCard[] = []; 19 | Zephyr.getCards().forEach((c) => { 20 | if (!birthdays.find((b) => b.idolId === c.idolId)) { 21 | if (today === c.birthday?.slice(5)) birthdays.push(c); 22 | } 23 | }); 24 | 25 | const embed = new MessageEmbed(`Birthdays`, msg.author) 26 | .setDescription( 27 | birthdays.length === 0 28 | ? `:confused: There are no birthdays today.` 29 | : `:birthday: Today's birthdays are...\n` + 30 | birthdays 31 | .map((c) => { 32 | const age = dayjs().year() - dayjs(c.birthday).year(); 33 | const groups = getGroupsByIdolId(c.idolId, Zephyr.getCards()); 34 | 35 | return `— ${age}: **${c.name}** (${groups.join(`, `)})`; 36 | }) 37 | .join("\n") 38 | ) 39 | .setFooter(`All idols have a 1.5x droprate on their birthday!`); 40 | 41 | await this.send(msg.channel, embed); 42 | return; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/commands/info/Invite.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 3 | import { BaseCommand } from "../../structures/command/Command"; 4 | 5 | export default class Invite extends BaseCommand { 6 | id = `papercut`; 7 | names = ["invite", "support"]; 8 | description = 9 | "Sends a link to the Zephyr Community server, and a link to add the bot."; 10 | usage = ["$CMD$"]; 11 | allowDm = true; 12 | 13 | async exec(msg: Message): Promise { 14 | const embed = new MessageEmbed(`Invite`, msg.author).setDescription( 15 | `You can add the bot to your server by [clicking here](https://discord.com/api/oauth2/authorize?client_id=791100707629432863&permissions=388160&scope=bot)!` + 16 | `\nYou can also join the Zephyr Community server at https://discord.gg/zephyr.` 17 | ); 18 | 19 | await this.send(msg.channel, embed); 20 | return; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/commands/info/Patreon.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 3 | import { BaseCommand } from "../../structures/command/Command"; 4 | 5 | export default class Patreon extends BaseCommand { 6 | id = `crawling`; 7 | names = ["patreon", "donate", "kofi", "ko-fi"]; 8 | description = "Sends a link to Zephyr's Patreon page."; 9 | usage = ["$CMD$"]; 10 | allowDm = true; 11 | 12 | async exec(msg: Message): Promise { 13 | const embed = new MessageEmbed(`Patreon`, msg.author).setDescription( 14 | `If you'd like to help fund the development of Zephyr and receive some cool perks, head over to the following link to become a patron!` + 15 | `\n— https://patreon.com/rtfl` 16 | ); 17 | 18 | await this.send(msg.channel, embed); 19 | return; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/commands/info/Vote.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 3 | import { Zephyr } from "../../structures/client/Zephyr"; 4 | import { BaseCommand } from "../../structures/command/Command"; 5 | import { GameProfile } from "../../structures/game/Profile"; 6 | 7 | export default class Vote extends BaseCommand { 8 | id = `runaway`; 9 | names = ["vote"]; 10 | description = "Sends a link to Zephyr's voting page."; 11 | usage = ["$CMD$"]; 12 | allowDm = true; 13 | 14 | async exec(msg: Message, _profile: GameProfile): Promise { 15 | const prefix = Zephyr.getPrefix(msg.guildID); 16 | const embed = new MessageEmbed(`Vote`, msg.author).setDescription( 17 | `You receive **2** cubits every time you vote.\nThis is doubled on weekends!\n— [Click here to vote!](https://top.gg/bot/791100707629432863/vote)` + 18 | `\n\n**Cubits** can be spent on various cosmetic rewards for your cards.\nCheck the \`${prefix}shop\` to view a full list of items!` 19 | ); 20 | 21 | await this.send(msg.channel, embed); 22 | return; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/commands/mod/blacklist/QuashCase.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { GameProfile } from "../../../structures/game/Profile"; 4 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 5 | import { ProfileService } from "../../../lib/database/services/game/ProfileService"; 6 | import { BlacklistService } from "../../../lib/database/services/meta/BlacklistService"; 7 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 8 | import { Zephyr } from "../../../structures/client/Zephyr"; 9 | 10 | export default class QuashCase extends BaseCommand { 11 | id = `one`; 12 | names = ["quash"]; 13 | description = 14 | "Quashes a blacklist case. In other words, it gets nullified and the user is unblacklisted."; 15 | usage = ["$CMD$ "]; 16 | allowDm = true; 17 | 18 | moderatorOnly = true; 19 | 20 | async exec( 21 | msg: Message, 22 | profile: GameProfile, 23 | options: string[] 24 | ): Promise { 25 | const caseId = parseInt(options[0]); 26 | 27 | if (isNaN(caseId) || caseId < 1) 28 | throw new ZephyrError.InvalidBlacklistIdError(); 29 | 30 | const blacklist = await BlacklistService.getBlacklistById(caseId); 31 | 32 | if (!blacklist.active) 33 | throw new ZephyrError.CaseAlreadyQuashedError(blacklist); 34 | 35 | const blacklistee = await Zephyr.fetchUser(blacklist.discordId); 36 | const blacklisteeProfile = await ProfileService.getProfile( 37 | blacklist.discordId 38 | ); 39 | 40 | const quashNote = options.slice(1).join(` `); 41 | if (quashNote.length < 1) throw new ZephyrError.InvalidQuashNoteError(); 42 | 43 | await ProfileService.unblacklistUser(blacklisteeProfile); 44 | await BlacklistService.quashBlacklist(blacklist, profile, quashNote); 45 | 46 | const embed = new MessageEmbed(`Quash`, msg.author) 47 | .setTitle(`Case #${caseId} - ${blacklistee?.tag || blacklist.discordId}`) 48 | .setDescription( 49 | `:unlock: Quashed \`Case #${blacklist.id}\` and unblacklisted **${ 50 | blacklistee?.tag || blacklist.discordId 51 | }**.` 52 | ); 53 | 54 | await this.send(msg.channel, embed); 55 | 56 | if (Zephyr.logChannel) { 57 | await this.send( 58 | Zephyr.logChannel, 59 | `:unlock: \`Case #${blacklist.id}\` was quashed by **${ 60 | msg.author.tag 61 | }**, unblacklisted **${blacklistee?.tag || blacklist.discordId}**.` 62 | ); 63 | } 64 | 65 | return; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/commands/mod/blacklist/ViewCases.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { GameProfile } from "../../../structures/game/Profile"; 4 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 5 | import { ProfileService } from "../../../lib/database/services/game/ProfileService"; 6 | import { BlacklistService } from "../../../lib/database/services/meta/BlacklistService"; 7 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 8 | import { Zephyr } from "../../../structures/client/Zephyr"; 9 | 10 | export default class ViewCase extends BaseCommand { 11 | id = `fantasy`; 12 | names = [`cases`, `blacklists`]; 13 | description = `Shows a user's blacklist history.`; 14 | usage = ["$CMD$ "]; 15 | allowDm = true; 16 | 17 | moderatorOnly = true; 18 | 19 | async exec( 20 | msg: Message, 21 | _profile: GameProfile, 22 | options: string[] 23 | ): Promise { 24 | const mention = options[0]?.replace(/[\\<>@#&!]/g, ""); 25 | 26 | if (isNaN(parseInt(mention)) || mention.length < 16 || mention.length > 18) 27 | throw new ZephyrError.InvalidMentionError(); 28 | 29 | const target = await Zephyr.fetchUser(mention); 30 | 31 | if (!target) throw new ZephyrError.UserNotFoundError(); 32 | 33 | const targetProfile = await ProfileService.getProfile(target.id); 34 | 35 | const blacklists = await BlacklistService.getProfileBlacklists( 36 | targetProfile 37 | ); 38 | 39 | const embed = new MessageEmbed(`Blacklists`, msg.author).setTitle( 40 | `${target.tag}'s blacklists (${target.id})` 41 | ); 42 | 43 | if (blacklists.length === 0) { 44 | embed.setDescription(`**${target.tag}** has never been blacklisted.`); 45 | } else { 46 | embed.setDescription( 47 | blacklists 48 | .map( 49 | (b) => 50 | `:lock: Case \`#${b.id}\` (${b.quasher ? `quashed` : `active`})` 51 | ) 52 | .join(`\n`) 53 | ); 54 | } 55 | 56 | await this.send(msg.channel, embed); 57 | return; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/commands/mod/club/BanClubName.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { GameProfile } from "../../../structures/game/Profile"; 4 | import { isIllegalClubName } from "../../../lib/database/sql/game/club/ClubGetter"; 5 | import { addIllegalClubName } from "../../../lib/database/sql/game/club/ClubSetter"; 6 | import { escapeMarkdown } from "../../../lib/utility/text/TextUtils"; 7 | 8 | export default class BanClubName extends BaseCommand { 9 | id = `paramore`; 10 | names = [`banclubname`]; 11 | description = `Bans a club name, permanently disallowing its use.`; 12 | usage = ["$CMD$ "]; 13 | allowDm = true; 14 | 15 | moderatorOnly = true; 16 | 17 | async exec( 18 | msg: Message, 19 | _profile: GameProfile, 20 | options: string[] 21 | ): Promise { 22 | if (!options[0]) { 23 | await this.send(msg.channel, `Please enter a club name to ban.`); 24 | 25 | return; 26 | } 27 | 28 | const clubName = options.join(` `)?.toLowerCase(); 29 | 30 | const isAlreadyBanned = await isIllegalClubName(clubName); 31 | 32 | if (isAlreadyBanned) { 33 | await this.send(msg.channel, `That club name is already banned.`); 34 | 35 | return; 36 | } 37 | 38 | await addIllegalClubName(clubName); 39 | 40 | await this.send( 41 | msg.channel, 42 | `:white_check_mark: **${escapeMarkdown( 43 | clubName 44 | )}** is now an illegal club name.` 45 | ); 46 | 47 | return; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/commands/mod/club/UnbanClubName.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../../structures/command/Command"; 3 | import { GameProfile } from "../../../structures/game/Profile"; 4 | import { isIllegalClubName } from "../../../lib/database/sql/game/club/ClubGetter"; 5 | import { deleteIllegalClubName } from "../../../lib/database/sql/game/club/ClubSetter"; 6 | import { escapeMarkdown } from "../../../lib/utility/text/TextUtils"; 7 | 8 | export default class BanClubName extends BaseCommand { 9 | id = `incubus`; 10 | names = [`unbanclubname`]; 11 | description = `Unbans a club name.`; 12 | usage = ["$CMD$ "]; 13 | allowDm = true; 14 | 15 | moderatorOnly = true; 16 | 17 | async exec( 18 | msg: Message, 19 | _profile: GameProfile, 20 | options: string[] 21 | ): Promise { 22 | if (!options[0]) { 23 | await this.send(msg.channel, `Please enter a club name to unban.`); 24 | 25 | return; 26 | } 27 | 28 | const clubName = options.join(` `)?.toLowerCase(); 29 | 30 | const isAlreadyBanned = await isIllegalClubName(clubName); 31 | 32 | if (!isAlreadyBanned) { 33 | await this.send(msg.channel, `That club name is not banned.`); 34 | 35 | return; 36 | } 37 | 38 | await deleteIllegalClubName(clubName); 39 | 40 | await this.send( 41 | msg.channel, 42 | `:white_check_mark: **${escapeMarkdown( 43 | clubName 44 | )}** is no longer an illegal club name.` 45 | ); 46 | 47 | return; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/commands/orpheus/OrpheusGift.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { BaseCommand } from "../../structures/command/Command"; 3 | import { GameProfile } from "../../structures/game/Profile"; 4 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 5 | import { OrpheusService } from "../../lib/database/services/orpheus/OrpheusService"; 6 | import dayjs from "dayjs"; 7 | import { Zephyr } from "../../structures/client/Zephyr"; 8 | 9 | export default class OrpheusGiftViewer extends BaseCommand { 10 | id = `frantic`; 11 | names = [`ops_gift`]; 12 | description = `Given a gift ID, shows information about it.`; 13 | usage = [`$CMD$ `]; 14 | allowDm = true; 15 | 16 | async exec( 17 | msg: Message, 18 | _profile: GameProfile, 19 | options: string[] 20 | ): Promise { 21 | if ( 22 | !Zephyr.config.moderators.includes(msg.author.id) && 23 | !Zephyr.config.developers.includes(msg.author.id) 24 | ) 25 | return; 26 | 27 | const giftId = parseInt(options[0]); 28 | 29 | if (!giftId || isNaN(giftId) || giftId < 1) { 30 | const embed = new MessageEmbed(`Orpheus Gift`, msg.author).setDescription( 31 | `Please enter a valid gift ID.` 32 | ); 33 | 34 | await this.send(msg.channel, embed); 35 | return; 36 | } 37 | 38 | const gift = await OrpheusService.getGiftById(giftId); 39 | 40 | if (!gift) { 41 | const embed = new MessageEmbed(`Orpheus Gift`, msg.author).setDescription( 42 | `There is no gift with that ID.` 43 | ); 44 | 45 | await this.send(msg.channel, embed); 46 | return; 47 | } 48 | 49 | const embed = new MessageEmbed(`Orpheus Gift`, msg.author) 50 | .setTitle(`Gift ID ${gift.id}`) 51 | .setDescription( 52 | `__**Card:**__ \`${gift.card_id.toString(36)}\`\n__**Sender:**__ ${ 53 | gift.giver 54 | }\n__**Receiver:**__ ${gift.recipient}\n\n__**Time:**__ \`${dayjs( 55 | gift.gift_time 56 | ).format(`YYYY-MM-DD HH:mm:ss`)}\`\n__**Server:**__ \`${ 57 | gift.guild_id 58 | }\`` 59 | ); 60 | 61 | await this.send(msg.channel, embed); 62 | return; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Eris, { TextChannel } from "eris"; 2 | import { DB } from "./lib/database"; 3 | import { FontLoader } from "./lib/FontLoader"; 4 | import { Zephyr } from "./structures/client/Zephyr"; 5 | import events from "events"; 6 | 7 | Object.defineProperty(Eris.User.prototype, "tag", { 8 | enumerable: false, 9 | get: function () { 10 | return `${this.username}#${this.discriminator}`; 11 | }, 12 | }); 13 | Object.defineProperty(Eris.Message.prototype, "textChannel", { 14 | enumerable: false, 15 | get: function () { 16 | return this.channel as TextChannel; 17 | }, 18 | }); 19 | 20 | events.captureRejections = true; 21 | 22 | Promise.all([DB.connect(), FontLoader.init()]).then(() => Zephyr.start()); 23 | -------------------------------------------------------------------------------- /src/lib/FontLoader.ts: -------------------------------------------------------------------------------- 1 | import canvas from "canvas"; 2 | import { glob } from "glob"; 3 | import { promisify } from "util"; 4 | 5 | class FontService { 6 | async init(): Promise { 7 | const globp = promisify(glob); 8 | const files = await globp(`./src/assets/fonts/*`); 9 | for (let file of files) { 10 | canvas.registerFont(file, { family: file.split("/")[4].slice(0, -4) }); 11 | } 12 | return files.length; 13 | } 14 | } 15 | 16 | export const FontLoader = new FontService(); 17 | -------------------------------------------------------------------------------- /src/lib/IssueHandler.ts: -------------------------------------------------------------------------------- 1 | import { GameBaseCard } from "../structures/game/BaseCard"; 2 | import { DB } from "./database"; 3 | 4 | export abstract class IssueHandler { 5 | public static queueIssueGeneration = (() => { 6 | let pending = Promise.resolve(); 7 | const run = async (card: GameBaseCard): Promise => { 8 | try { 9 | await pending; 10 | } finally { 11 | await DB.query(`UPDATE card_base SET serial_total=? WHERE id=?;`, [ 12 | card.serialTotal + 1, 13 | card.id, 14 | ]); 15 | return card.serialTotal + 1; 16 | } 17 | }; 18 | return (card: GameBaseCard) => (pending = run(card)); 19 | })(); 20 | } 21 | -------------------------------------------------------------------------------- /src/lib/StatsD.ts: -------------------------------------------------------------------------------- 1 | import { StatsD } from "node-statsd"; 2 | import { Logger } from "./logger/Logger"; 3 | 4 | const Stats = new StatsD(); 5 | Stats.socket.on(`error`, (err) => { 6 | Logger.error(`StatsD error: ${err}`); 7 | }); 8 | 9 | export { Stats as StatsD }; 10 | -------------------------------------------------------------------------------- /src/lib/ZephyrUtils.ts: -------------------------------------------------------------------------------- 1 | import { Channel, Constants, TextChannel, User } from "eris"; 2 | import { Zephyr } from "../structures/client/Zephyr"; 3 | import { ErisFile } from "../structures/client/ErisFile"; 4 | 5 | function checkPermission(permission: string, channel: Channel): boolean { 6 | if (!channel || channel.type !== 0) return false; 7 | return (channel) 8 | .permissionsOf(Zephyr.user.id) 9 | .has(permission as keyof Constants["Permissions"]); 10 | } 11 | 12 | function isFile(body: any): body is ErisFile { 13 | return !!(body as ErisFile).file; 14 | } 15 | 16 | function isDeveloper(user: User): boolean { 17 | return Zephyr.config.developers.includes(user.id); 18 | } 19 | 20 | export { checkPermission, isFile, isDeveloper }; 21 | -------------------------------------------------------------------------------- /src/lib/command/multitrade/ProcessItems.ts: -------------------------------------------------------------------------------- 1 | import { getItemByName } from "../../../assets/Items"; 2 | import { CardService } from "../../database/services/game/CardService"; 3 | import { ProfileService } from "../../database/services/game/ProfileService"; 4 | import { strToInt } from "../../utility/text/TextUtils"; 5 | 6 | export async function processItems( 7 | stringItems: string[] 8 | ): Promise { 9 | const tradeItems: TradeItemResolvable[] = []; 10 | let bits, cubits; 11 | for (let item of stringItems) { 12 | const split = item.split(" ").map((t) => t.toLowerCase()); 13 | if (split.length === 1) { 14 | if (item.startsWith("$")) { 15 | try { 16 | const getDye = await ProfileService.getDyeByIdentifier(item); 17 | tradeItems.push(getDye); 18 | continue; 19 | } catch {} 20 | } 21 | try { 22 | const getCard = await CardService.getUserCardByIdentifier(item); 23 | tradeItems.push(getCard); 24 | continue; 25 | } catch {} 26 | } 27 | 28 | if (split.length === 2) { 29 | if ([`bit`, `bits`].includes(split[1])) { 30 | const amount = strToInt(split[0]); 31 | 32 | if (!isNaN(amount) && amount > 0) { 33 | bits = (bits || 0) + amount; 34 | } 35 | continue; 36 | } 37 | 38 | if ([`cubit`, `cubits`].includes(split[1])) { 39 | const amount = strToInt(split[0]); 40 | 41 | if (!isNaN(amount) && amount > 0) { 42 | cubits = (cubits || 0) + amount; 43 | } 44 | continue; 45 | } 46 | } 47 | 48 | // Single Item Detection 49 | let amount = 1; 50 | let baseItem = getItemByName(item); 51 | if (!baseItem) { 52 | const noNumber = item.split(" ").slice(1).join(" ").toLowerCase(); 53 | const derivedAmount = parseInt(item.split(" ")[0]); 54 | if (isNaN(derivedAmount) || derivedAmount < 0) continue; 55 | 56 | amount = derivedAmount; 57 | 58 | baseItem = getItemByName(noNumber); 59 | } 60 | 61 | if (!baseItem) continue; 62 | tradeItems.push({ item: baseItem, count: amount }); 63 | } 64 | 65 | if (bits) tradeItems.push({ bits }); 66 | if (cubits) tradeItems.push({ cubits }); 67 | 68 | return tradeItems; 69 | } 70 | -------------------------------------------------------------------------------- /src/lib/command/multitrade/ProcessLogs.ts: -------------------------------------------------------------------------------- 1 | import { GameDye } from "../../../structures/game/Dye"; 2 | import { GameProfile } from "../../../structures/game/Profile"; 3 | import { GameUserCard } from "../../../structures/game/UserCard"; 4 | import { DB } from "../../database"; 5 | import { 6 | isBitItem, 7 | isCubitItem, 8 | isInteractableItem, 9 | } from "./typeguards/MultitradeTypeguards"; 10 | 11 | export function processMultitradeLogs( 12 | items: TradeItemResolvable[], 13 | giver: GameProfile, 14 | receiver: GameProfile, 15 | uuid: string, 16 | timestamp: string 17 | ): string[] { 18 | const queries = []; 19 | 20 | for (let item of items) { 21 | let itemType, itemValue, quantity; 22 | 23 | if (item instanceof GameUserCard) { 24 | itemType = "card"; 25 | itemValue = item.id; 26 | quantity = 1; 27 | } else if (item instanceof GameDye) { 28 | itemType = "dye"; 29 | itemValue = item.id; 30 | quantity = 1; 31 | } else if (isBitItem(item)) { 32 | itemType = "bits"; 33 | itemValue = ""; 34 | quantity = item.bits; 35 | } else if (isCubitItem(item)) { 36 | itemType = "cubits"; 37 | itemValue = ""; 38 | quantity = item.cubits; 39 | } else if (isInteractableItem(item)) { 40 | itemType = "item"; 41 | itemValue = item.item.id; 42 | quantity = item.count; 43 | } else { 44 | itemType = "unknown"; 45 | itemValue = ""; 46 | quantity = 0; 47 | } 48 | 49 | queries.push( 50 | `(${DB.connection.escape(uuid)}, ${DB.connection.escape( 51 | giver.discordId 52 | )}, ${DB.connection.escape(receiver.discordId)}, ${DB.connection.escape( 53 | itemType 54 | )}, ${DB.connection.escape(itemValue)}, ${DB.connection.escape( 55 | quantity 56 | )}, ${DB.connection.escape(timestamp)})` 57 | ); 58 | } 59 | 60 | return queries; 61 | } 62 | -------------------------------------------------------------------------------- /src/lib/command/multitrade/RenderInventory.ts: -------------------------------------------------------------------------------- 1 | import { GameDye } from "../../../structures/game/Dye"; 2 | import { GameUserCard } from "../../../structures/game/UserCard"; 3 | 4 | export function renderMultitradeInventory( 5 | tradeItems: TradeItemResolvable[], 6 | confirmed: boolean, 7 | accepted: boolean 8 | ): string { 9 | const cards = tradeItems.filter( 10 | (i) => i instanceof GameUserCard 11 | ) as GameUserCard[]; 12 | 13 | const dyes = tradeItems.filter((i) => i instanceof GameDye) as GameDye[]; 14 | 15 | const bits = (tradeItems.filter((i) => isBitItem(i))[0]) 16 | ?.bits; 17 | 18 | const cubits = (( 19 | tradeItems.filter((i) => isCubitItem(i))[0] 20 | ))?.cubits; 21 | 22 | const baseItems = tradeItems.filter((i) => 23 | isInteractableItem(i) 24 | ) as InteractableItem[]; 25 | 26 | const trueBaseItems: InteractableItem[] = []; 27 | 28 | for (let item of baseItems) { 29 | const findItem = trueBaseItems.find((i) => i.item.id === item.item.id); 30 | if (findItem) { 31 | findItem.count += item.count; 32 | } else { 33 | trueBaseItems.push({ ...item }); 34 | } 35 | } 36 | 37 | let status = accepted 38 | ? `+ Accepted +` 39 | : confirmed 40 | ? `+ Confirmed +` 41 | : `- Unconfirmed -`; 42 | 43 | return `\`\`\`diff\n${status}\n\n${ 44 | bits ? `${bits.toLocaleString()} bits\n` : `` 45 | }${cubits ? `${cubits.toLocaleString()} cubits\n` : ``}${ 46 | cards.length > 0 47 | ? cards.map((c) => c.id.toString(36)).join(`, `) + `\n` 48 | : `` 49 | }${ 50 | dyes.length > 0 51 | ? dyes.map((d) => `$${d.id.toString(36)}`).join(`, `) + `\n` 52 | : `` 53 | }${ 54 | trueBaseItems.length > 0 55 | ? trueBaseItems.map((i) => `${i.count}x ${i.item.names[0]}`).join(`, `) 56 | : `` 57 | }\n\`\`\``; 58 | } 59 | 60 | function isInteractableItem(value: any): value is InteractableItem { 61 | return value.hasOwnProperty("item") && value.hasOwnProperty("count"); 62 | } 63 | 64 | function isBitItem(value: any): value is InteractableBits { 65 | return value.hasOwnProperty("bits"); 66 | } 67 | 68 | function isCubitItem(value: any): value is InteractableCubits { 69 | return value.hasOwnProperty("cubits"); 70 | } 71 | -------------------------------------------------------------------------------- /src/lib/command/multitrade/TransferItems.ts: -------------------------------------------------------------------------------- 1 | import { GameDye } from "../../../structures/game/Dye"; 2 | import { GameProfile } from "../../../structures/game/Profile"; 3 | import { GameUserCard } from "../../../structures/game/UserCard"; 4 | import { AutotagService } from "../../database/services/game/AutotagService"; 5 | import { CardService } from "../../database/services/game/CardService"; 6 | import { ProfileService } from "../../database/services/game/ProfileService"; 7 | import { 8 | isBitItem, 9 | isCubitItem, 10 | isInteractableItem, 11 | } from "./typeguards/MultitradeTypeguards"; 12 | 13 | export async function transferItems( 14 | tradeItems: TradeItemResolvable[], 15 | receiver: GameProfile, 16 | giver: GameProfile 17 | ): Promise { 18 | const cards = tradeItems.filter( 19 | (i) => i instanceof GameUserCard 20 | ) as GameUserCard[]; 21 | 22 | const dyes = tradeItems.filter((i) => i instanceof GameDye) as GameDye[]; 23 | 24 | const items = tradeItems.filter((i) => 25 | isInteractableItem(i) 26 | ) as InteractableItem[]; 27 | 28 | const { bits } = 29 | (tradeItems.find((i) => isBitItem(i)) as InteractableBits) || 0; 30 | 31 | const { cubits } = 32 | (tradeItems.find((i) => isCubitItem(i)) as InteractableCubits) || 0; 33 | 34 | if (cards.length > 0) { 35 | await CardService.transferCardsToUser(cards, giver, receiver); 36 | 37 | const tags = await receiver.getTags(); 38 | if (tags.length > 0) { 39 | for (let card of cards) { 40 | await AutotagService.autotag(receiver, tags, await card.fetch()); 41 | } 42 | } 43 | } 44 | if (items.length > 0) { 45 | await ProfileService.removeItems(giver, items); 46 | await ProfileService.addItems(receiver, items); 47 | } 48 | if (dyes.length > 0) await ProfileService.transferDyesToUser(dyes, receiver); 49 | if (bits > 0) { 50 | await ProfileService.removeBitsFromProfile(giver, bits); 51 | await ProfileService.addBitsToProfile(receiver, bits); 52 | } 53 | if (cubits > 0) { 54 | await ProfileService.removeCubits(giver, cubits); 55 | await ProfileService.addCubits(receiver, cubits); 56 | } 57 | 58 | return; 59 | } 60 | -------------------------------------------------------------------------------- /src/lib/command/multitrade/typeguards/MultitradeTypeguards.ts: -------------------------------------------------------------------------------- 1 | export function isInteractableItem(value: any): value is InteractableItem { 2 | return value.hasOwnProperty("item") && value.hasOwnProperty("count"); 3 | } 4 | 5 | export function isBitItem(value: any): value is InteractableBits { 6 | return value.hasOwnProperty("bits"); 7 | } 8 | 9 | export function isCubitItem(value: any): value is InteractableCubits { 10 | return value.hasOwnProperty("cubits"); 11 | } 12 | -------------------------------------------------------------------------------- /src/lib/cosmetics/Backgrounds.ts: -------------------------------------------------------------------------------- 1 | import { loadImage } from "canvas"; 2 | import { 3 | GameAlbumBackground, 4 | IntermediateBackground, 5 | } from "../../structures/game/Album"; 6 | import { CosmeticGetter } from "../database/sql/game/shop/CosmeticGetter"; 7 | 8 | class BackgroundService { 9 | private backgrounds: GameAlbumBackground[] = []; 10 | 11 | public async loadBackgrounds(): Promise { 12 | const backgrounds = await CosmeticGetter.getBackgrounds(); 13 | 14 | const finalBackgrounds = []; 15 | for (let background of backgrounds) { 16 | const intermediate: IntermediateBackground = { 17 | id: background.id, 18 | background_name: background.background_name, 19 | image: await loadImage(background.image_url), 20 | }; 21 | 22 | finalBackgrounds.push(new GameAlbumBackground(intermediate)); 23 | } 24 | 25 | this.backgrounds = finalBackgrounds; 26 | return this.backgrounds; 27 | } 28 | 29 | public getBackgroundById(id: number): GameAlbumBackground | undefined { 30 | return this.backgrounds.find((background) => background.id === id); 31 | } 32 | 33 | public getBackgroundByName(name: string): GameAlbumBackground | undefined { 34 | return this.backgrounds.find((background) => 35 | background.name.toLowerCase().startsWith(name.toLowerCase()) 36 | ); 37 | } 38 | 39 | public async init() { 40 | await this.loadBackgrounds(); 41 | return; 42 | } 43 | } 44 | 45 | export const Backgrounds = new BackgroundService(); 46 | -------------------------------------------------------------------------------- /src/lib/cosmetics/Frames.ts: -------------------------------------------------------------------------------- 1 | import { loadImage } from "canvas"; 2 | import { GameFrame, IntermediateFrame } from "../../structures/game/Frame"; 3 | import { CosmeticGetter } from "../database/sql/game/shop/CosmeticGetter"; 4 | import fs from "fs/promises"; 5 | 6 | class FrameService { 7 | private frames: GameFrame[] = []; 8 | 9 | public async loadFrames(): Promise { 10 | const frames = await CosmeticGetter.getFrames(); 11 | 12 | const finalFrames = []; 13 | for (let frame of frames) { 14 | const intermediate: IntermediateFrame = { 15 | id: frame.id, 16 | name: frame.frame_name || `Unknown Frame`, 17 | frame: await loadImage( 18 | frame.frame_url || `./src/assets/frames/default/frame-default.png` 19 | ), 20 | frameUrl: 21 | frame.frame_url || `./src/assets/frames/default/frame-default.png`, 22 | mask: await fs.readFile( 23 | frame.dye_mask_url || `./src/assets/frames/default/mask-default.png` 24 | ), 25 | maskUrl: 26 | frame.dye_mask_url || `./src/assets/frames/default/mask-default.png`, 27 | overlay: frame.overlay, 28 | textColor: frame.text_color_hex || `FFFFFF`, 29 | }; 30 | 31 | finalFrames.push(new GameFrame(intermediate)); 32 | } 33 | 34 | this.frames = finalFrames; 35 | return this.frames; 36 | } 37 | 38 | public getFrames(): GameFrame[] { 39 | return this.frames; 40 | } 41 | 42 | public getFrameById(id: number): GameFrame | undefined { 43 | return this.frames.find((frame) => frame.id === id); 44 | } 45 | 46 | public getFrameByName(name: string): GameFrame | undefined { 47 | return this.frames.find( 48 | (frame) => frame.name.toLowerCase() === name.toLowerCase() 49 | ); 50 | } 51 | 52 | public async init(): Promise { 53 | await this.loadFrames(); 54 | return; 55 | } 56 | } 57 | 58 | export const Frames = new FrameService(); 59 | -------------------------------------------------------------------------------- /src/lib/database/index.ts: -------------------------------------------------------------------------------- 1 | import config from "../../../config.json"; 2 | import { Pool, createPool, QueryOptions } from "mysql"; 3 | import { promisify } from "util"; 4 | 5 | export class DB { 6 | public static query: ( 7 | arg1: string | QueryOptions, 8 | values?: (string | number | boolean | string[] | number[])[] 9 | ) => Promise; 10 | public static connection: Pool; 11 | static async connect() { 12 | const _pool = createPool(config.mariadb); 13 | const _query = promisify(_pool.query).bind(_pool); 14 | this.query = _query; 15 | this.connection = _pool; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/database/services/game/BadgeService.ts: -------------------------------------------------------------------------------- 1 | import { GameBadge, GameUserBadge } from "../../../../structures/game/Badge"; 2 | import { GameProfile } from "../../../../structures/game/Profile"; 3 | import { BadgeGet } from "../../sql/game/badge/BadgeGet"; 4 | import { BadgeSet } from "../../sql/game/badge/BadgeSet"; 5 | 6 | export abstract class BadgeService { 7 | public static async createBadge( 8 | name: string, 9 | emoji: string 10 | ): Promise { 11 | return await BadgeSet.createBadge(name, emoji); 12 | } 13 | 14 | public static async deleteBadge(badge: GameBadge): Promise { 15 | return await BadgeSet.deleteBadge(badge); 16 | } 17 | 18 | public static async createUserBadge( 19 | profile: GameProfile, 20 | badge: GameBadge 21 | ): Promise { 22 | return await BadgeSet.createUserBadge(profile, badge); 23 | } 24 | 25 | public static async deleteUserBadge( 26 | profile: GameProfile, 27 | badge: GameUserBadge 28 | ): Promise { 29 | return await BadgeSet.deleteUserBadge(profile, badge); 30 | } 31 | 32 | public static async getBadgeById(id: number): Promise { 33 | return await BadgeGet.getBadgeById(id); 34 | } 35 | 36 | public static async getUserBadgeById(id: number): Promise { 37 | return await BadgeGet.getUserBadgeById(id); 38 | } 39 | 40 | public static async getProfileBadges( 41 | profile: GameProfile 42 | ): Promise { 43 | return await BadgeGet.getProfileBadges(profile); 44 | } 45 | 46 | public static async getBadgeByName( 47 | name: string 48 | ): Promise { 49 | return await BadgeGet.getBadgeByName(name); 50 | } 51 | 52 | public static async getBadgeByEmoji( 53 | emoji: string 54 | ): Promise { 55 | return await BadgeGet.getBadgeByEmoji(emoji); 56 | } 57 | 58 | public static async getNumberOfBadgeGranted( 59 | badge: GameBadge 60 | ): Promise { 61 | return await BadgeGet.getNumberOfBadgeGranted(badge); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/lib/database/services/game/LeaderboardService.ts: -------------------------------------------------------------------------------- 1 | import { GameProfile } from "../../../../structures/game/Profile"; 2 | import { LeaderboardGet } from "../../sql/game/leaderboard/LeaderboardGet"; 3 | 4 | export abstract class LeaderboardService { 5 | public static async getBitLeaderboardCount(): Promise { 6 | return await LeaderboardGet.getBitLeaderboardCount(); 7 | } 8 | 9 | public static async getBitLeaderboard( 10 | page: number = 1 11 | ): Promise { 12 | return await LeaderboardGet.getBitLeaderboard(page); 13 | } 14 | 15 | public static async getDailyStreakLeaderboardCount(): Promise { 16 | return await LeaderboardGet.getDailyStreakLeaderboardCount(); 17 | } 18 | 19 | public static async getDailyStreakLeaderboard( 20 | page: number = 1 21 | ): Promise { 22 | return await LeaderboardGet.getDailyStreakLeaderboard(page); 23 | } 24 | 25 | public static async getCardLeaderboardCount(): Promise { 26 | return await LeaderboardGet.getCardLeaderboardCount(); 27 | } 28 | 29 | public static async getCardLeaderboard( 30 | page: number = 1 31 | ): Promise<{ profile: GameProfile; count: number }[]> { 32 | return await LeaderboardGet.getCardLeaderboard(page); 33 | } 34 | 35 | public static async getCubitLeaderboardCount(): Promise { 36 | return await LeaderboardGet.getCubitLeaderboardCount(); 37 | } 38 | 39 | public static async getCubitLeaderboard( 40 | page: number = 1 41 | ): Promise { 42 | return await LeaderboardGet.getCubitLeaderboard(page); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/lib/database/services/guild/GuildService.ts: -------------------------------------------------------------------------------- 1 | import { GuildGet } from "../../sql/guild/GuildGet"; 2 | import { GuildSet } from "../../sql/guild/GuildSet"; 3 | 4 | export abstract class GuildService { 5 | public static async getPrefixes(): Promise<{ [guildId: string]: string }> { 6 | return await GuildGet.getPrefixes(); 7 | } 8 | 9 | public static async setPrefix( 10 | guildId: string, 11 | prefix: string 12 | ): Promise { 13 | return await GuildSet.setPrefix(guildId, prefix); 14 | } 15 | 16 | public static async getDropChannel(guildId: string): Promise { 17 | return await GuildGet.getDropChannel(guildId); 18 | } 19 | 20 | public static async setDropChannel( 21 | guildId: string, 22 | dropChannel: string 23 | ): Promise { 24 | return await GuildSet.setDropChannel(guildId, dropChannel); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/database/services/meta/BlacklistService.ts: -------------------------------------------------------------------------------- 1 | import { GameBlacklist } from "../../../../structures/game/blacklist/Blacklist"; 2 | import { GameProfile } from "../../../../structures/game/Profile"; 3 | import { BlacklistGet } from "../../sql/meta/blacklist/BlacklistGet"; 4 | import { BlacklistSet } from "../../sql/meta/blacklist/BlacklistSet"; 5 | 6 | export abstract class BlacklistService { 7 | public static async getBlacklistById(id: number): Promise { 8 | return await BlacklistGet.getBlacklistById(id); 9 | } 10 | 11 | public static async blacklist( 12 | profile: GameProfile, 13 | moderator: GameProfile, 14 | reason: string 15 | ): Promise { 16 | return await BlacklistSet.blacklist(profile, moderator, reason); 17 | } 18 | 19 | public static async quashBlacklist( 20 | blacklist: GameBlacklist, 21 | quasher: GameProfile, 22 | note: string 23 | ): Promise { 24 | return await BlacklistSet.quashBlacklist(blacklist, quasher, note); 25 | } 26 | 27 | public static async findBlacklist( 28 | profile: GameProfile 29 | ): Promise { 30 | return await BlacklistGet.findBlacklist(profile); 31 | } 32 | 33 | public static async getProfileBlacklists( 34 | profile: GameProfile 35 | ): Promise { 36 | return await BlacklistGet.getProfileBlacklists(profile); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/lib/database/services/meta/PatreonService.ts: -------------------------------------------------------------------------------- 1 | import { GameProfile } from "../../../../structures/game/Profile"; 2 | import { GamePatron } from "../../../../structures/meta/Patron"; 3 | import { PatreonGet } from "../../sql/meta/patreon/PatreonGet"; 4 | import { PatreonSet } from "../../sql/meta/patreon/PatreonSet"; 5 | 6 | export abstract class PatreonService { 7 | public static async getPatronInformation( 8 | profile: GameProfile 9 | ): Promise { 10 | return await PatreonGet.getPatronInformation(profile); 11 | } 12 | 13 | public static async addPatron(profile: GameProfile): Promise { 14 | return await PatreonSet.addPatron(profile); 15 | } 16 | 17 | public static async removePatron(profile: GameProfile): Promise { 18 | return await PatreonSet.removePatron(profile); 19 | } 20 | 21 | public static async setNextPatreonClaimTime( 22 | profile: GameProfile 23 | ): Promise { 24 | return await PatreonSet.setNextPatreonClaimTime(profile); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/lib/database/services/meta/PollService.ts: -------------------------------------------------------------------------------- 1 | import { GameProfile } from "../../../../structures/game/Profile"; 2 | import { GameAnswer, GamePoll } from "../../../../structures/poll/Poll"; 3 | import { PollGet } from "../../sql/meta/poll/PollGet"; 4 | import { PollSet } from "../../sql/meta/poll/PollSet"; 5 | 6 | export abstract class PollService { 7 | public static async createPoll( 8 | title: string, 9 | body: string, 10 | endsAt?: string 11 | ): Promise { 12 | return await PollSet.createPoll(title, body, endsAt); 13 | } 14 | 15 | public static async getPollById(id: number): Promise { 16 | return await PollGet.getPollById(id); 17 | } 18 | 19 | public static async activatePoll(poll: GamePoll): Promise { 20 | return await PollSet.activatePoll(poll); 21 | } 22 | 23 | public static async deactivatePoll(poll: GamePoll): Promise { 24 | return await PollSet.deactivatePoll(poll); 25 | } 26 | 27 | public static async getActivePolls(): Promise { 28 | return await PollGet.getActivePolls(); 29 | } 30 | 31 | public static async userAnsweredPoll( 32 | poll: GamePoll, 33 | profile: GameProfile 34 | ): Promise { 35 | return await PollGet.userAnsweredPoll(poll, profile); 36 | } 37 | 38 | public static async getAnswerById(id: number): Promise { 39 | return await PollGet.getAnswerById(id); 40 | } 41 | 42 | public static async answerPoll( 43 | poll: GamePoll, 44 | profile: GameProfile, 45 | answer: `yes` | `no` 46 | ): Promise { 47 | return await PollSet.answerPoll(poll, profile, answer); 48 | } 49 | 50 | public static async getPollStandings( 51 | poll: GamePoll 52 | ): Promise<{ yes: number; no: number }> { 53 | return await PollGet.getPollStandings(poll); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/lib/database/services/orpheus/OrpheusService.ts: -------------------------------------------------------------------------------- 1 | import { User } from "eris"; 2 | import { OrpheusBitTransaction } from "../../../../structures/orpheus/OrpheusBitTransaction"; 3 | import { OrpheusClaim } from "../../../../structures/orpheus/OrpheusClaim"; 4 | import { OrpheusCommand } from "../../../../structures/orpheus/OrpheusCommand"; 5 | import { OrpheusGift } from "../../../../structures/orpheus/OrpheusGift"; 6 | import { OrpheusMultitrade } from "../../../../structures/orpheus/OrpheusMultitrade"; 7 | import { OrpheusTrade } from "../../../../structures/orpheus/OrpheusTrade"; 8 | import { OrpheusGet } from "../../sql/orpheus/OrpheusGet"; 9 | 10 | export async function getGifts(user: User): Promise { 11 | return await OrpheusGet.getGifts(user); 12 | } 13 | 14 | export async function getGiftById( 15 | id: number 16 | ): Promise { 17 | return await OrpheusGet.getGiftById(id); 18 | } 19 | 20 | export async function getGiftsReceived(user: User): Promise { 21 | return await OrpheusGet.getGiftsReceived(user); 22 | } 23 | 24 | export async function getGiftsSent(user: User): Promise { 25 | return await OrpheusGet.getGiftsSent(user); 26 | } 27 | 28 | export async function getTrades(user: User): Promise { 29 | return await OrpheusGet.getTrades(user); 30 | } 31 | 32 | export async function getMultitrades(user: User): Promise { 33 | return await OrpheusGet.getMultitrades(user); 34 | } 35 | 36 | export async function getClaims(user: User): Promise { 37 | return await OrpheusGet.getClaims(user); 38 | } 39 | 40 | export async function getDrops(user: User): Promise { 41 | return await OrpheusGet.getDrops(user); 42 | } 43 | 44 | export async function getCommandUses(user: User): Promise { 45 | return await OrpheusGet.getCommandUses(user); 46 | } 47 | 48 | export async function getBitTransactions( 49 | user: User 50 | ): Promise { 51 | return await OrpheusGet.getBitTransactions(user); 52 | } 53 | 54 | export * as OrpheusService from "./OrpheusService"; 55 | -------------------------------------------------------------------------------- /src/lib/database/sql/game/album/AlbumSet.ts: -------------------------------------------------------------------------------- 1 | import { DB } from "../../.."; 2 | import { GameAlbum, GameAlbumCard } from "../../../../../structures/game/Album"; 3 | import { GameProfile } from "../../../../../structures/game/Profile"; 4 | import { GameUserCard } from "../../../../../structures/game/UserCard"; 5 | import { AlbumService } from "../../../services/game/AlbumService"; 6 | 7 | export async function createAlbum( 8 | name: string, 9 | owner: GameProfile 10 | ): Promise { 11 | const query = (await DB.query( 12 | `INSERT INTO album (discord_id, album_name) VALUES (?, ?);`, 13 | [owner.discordId, name] 14 | )) as { insertId: number }; 15 | 16 | return await AlbumService.getAlbumById(query.insertId); 17 | } 18 | 19 | export async function changeAlbumName( 20 | album: GameAlbum, 21 | newName: string 22 | ): Promise { 23 | await DB.query(`UPDATE album SET album_name=? WHERE id=?;`, [ 24 | newName, 25 | album.id, 26 | ]); 27 | 28 | return; 29 | } 30 | 31 | export async function addPageToAlbum(album: GameAlbum): Promise { 32 | await DB.query(`UPDATE album SET pages=pages+1 WHERE id=?;`, [album.id]); 33 | 34 | return; 35 | } 36 | 37 | export async function setAlbumBackground( 38 | album: GameAlbum, 39 | backgroundId: number 40 | ): Promise { 41 | await DB.query(`UPDATE album SET background_id=? WHERE id=?;`, [ 42 | backgroundId, 43 | album.id, 44 | ]); 45 | 46 | return; 47 | } 48 | 49 | /* 50 | Cards 51 | */ 52 | export async function addCardToAlbum( 53 | album: GameAlbum, 54 | card: GameUserCard, 55 | slot: number 56 | ): Promise { 57 | await DB.query( 58 | `INSERT INTO album_card (album_id, card_id, slot) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE card_id=?;`, 59 | [album.id, card.id, slot, card.id] 60 | ); 61 | 62 | return; 63 | } 64 | 65 | export async function removeCardsFromAlbum( 66 | cards: GameAlbumCard[] | GameUserCard[] 67 | ): Promise { 68 | const ids = []; 69 | for (let card of cards) { 70 | if (card instanceof GameUserCard) { 71 | ids.push(card.id); 72 | } else { 73 | ids.push(card.cardId); 74 | } 75 | } 76 | 77 | await DB.query(`DELETE FROM album_card WHERE card_id IN (?);`, [ids]); 78 | 79 | return; 80 | } 81 | 82 | export * as AlbumSet from "./AlbumSet"; 83 | -------------------------------------------------------------------------------- /src/lib/database/sql/game/autotag/AutotagGet.ts: -------------------------------------------------------------------------------- 1 | import { DB } from "../../.."; 2 | import * as ZephyrError from "../../../../../structures/error/ZephyrError"; 3 | import { Autotag, GameAutotag } from "../../../../../structures/game/Autotag"; 4 | import { GameProfile } from "../../../../../structures/game/Profile"; 5 | 6 | export async function getAutotags( 7 | profile: GameProfile 8 | ): Promise { 9 | const query = (await DB.query(`SELECT * FROM autotag WHERE discord_id=?;`, [ 10 | profile.discordId, 11 | ])) as Autotag[]; 12 | 13 | return query.map((q) => new GameAutotag(q)); 14 | } 15 | 16 | export async function getAutotagById(id: number): Promise { 17 | const query = (await DB.query(`SELECT * FROM autotag WHERE id=?;`, [ 18 | id, 19 | ])) as Autotag[]; 20 | 21 | if (!query[0]) throw new ZephyrError.AutotagIdNotFoundError(id); 22 | 23 | return new GameAutotag(query[0]); 24 | } 25 | 26 | export * as AutotagGet from "./AutotagGet"; 27 | -------------------------------------------------------------------------------- /src/lib/database/sql/game/autotag/AutotagSet.ts: -------------------------------------------------------------------------------- 1 | import { DB } from "../../.."; 2 | import { GameAutotag } from "../../../../../structures/game/Autotag"; 3 | import { GameProfile } from "../../../../../structures/game/Profile"; 4 | import { GameTag } from "../../../../../structures/game/Tag"; 5 | import { AutotagService } from "../../../services/game/AutotagService"; 6 | 7 | export async function createAutotag( 8 | profile: GameProfile, 9 | key: AutotagKey, 10 | value: number, 11 | tag: GameTag, 12 | priority: number 13 | ): Promise { 14 | const query = (await DB.query( 15 | `INSERT INTO autotag (discord_id, autotag_key, autotag_value, priority, tag_id) VALUES (?, ?, ?, ?, ?);`, 16 | [profile.discordId, key, value, priority, tag.id] 17 | )) as { insertId: number }; 18 | 19 | return await AutotagService.getAutotagById(query.insertId); 20 | } 21 | 22 | export async function deleteAutotag(autotag: GameAutotag): Promise { 23 | await DB.query(`DELETE FROM autotag WHERE id=?;`, [autotag.id]); 24 | await DB.query( 25 | `UPDATE autotag SET priority=priority-1 WHERE discord_id=? AND priority > ?;`, 26 | [autotag.discordId, autotag.priority] 27 | ); 28 | 29 | return; 30 | } 31 | 32 | export async function setAutotagKey( 33 | autotag: GameAutotag, 34 | key: AutotagKey 35 | ): Promise { 36 | await DB.query(`UPDATE autotag SET autotag_key=? WHERE id=?;`, [ 37 | key, 38 | autotag.id, 39 | ]); 40 | 41 | return await autotag.fetch(); 42 | } 43 | 44 | export async function setAutotagValue( 45 | autotag: GameAutotag, 46 | value: number 47 | ): Promise { 48 | await DB.query(`UPDATE autotag SET autotag_value=? WHERE id=?;`, [ 49 | value, 50 | autotag.id, 51 | ]); 52 | 53 | return await autotag.fetch(); 54 | } 55 | 56 | export async function setAutotagPriority( 57 | autotag: GameAutotag, 58 | priority: number 59 | ): Promise { 60 | await DB.query(`UPDATE autotag SET priority=? WHERE id=?;`, [ 61 | priority, 62 | autotag.id, 63 | ]); 64 | 65 | // automatically re-adjust priorities? 66 | 67 | return await autotag.fetch(); 68 | } 69 | 70 | export * as AutotagSet from "./AutotagSet"; 71 | -------------------------------------------------------------------------------- /src/lib/database/sql/game/badge/BadgeSet.ts: -------------------------------------------------------------------------------- 1 | import { DB } from "../../.."; 2 | import { GameBadge, GameUserBadge } from "../../../../../structures/game/Badge"; 3 | import { GameProfile } from "../../../../../structures/game/Profile"; 4 | import { BadgeService } from "../../../services/game/BadgeService"; 5 | 6 | export async function createBadge( 7 | name: string, 8 | emoji: string 9 | ): Promise { 10 | const query = (await DB.query( 11 | `INSERT INTO badge (badge_name, badge_emoji) VALUES (?, ?);`, 12 | [name, emoji] 13 | )) as { insertId: number }; 14 | 15 | return await BadgeService.getBadgeById(query.insertId); 16 | } 17 | 18 | export async function deleteBadge(badge: GameBadge): Promise { 19 | await DB.query( 20 | `DELETE badge.*, user_badge.* FROM 21 | badge 22 | LEFT JOIN 23 | user_badge 24 | ON 25 | user_badge.badge_id=badge.id 26 | WHERE badge.id=? OR user_badge.badge_id=?;`, 27 | [badge.id, badge.id] 28 | ); 29 | 30 | return; 31 | } 32 | 33 | export async function setBadgeDescription( 34 | badge: GameBadge, 35 | description: string 36 | ): Promise { 37 | await DB.query(`UPDATE badge SET badge_description=? WHERE id=?;`, [ 38 | description, 39 | badge.id, 40 | ]); 41 | 42 | return await badge.fetch(); 43 | } 44 | 45 | export async function createUserBadge( 46 | profile: GameProfile, 47 | badge: GameBadge 48 | ): Promise { 49 | const query = (await DB.query( 50 | `INSERT INTO user_badge (discord_id, badge_id) VALUES (?, ?);`, 51 | [profile.discordId, badge.id] 52 | )) as { insertId: number }; 53 | 54 | return await BadgeService.getUserBadgeById(query.insertId); 55 | } 56 | 57 | export async function deleteUserBadge( 58 | profile: GameProfile, 59 | badge: GameUserBadge 60 | ): Promise { 61 | await DB.query(`DELETE FROM user_badge WHERE id=? AND discord_id=?;`, [ 62 | badge.id, 63 | profile.discordId, 64 | ]); 65 | 66 | return; 67 | } 68 | 69 | export * as BadgeSet from "./BadgeSet"; 70 | 71 | /* 72 | 73 | CREATE TABLE badge 74 | ( 75 | id INT(11) AUTO_INCREMENT, 76 | badge_name TEXT(32) NOT NULL, 77 | badge_emoji TEXT(8) NOT NULL, 78 | PRIMARY KEY(id) 79 | ); 80 | 81 | CREATE TABLE user_badge 82 | ( 83 | id INT(11) AUTO_INCREMENT, 84 | discord_id VARCHAR(32) NOT NULL, 85 | badge_id INT(11) NOT NULL, 86 | PRIMARY KEY(id), 87 | CONSTRAINT UniqueUserBadge UNIQUE (discord_id, badge_id) 88 | ); 89 | 90 | */ 91 | -------------------------------------------------------------------------------- /src/lib/database/sql/game/shop/CosmeticGetter.ts: -------------------------------------------------------------------------------- 1 | import { DB } from "../../.."; 2 | import { AlbumBackground } from "../../../../../structures/game/Album"; 3 | import { Frame } from "../../../../../structures/game/Frame"; 4 | import { Sticker } from "../../../../../structures/game/Sticker"; 5 | import { StickerPack } from "../../../../../structures/shop/StickerPack"; 6 | 7 | export async function getStickerPacks(): Promise { 8 | const query = (await DB.query( 9 | ` 10 | SELECT 11 | sticker_pack.id, 12 | sticker_pack.pack_name, 13 | sticker_pack.price, 14 | sticker_pack.currency, 15 | sticker_pack.item_id, 16 | sticker_pack.featured, 17 | sticker_pack.shoppable, 18 | sticker_pack.selection, 19 | sticker_pack.pulls 20 | FROM 21 | sticker_pack; 22 | ` 23 | )) as StickerPack[]; 24 | 25 | return query; 26 | } 27 | 28 | export async function getStickers(): Promise { 29 | const query = (await DB.query(` 30 | SELECT 31 | sticker.id, 32 | sticker.name, 33 | sticker.image_url, 34 | sticker.item_id, 35 | sticker.pack_id, 36 | sticker.rarity 37 | FROM sticker; 38 | `)) as Sticker[]; 39 | 40 | return query; 41 | } 42 | 43 | export async function getFrames(): Promise { 44 | const query = (await DB.query(` 45 | SELECT 46 | id, 47 | frame_name, 48 | frame_url, 49 | dye_mask_url, 50 | text_color_hex, 51 | overlay 52 | FROM card_frame;`)) as Frame[]; 53 | 54 | return query; 55 | } 56 | 57 | export async function getBackgrounds(): Promise { 58 | const query = (await DB.query( 59 | ` 60 | SELECT 61 | album_background.id, 62 | album_background.background_name, 63 | album_background.image_url 64 | FROM 65 | album_background; 66 | ` 67 | )) as AlbumBackground[]; 68 | 69 | return query; 70 | } 71 | 72 | export * as CosmeticGetter from "./CosmeticGetter"; 73 | -------------------------------------------------------------------------------- /src/lib/database/sql/game/shop/ShopGetter.ts: -------------------------------------------------------------------------------- 1 | import { DB } from "../../.."; 2 | import { DBShop } from "../../../../../structures/shop/Shop"; 3 | 4 | export async function getShopItems(): Promise { 5 | const query = (await DB.query( 6 | ` 7 | SELECT 8 | item_shop.id, 9 | item_shop.item_id, 10 | item_shop.price, 11 | item_shop.currency, 12 | item_shop.featured 13 | FROM 14 | item_shop; 15 | ` 16 | )) as DBShop[]; 17 | 18 | return query; 19 | } 20 | 21 | export * as ShopGetter from "./ShopGetter"; 22 | -------------------------------------------------------------------------------- /src/lib/database/sql/guild/GuildGet.ts: -------------------------------------------------------------------------------- 1 | import { DB } from "../.."; 2 | 3 | export async function getPrefixes(): Promise<{ 4 | [guildId: string]: string; 5 | }> { 6 | const query = (await DB.query(`SELECT * FROM guild;`)) as { 7 | guild_id: string; 8 | prefix: string; 9 | }[]; 10 | const prefixes: { [guildId: string]: string } = {}; 11 | for (let setting of query) { 12 | prefixes[setting.guild_id] = setting.prefix; 13 | } 14 | return prefixes; 15 | } 16 | 17 | export async function getDropChannel(guildId: string): Promise { 18 | const query = (await DB.query(`SELECT * FROM guild WHERE guild_id=?;`, [ 19 | guildId, 20 | ])) as { 21 | guild_id: string; 22 | prefix: string; 23 | drop_channel_id: string | null; 24 | }[]; 25 | return query[0]?.drop_channel_id; 26 | } 27 | 28 | export * as GuildGet from "./GuildGet"; 29 | -------------------------------------------------------------------------------- /src/lib/database/sql/guild/GuildSet.ts: -------------------------------------------------------------------------------- 1 | import { DB } from "../.."; 2 | 3 | export async function setPrefix( 4 | guildId: string, 5 | prefix: string 6 | ): Promise { 7 | await DB.query( 8 | `INSERT INTO guild (guild_id, prefix) VALUES (?, ?) ON DUPLICATE KEY UPDATE prefix=?;`, 9 | [guildId, prefix, prefix] 10 | ); 11 | return; 12 | } 13 | 14 | export async function setDropChannel( 15 | guildId: string, 16 | dropChannel: string 17 | ): Promise { 18 | await DB.query( 19 | `INSERT INTO guild (guild_id, drop_channel_id) VALUES (?, ?) ON DUPLICATE KEY UPDATE drop_channel_id=?;`, 20 | [guildId, dropChannel, dropChannel] 21 | ); 22 | return; 23 | } 24 | 25 | export * as GuildSet from "./GuildSet"; 26 | -------------------------------------------------------------------------------- /src/lib/database/sql/meta/blacklist/BlacklistGet.ts: -------------------------------------------------------------------------------- 1 | import { DB } from "../../.."; 2 | import * as ZephyrError from "../../../../../structures/error/ZephyrError"; 3 | import { 4 | Blacklist, 5 | GameBlacklist, 6 | } from "../../../../../structures/game/blacklist/Blacklist"; 7 | import { GameProfile } from "../../../../../structures/game/Profile"; 8 | 9 | export async function getBlacklistById(id: number): Promise { 10 | const query = (await DB.query(`SELECT * FROM blacklist WHERE id=?;`, [ 11 | id, 12 | ])) as Blacklist[]; 13 | 14 | if (!query[0]) throw new ZephyrError.BlacklistDoesNotExistError(); 15 | 16 | return new GameBlacklist(query[0]); 17 | } 18 | 19 | export async function findBlacklist( 20 | profile: GameProfile 21 | ): Promise { 22 | const query = (await DB.query( 23 | `SELECT * FROM blacklist WHERE active=1 AND discord_id=?;`, 24 | [profile.discordId] 25 | )) as Blacklist[]; 26 | 27 | if (query[0]) { 28 | return new GameBlacklist(query[0]); 29 | } else return; 30 | } 31 | 32 | export async function getProfileBlacklists( 33 | profile: GameProfile 34 | ): Promise { 35 | const query = (await DB.query(`SELECT * FROM blacklist WHERE discord_id=?;`, [ 36 | profile.discordId, 37 | ])) as Blacklist[]; 38 | 39 | return query.map((b) => new GameBlacklist(b)); 40 | } 41 | 42 | export * as BlacklistGet from "./BlacklistGet"; 43 | -------------------------------------------------------------------------------- /src/lib/database/sql/meta/blacklist/BlacklistSet.ts: -------------------------------------------------------------------------------- 1 | import { DB } from "../../.."; 2 | import { GameBlacklist } from "../../../../../structures/game/blacklist/Blacklist"; 3 | import { GameProfile } from "../../../../../structures/game/Profile"; 4 | import { BlacklistService } from "../../../services/meta/BlacklistService"; 5 | 6 | export async function blacklist( 7 | profile: GameProfile, 8 | moderator: GameProfile, 9 | reason: string 10 | ): Promise { 11 | const query = (await DB.query( 12 | `INSERT INTO blacklist (discord_id, reason, moderator_id) VALUES (?, ?, ?);`, 13 | [profile.discordId, reason, moderator.discordId] 14 | )) as { insertId: number }; 15 | 16 | return await BlacklistService.getBlacklistById(query.insertId); 17 | } 18 | 19 | export async function quashBlacklist( 20 | blacklist: GameBlacklist, 21 | quasher: GameProfile, 22 | note: string 23 | ): Promise { 24 | await DB.query( 25 | `UPDATE blacklist SET active=0, quashed_by=?, quash_note=? WHERE id=?;`, 26 | [quasher.discordId, note, blacklist.id] 27 | ); 28 | 29 | return await blacklist.fetch(); 30 | } 31 | 32 | export * as BlacklistSet from "./BlacklistSet"; 33 | -------------------------------------------------------------------------------- /src/lib/database/sql/meta/patreon/PatreonGet.ts: -------------------------------------------------------------------------------- 1 | import { DB } from "../../.."; 2 | import { GameProfile } from "../../../../../structures/game/Profile"; 3 | import { GamePatron, Patron } from "../../../../../structures/meta/Patron"; 4 | import * as ZephyrError from "../../../../../structures/error/ZephyrError"; 5 | 6 | export async function getPatronInformation( 7 | profile: GameProfile 8 | ): Promise { 9 | const query = (await DB.query(`SELECT * FROM patron WHERE discord_id=?;`, [ 10 | profile.discordId, 11 | ])) as Patron[]; 12 | if (!query[0]) throw new ZephyrError.NotAPatronError(); 13 | 14 | return new GamePatron(query[0]); 15 | } 16 | 17 | export * as PatreonGet from "./PatreonGet"; 18 | -------------------------------------------------------------------------------- /src/lib/database/sql/meta/patreon/PatreonSet.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | import { DB } from "../../.."; 3 | import { GameProfile } from "../../../../../structures/game/Profile"; 4 | 5 | export async function addPatron(profile: GameProfile): Promise { 6 | await DB.query( 7 | `INSERT INTO patron (discord_id) VALUES (?) ON DUPLICATE KEY UPDATE discord_id=discord_id;`, 8 | [profile.discordId] 9 | ); 10 | return; 11 | } 12 | 13 | export async function removePatron(profile: GameProfile): Promise { 14 | await DB.query(`DELETE FROM patron WHERE discord_id=?;`, [profile.discordId]); 15 | return; 16 | } 17 | 18 | export async function setNextPatreonClaimTime( 19 | profile: GameProfile 20 | ): Promise { 21 | const formattedTimestamp = dayjs() 22 | .add(1, "month") 23 | .startOf("month") 24 | .format(`YYYY/MM/DD HH:mm:ss`); 25 | 26 | await DB.query(`UPDATE patron SET next_frame_time=? WHERE discord_id=?;`, [ 27 | formattedTimestamp, 28 | profile.discordId, 29 | ]); 30 | } 31 | 32 | export * as PatreonSet from "./PatreonSet"; 33 | -------------------------------------------------------------------------------- /src/lib/database/sql/meta/poll/PollGet.ts: -------------------------------------------------------------------------------- 1 | import { DB } from "../../.."; 2 | import * as ZephyrError from "../../../../../structures/error/ZephyrError"; 3 | import { GameProfile } from "../../../../../structures/game/Profile"; 4 | import { 5 | Answer, 6 | GameAnswer, 7 | GamePoll, 8 | Poll, 9 | } from "../../../../../structures/poll/Poll"; 10 | 11 | export async function getPollById(id: number): Promise { 12 | const query = (await DB.query(`SELECT * FROM poll WHERE id=?;`, [ 13 | id, 14 | ])) as Poll[]; 15 | 16 | if (!query[0]) throw new ZephyrError.PollDoesNotExistError(id); 17 | 18 | return new GamePoll(query[0]); 19 | } 20 | 21 | export async function getActivePolls(): Promise { 22 | const query = (await DB.query( 23 | `SELECT * FROM poll WHERE active=1 AND CURRENT_TIMESTAMP < ends_at;` 24 | )) as Poll[]; 25 | 26 | return query.map((p) => new GamePoll(p)); 27 | } 28 | 29 | export async function userAnsweredPoll( 30 | poll: GamePoll, 31 | profile: GameProfile 32 | ): Promise { 33 | const query = (await DB.query( 34 | `SELECT * FROM poll_answer WHERE poll_id=? AND discord_id=?;`, 35 | [poll.id, profile.discordId] 36 | )) as Answer[]; 37 | 38 | return !!query[0]; 39 | } 40 | 41 | export async function getAnswerById(id: number): Promise { 42 | const query = (await DB.query(`SELECT * FROM poll_answer WHERE id=?;`, [ 43 | id, 44 | ])) as Answer[]; 45 | 46 | if (!query[0]) throw new ZephyrError.PollAnswerDoesNotExistError(); 47 | 48 | return new GameAnswer(query[0]); 49 | } 50 | 51 | export async function getPollStandings( 52 | poll: GamePoll 53 | ): Promise<{ yes: number; no: number }> { 54 | const query = (await DB.query( 55 | `SELECT IFNULL(SUM(answer="yes"), 0) AS yes, IFNULL(SUM(answer="no"), 0) AS no FROM poll_answer WHERE poll_id=?;`, 56 | [poll.id] 57 | )) as { yes: number; no: number }[]; 58 | 59 | return query[0]; 60 | } 61 | 62 | export * as PollGet from "./PollGet"; 63 | -------------------------------------------------------------------------------- /src/lib/database/sql/meta/poll/PollSet.ts: -------------------------------------------------------------------------------- 1 | import { DB } from "../../.."; 2 | import { GameProfile } from "../../../../../structures/game/Profile"; 3 | import { GameAnswer, GamePoll } from "../../../../../structures/poll/Poll"; 4 | import { PollService } from "../../../services/meta/PollService"; 5 | 6 | export async function createPoll( 7 | title: string, 8 | body: string, 9 | endsAt?: string 10 | ): Promise { 11 | let query; 12 | 13 | if (endsAt) { 14 | query = (await DB.query( 15 | `INSERT INTO poll (title, body, ends_at) VALUES (?, ?, ?);`, 16 | [title, body, endsAt] 17 | )) as { insertId: number }; 18 | } else 19 | query = (await DB.query(`INSERT INTO poll (title, body) VALUES (?, ?);`, [ 20 | title, 21 | body, 22 | ])) as { insertId: number }; 23 | 24 | return await PollService.getPollById(query.insertId); 25 | } 26 | 27 | export async function activatePoll(poll: GamePoll): Promise { 28 | await DB.query(`UPDATE poll SET active=1 WHERE id=?;`, [poll.id]); 29 | 30 | return await poll.fetch(); 31 | } 32 | 33 | export async function deactivatePoll(poll: GamePoll): Promise { 34 | await DB.query(`UPDATE poll SET active=0 WHERE id=?;`, [poll.id]); 35 | 36 | return await poll.fetch(); 37 | } 38 | 39 | export async function deletePoll(poll: GamePoll): Promise { 40 | await DB.query(`DELETE FROM poll WHERE id=?;`, [poll.id]); 41 | 42 | return; 43 | } 44 | 45 | export async function answerPoll( 46 | poll: GamePoll, 47 | profile: GameProfile, 48 | answer: `yes` | `no` 49 | ): Promise { 50 | const query = (await DB.query( 51 | `INSERT INTO poll_answer (poll_id, discord_id, answer) VALUES (?, ?, ?);`, 52 | [poll.id, profile.discordId, answer] 53 | )) as { insertId: number }; 54 | 55 | return await PollService.getAnswerById(query.insertId); 56 | } 57 | 58 | export * as PollSet from "./PollSet"; 59 | -------------------------------------------------------------------------------- /src/lib/discord/Retry.ts: -------------------------------------------------------------------------------- 1 | import { StatsD } from "../StatsD"; 2 | 3 | export const wait: (ms: number) => Promise = (ms: number) => 4 | new Promise((r) => setTimeout(r, ms)); 5 | 6 | export function retryOperation( 7 | operation: (...args: any) => Promise, 8 | delay: number, 9 | retries: number 10 | ): Promise { 11 | return new Promise(async (resolve, reject) => { 12 | try { 13 | return resolve(await operation()); 14 | } catch (e) { 15 | if (retries <= 0) return reject(e); 16 | 17 | if (retries > 0) { 18 | await wait(delay); 19 | StatsD.increment(`zephyr.message.retry`, 1) 20 | return resolve(retryOperation(operation, delay, retries - 1)); 21 | } 22 | } 23 | }) as Promise; 24 | } 25 | -------------------------------------------------------------------------------- /src/lib/discord/message/addReaction.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 3 | import { retryOperation } from "../Retry"; 4 | import { StatsD } from "../../StatsD"; 5 | import { Logger } from "../../logger/Logger"; 6 | 7 | export async function addReaction( 8 | message: Message, 9 | reaction: string 10 | ): Promise { 11 | StatsD.increment(`zephyr.message.add_reaction`, 1); 12 | await retryOperation( 13 | async () => message.addReaction(reaction), 14 | 10000, 15 | 3 16 | ).catch((e) => { 17 | Logger.error( 18 | `Failed trying to add reaction ${reaction} to message ${message.id}. Full stack:\n${e.stack}` 19 | ); 20 | throw new ZephyrError.FailedToAddReactionError(); 21 | }); 22 | 23 | return; 24 | } 25 | -------------------------------------------------------------------------------- /src/lib/discord/message/createMessage.ts: -------------------------------------------------------------------------------- 1 | import { Message, MessageFile, TextableChannel } from "eris"; 2 | import { ErisFile } from "../../../structures/client/ErisFile"; 3 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 4 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 5 | import { retryOperation } from "../Retry"; 6 | import { StatsD } from "../../StatsD"; 7 | import { Logger } from "../../logger/Logger"; 8 | 9 | function deriveFileExtension(url: string | Buffer): string { 10 | if (url instanceof Buffer) { 11 | return ""; 12 | } else return url.split(".")[url.split(".").length - 1]; 13 | } 14 | 15 | export async function createMessage( 16 | channel: TextableChannel, 17 | body: string | MessageEmbed, 18 | options?: { embed?: MessageEmbed; files?: ErisFile[] } 19 | ): Promise> { 20 | let embed: MessageEmbed | undefined; 21 | let content: string; 22 | 23 | if (body instanceof MessageEmbed) { 24 | embed = body; 25 | } else content = body; 26 | 27 | let derivedFiles: MessageFile[] = []; 28 | 29 | if (options) { 30 | if (options.embed) { 31 | embed = options.embed; 32 | } 33 | if (options.files) { 34 | for (let file of options.files) { 35 | let fileName = file.name; 36 | 37 | if (!fileName) 38 | fileName = `untitled-image.${deriveFileExtension(file.file)}`; 39 | 40 | derivedFiles.push({ file: file.file, name: fileName }); 41 | } 42 | } 43 | } 44 | 45 | let message; 46 | 47 | StatsD.increment(`zephyr.message.create`, 1); 48 | message = await retryOperation( 49 | async () => channel.createMessage({ content, embed }, derivedFiles), 50 | 10000, 51 | 3 52 | ).catch((e) => { 53 | if (!e?.stack.includes(`[50013]:`)) { 54 | if (channel.type !== 1) { 55 | Logger.error( 56 | `Failed trying to send message with content ${content} (embed content: ${embed?.description}) in channel ${channel.id}. Full stack:\n${e.stack}` 57 | ); 58 | } 59 | } 60 | 61 | throw new ZephyrError.MessageFailedToSendError(); 62 | }); 63 | 64 | return message; 65 | } 66 | -------------------------------------------------------------------------------- /src/lib/discord/message/deleteMessage.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 3 | import { retryOperation } from "../Retry"; 4 | import { StatsD } from "../../StatsD"; 5 | import { Logger } from "../../logger/Logger"; 6 | 7 | export async function deleteMessage(message: Message): Promise { 8 | StatsD.increment(`zephyr.message.delete`, 1); 9 | await retryOperation(async () => message.delete(), 10000, 3).catch((e) => { 10 | Logger.error( 11 | `Failed trying to delete message ${message.id}. Full stack:\n${e.stack}` 12 | ); 13 | throw new ZephyrError.FailedToDeleteMessageError(); 14 | }); 15 | 16 | return; 17 | } 18 | -------------------------------------------------------------------------------- /src/lib/discord/message/editMessage.ts: -------------------------------------------------------------------------------- 1 | import { Message, TextableChannel } from "eris"; 2 | import { MessageEmbed } from "../../../structures/client/RichEmbed"; 3 | import * as ZephyrError from "../../../structures/error/ZephyrError"; 4 | import { retryOperation } from "../Retry"; 5 | import { StatsD } from "../../StatsD"; 6 | import { Logger } from "../../logger/Logger"; 7 | 8 | export async function editMessage( 9 | msg: Message, 10 | body: string | MessageEmbed, 11 | options?: { embed?: MessageEmbed } 12 | ): Promise> { 13 | let embed: MessageEmbed; 14 | let content: string; 15 | 16 | if (body instanceof MessageEmbed) { 17 | embed = body; 18 | } else content = body; 19 | 20 | if (options) { 21 | if (options.embed) { 22 | embed = options.embed; 23 | } 24 | } 25 | 26 | let message: Message; 27 | 28 | StatsD.increment(`zephyr.message.edit`, 1); 29 | message = await retryOperation( 30 | async () => msg.edit({ content, embed }), 31 | 10000, 32 | 3 33 | ).catch((e) => { 34 | Logger.error( 35 | `Failed trying to edit message ${msg.id} with content ${content} in channel ${msg.channel.id}. Full stack:\n${e.stack}` 36 | ); 37 | throw new ZephyrError.FailedToEditMessageError(); 38 | }); 39 | 40 | if (!message) throw new ZephyrError.FailedToEditMessageError(); 41 | return message; 42 | } 43 | -------------------------------------------------------------------------------- /src/lib/discord/readme.md: -------------------------------------------------------------------------------- 1 | This folder is a library of Eris functions that have been extended to house various improvements and, most notably, better handling of 500 Errors. 2 | -------------------------------------------------------------------------------- /src/lib/items/UseBackground.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { MessageEmbed } from "../../structures/client/RichEmbed"; 3 | import { GameProfile } from "../../structures/game/Profile"; 4 | import { PrefabItem } from "../../structures/item/PrefabItem"; 5 | import { ProfileService } from "../database/services/game/ProfileService"; 6 | import * as ZephyrError from "../../structures/error/ZephyrError"; 7 | import { createMessage } from "../discord/message/createMessage"; 8 | import { AlbumService } from "../database/services/game/AlbumService"; 9 | import { Backgrounds } from "../cosmetics/Backgrounds"; 10 | 11 | export async function useBackground( 12 | msg: Message, 13 | profile: GameProfile, 14 | parameters: string[], 15 | item: PrefabItem 16 | ): Promise { 17 | const albumName = parameters[0]?.toLowerCase(); 18 | if (!albumName) throw new ZephyrError.InvalidAlbumNameError(); 19 | 20 | const targetBackground = Backgrounds.getBackgroundByName(item.names[0]); 21 | 22 | if (!targetBackground) throw new ZephyrError.InvalidAlbumBackgroundError(); 23 | 24 | const album = await AlbumService.getAlbumByName( 25 | albumName, 26 | msg.author, 27 | msg.author 28 | ); 29 | 30 | if (album.backgroundId === targetBackground.id) 31 | throw new ZephyrError.DuplicateAlbumBackgroundError(targetBackground.name); 32 | 33 | const cards = await AlbumService.getCardsByAlbum(album); 34 | 35 | const newAlbum = await AlbumService.setAlbumBackground( 36 | album, 37 | targetBackground.id 38 | ); 39 | 40 | const image = await AlbumService.updateAlbumCache(newAlbum, cards, 1); 41 | 42 | await ProfileService.removeItems(profile, [{ item: item, count: 1 }]); 43 | 44 | const embed = new MessageEmbed(`Use Background`, msg.author) 45 | .setDescription( 46 | `The background of \`${newAlbum.name}\` was changed to **${targetBackground.name}**!` 47 | ) 48 | .setImage(`attachment://album.png`); 49 | 50 | await createMessage(msg.channel, embed, { 51 | files: [{ file: image, name: `album.png` }], 52 | }); 53 | 54 | return; 55 | } 56 | -------------------------------------------------------------------------------- /src/lib/logger/Logger.ts: -------------------------------------------------------------------------------- 1 | import winston from "winston"; 2 | 3 | export const Logger = winston.createLogger({ 4 | level: `debug`, 5 | format: winston.format.cli(), 6 | transports: [ 7 | new winston.transports.File({ filename: `error.log`, level: `error` }), 8 | new winston.transports.Console({ format: winston.format.simple() }), 9 | ], 10 | }); 11 | 12 | export const loggerSettings = { 13 | debug: true, 14 | verbose: true, 15 | }; 16 | -------------------------------------------------------------------------------- /src/lib/quest/Quest.ts: -------------------------------------------------------------------------------- 1 | import { quests } from "../../assets/quests"; 2 | import { BaseQuest } from "../../structures/game/quest/BaseQuest"; 3 | import { 4 | ItemReward, 5 | QuestReward, 6 | RewardChance, 7 | XPReward, 8 | } from "../../structures/game/quest/QuestReward"; 9 | import { Logger, loggerSettings } from "../logger/Logger"; 10 | 11 | class QuestService { 12 | private quests: BaseQuest[] = []; 13 | 14 | public loadQuests(): BaseQuest[] { 15 | const questIds: Set = new Set(); 16 | for (let quest of quests) { 17 | if (questIds.has(quest.id)) { 18 | Logger.error(`[Quest ID: ${quest.id}] Duplicate Quest ID! Skipping...`); 19 | continue; 20 | } 21 | 22 | if (quest.completion.length === 0) { 23 | Logger.error( 24 | `[Quest ID: ${quest.id}] No completion criteria set! Skipping...` 25 | ); 26 | continue; 27 | } 28 | 29 | this.quests.push(quest); 30 | 31 | if (loggerSettings.verbose) { 32 | Logger.verbose(`[Quest ID: ${quest.id}] Loaded successfully.`); 33 | } 34 | } 35 | 36 | return this.quests; 37 | } 38 | 39 | public getQuestById(id: number): BaseQuest | undefined { 40 | const quest = this.quests.find((q) => q.id === id); 41 | 42 | if (!quest) { 43 | Logger.error(`[Quest ID: ${id}] Quest not found!`); 44 | return; 45 | } 46 | 47 | return quest; 48 | } 49 | 50 | public getQuests(): BaseQuest[] { 51 | return this.quests; 52 | } 53 | 54 | public isXpReward(reward: QuestReward): reward is XPReward & RewardChance { 55 | return (reward as XPReward).xp !== undefined; 56 | } 57 | 58 | public isItemReward( 59 | reward: QuestReward 60 | ): reward is ItemReward & RewardChance { 61 | return (reward as ItemReward).item !== undefined; 62 | } 63 | } 64 | 65 | export const Quests = new QuestService(); 66 | -------------------------------------------------------------------------------- /src/lib/shop/Shop.ts: -------------------------------------------------------------------------------- 1 | import { items } from "../../assets/Items"; 2 | import { GameShop, IntermediateShop } from "../../structures/shop/Shop"; 3 | import { ShopGetter } from "../database/sql/game/shop/ShopGetter"; 4 | import { Logger } from "../logger/Logger"; 5 | 6 | class ShopService { 7 | private shop: GameShop[] = []; 8 | 9 | public async loadShop(): Promise { 10 | const shop = await ShopGetter.getShopItems(); 11 | 12 | const finalShop = []; 13 | for (let item of shop) { 14 | const prefabItem = items.find((i) => i.id === item.item_id); 15 | 16 | if (!prefabItem) { 17 | Logger.error(`Item not found for shop entry ${item.id}.`); 18 | continue; 19 | } 20 | 21 | const intermediate: IntermediateShop = { 22 | id: item.id, 23 | item: prefabItem, 24 | price: item.price, 25 | currency: item.currency, 26 | featured: item.featured, 27 | }; 28 | 29 | finalShop.push(new GameShop(intermediate)); 30 | } 31 | 32 | this.shop = finalShop; 33 | return this.shop; 34 | } 35 | 36 | public getShop(): GameShop[] { 37 | return this.shop; 38 | } 39 | 40 | public getShopItemByName(name: string): GameShop | undefined { 41 | return this.shop.find((s) => 42 | s.item.names.map((n) => n.toLowerCase()).includes(name.toLowerCase()) 43 | ); 44 | } 45 | 46 | public async init(): Promise { 47 | await this.loadShop(); 48 | return; 49 | } 50 | } 51 | 52 | export const Shop = new ShopService(); 53 | -------------------------------------------------------------------------------- /src/lib/utility/color/ColorUtils.ts: -------------------------------------------------------------------------------- 1 | import nearestColor from "nearest-color"; 2 | import colorNames from "color-name-list"; 3 | 4 | export function rgbToCmy( 5 | r: number, 6 | g: number, 7 | b: number 8 | ): { c: number; m: number; y: number } { 9 | return { 10 | c: (1 - r / 255) * 100, 11 | m: (1 - g / 255) * 100, 12 | y: (1 - b / 255) * 100, 13 | }; 14 | } 15 | 16 | export function rgbToHex(r: number, g: number, b: number): string { 17 | return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); 18 | } 19 | 20 | export function hexToRgb(hex: string): { r: number; g: number; b: number } { 21 | const r = parseInt(hex.slice(0, 2), 16); 22 | const g = parseInt(hex.slice(2, 4), 16); 23 | const b = parseInt(hex.slice(4, 6), 16); 24 | 25 | return { r, g, b }; 26 | } 27 | 28 | export function hexToCmy(hex: string): { c: number; m: number; y: number } { 29 | const rgb = hexToRgb(hex); 30 | return rgbToCmy(rgb.r, rgb.g, rgb.b); 31 | } 32 | 33 | export function getNearestColor( 34 | hex: string 35 | ): { 36 | name: string; 37 | value: string; 38 | rgb: { r: number; g: number; b: number }; 39 | distance: number; 40 | } { 41 | const nearest = nearestColor.from( 42 | colorNames.reduce( 43 | (o, { name, hex }) => Object.assign(o, { [name]: hex }), 44 | {} 45 | ) 46 | ); 47 | const trueNearest = nearest(hex) as { 48 | name: string; 49 | value: string; 50 | rgb: { r: number; g: number; b: number }; 51 | distance: number; 52 | }; 53 | 54 | return trueNearest; 55 | } 56 | -------------------------------------------------------------------------------- /src/lib/utility/time/TimeUtils.ts: -------------------------------------------------------------------------------- 1 | import dayjs, { Dayjs } from "dayjs"; 2 | import { padIfNotLeading } from "../text/TextUtils"; 3 | 4 | export function getCurrentTimestamp(): string { 5 | return dayjs().format(`YYYY/MM/DD HH:mm:ss`); 6 | } 7 | 8 | export function dateTimeDisplay(date: Dayjs): string { 9 | return date.format(`MMMM D, YYYY`); 10 | } 11 | 12 | export function getTimeUntil(from: Dayjs, to: Dayjs): string { 13 | const days = to.diff(from, "d"); 14 | const hours = to.diff(from, "h"); 15 | const minutes = to.diff(from, "m") - hours * 60; 16 | const seconds = to.diff(from, "s") - to.diff(from, "m") * 60; 17 | const ms = to.diff(from, "ms") - to.diff(from, "s") * 1000; 18 | 19 | const daysText = days > 0 ? `${days}d ` : ``; 20 | const hoursText = 21 | hours > 0 ? `${padIfNotLeading(hours - days * 24, days === 0)}h ` : ``; 22 | const minutesText = 23 | minutes > 0 ? `${padIfNotLeading(minutes, hours === 0)}m ` : ``; 24 | const secondsText = 25 | seconds > 0 ? `${padIfNotLeading(seconds, minutes === 0)}s` : ``; 26 | const msText = ms > 0 ? `${padIfNotLeading(ms, seconds === 0)}ms` : ``; 27 | 28 | if (ms === 0 && seconds === 0 && minutes === 0 && hours === 0 && days === 0) { 29 | return `Now`; 30 | } else if (seconds === 0 && minutes === 0 && hours === 0 && days === 0) { 31 | return msText; 32 | } else { 33 | return ( 34 | `${daysText}` + 35 | `${hoursText}` + 36 | `${minutesText}` + 37 | `${secondsText}` 38 | ).trim(); 39 | } 40 | } 41 | 42 | export function getTimeUntilNextDay(): string { 43 | const today = dayjs(); 44 | 45 | return getTimeUntil(today, today.add(1, `day`).startOf(`day`)); 46 | } 47 | -------------------------------------------------------------------------------- /src/structures/client/ErisFile.ts: -------------------------------------------------------------------------------- 1 | export type ErisFile = { 2 | file: string | Buffer; 3 | name?: string; 4 | }; 5 | -------------------------------------------------------------------------------- /src/structures/client/RichEmbed.ts: -------------------------------------------------------------------------------- 1 | import { User } from "eris"; 2 | 3 | interface EmbedField { 4 | name: string; 5 | value: string; 6 | inline?: boolean; 7 | } 8 | 9 | export class MessageEmbed { 10 | title?: string; 11 | description?: string; 12 | author?: { 13 | name: string; 14 | url?: string; 15 | icon_url?: string; 16 | }; 17 | color?: number = parseInt("1FB7CF", 16); 18 | footer?: { text: string; icon_url?: string }; 19 | fields: EmbedField[] = []; 20 | image?: { 21 | url: string; 22 | }; 23 | thumbnail?: { 24 | url: string; 25 | }; 26 | 27 | constructor(title?: string, user?: User) { 28 | if (title && user) { 29 | this.author = { 30 | name: `${title} | ${user.tag}`, 31 | icon_url: user.dynamicAvatarURL("png"), 32 | }; 33 | } else if (title && !user) { 34 | this.author = { name: `${title}` }; 35 | } 36 | } 37 | 38 | public setTitle(title: string): MessageEmbed { 39 | this.title = title; 40 | return this; 41 | } 42 | 43 | public setDescription(description: string): MessageEmbed { 44 | this.description = description; 45 | return this; 46 | } 47 | 48 | public setAuthor(name: string, iconUrl?: string, url?: string): MessageEmbed { 49 | this.author = { name, icon_url: iconUrl, url }; 50 | return this; 51 | } 52 | 53 | public setColor(color: string | number): MessageEmbed { 54 | if (typeof color === "string") { 55 | if (color.startsWith("#")) { 56 | this.color = parseInt(color.slice(1), 16); 57 | } else this.color = parseInt(color, 16); 58 | } else this.color = color; 59 | return this; 60 | } 61 | 62 | public setFooter(footer: string, iconUrl?: string): MessageEmbed { 63 | this.footer = { text: footer, icon_url: iconUrl }; 64 | return this; 65 | } 66 | 67 | public addField(field: { 68 | name: string; 69 | value: string; 70 | inline?: boolean; 71 | }): MessageEmbed { 72 | this.fields.push(field); 73 | return this; 74 | } 75 | 76 | public addFields( 77 | fields: { name: string; value: string; inline?: boolean }[] 78 | ): MessageEmbed { 79 | this.fields.push(...fields); 80 | return this; 81 | } 82 | 83 | public setImage(url: string): MessageEmbed { 84 | this.image = { url }; 85 | return this; 86 | } 87 | 88 | public setThumbnail(url: string): MessageEmbed { 89 | this.thumbnail = { url }; 90 | return this; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/structures/client/commands/HelloCommand.ts: -------------------------------------------------------------------------------- 1 | import { 2 | SlashCommand, 3 | CommandOptionType, 4 | SlashCreator, 5 | CommandContext, 6 | } from "slash-create"; 7 | 8 | module.exports = class HelloCommand extends ( 9 | SlashCommand 10 | ) { 11 | constructor(creator: SlashCreator) { 12 | super(creator, { 13 | name: `hello`, 14 | description: `Says hello to you.`, 15 | options: [ 16 | { 17 | type: CommandOptionType.STRING, 18 | name: `food`, 19 | description: `What food do you like?`, 20 | }, 21 | ], 22 | }); 23 | 24 | this.filePath = __filename; 25 | } 26 | 27 | async run(ctx: CommandContext) { 28 | return ctx.options.food 29 | ? `You like ${ctx.options.food}? Nice!` 30 | : `Hello, ${ctx.user.username}!`; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /src/structures/command/Command.ts: -------------------------------------------------------------------------------- 1 | import { Message, User } from "eris"; 2 | import { addReaction } from "../../lib/discord/message/addReaction"; 3 | import { createMessage } from "../../lib/discord/message/createMessage"; 4 | import { deleteMessage } from "../../lib/discord/message/deleteMessage"; 5 | import { editMessage } from "../../lib/discord/message/editMessage"; 6 | import { MessageEmbed } from "../client/RichEmbed"; 7 | import { GameProfile } from "../game/Profile"; 8 | 9 | export interface Command { 10 | id?: string; 11 | names: string[]; 12 | description: string; 13 | usage: string[]; 14 | subcommands: string[]; 15 | allowDm: boolean; 16 | exec(msg: Message, profile: GameProfile, options: string[]): Promise; 17 | } 18 | 19 | export abstract class BaseCommand implements Command { 20 | id?: string; 21 | names: string[] = []; 22 | description: string = "This command has no description!"; 23 | usage: string[] = []; 24 | subcommands: string[] = []; 25 | allowDm = false; 26 | developerOnly = false; 27 | moderatorOnly = false; 28 | 29 | path: string | undefined; // Used for "hot-swapping" commands... weird? 30 | 31 | disabled = false; 32 | disabledMessage = `This command is currently disabled.`; 33 | 34 | abstract exec( 35 | msg: Message, 36 | profile: GameProfile, 37 | options: string[] 38 | ): Promise; 39 | 40 | public async run(msg: Message, profile: GameProfile) { 41 | let options = []; 42 | 43 | for (const [index, option] of msg.content.split(` `).entries()) { 44 | if (index === 0 && option.includes(`\n`)) { 45 | const splitOptions = option.split(`\n`).slice(1); 46 | 47 | for (let splitOption of splitOptions) { 48 | options.push(splitOption); 49 | } 50 | continue; 51 | } 52 | options.push(option.trim()); 53 | } 54 | 55 | // Parses out the command itself and removes empty elements 56 | options = options.slice(1).filter((o) => o); 57 | 58 | await this.exec(msg, profile, options); 59 | } 60 | 61 | public selfDestruct(): string { 62 | return "💥 Self destructing..."; 63 | } 64 | 65 | public async handleError( 66 | msg: Message, 67 | user: User, 68 | error: Error 69 | ): Promise { 70 | const embed = new MessageEmbed(`Error`, user).setDescription(error.message); 71 | await createMessage(msg.channel, embed); 72 | return; 73 | } 74 | 75 | public send = createMessage; 76 | public react = addReaction; 77 | public edit = editMessage; 78 | public delete = deleteMessage; 79 | } 80 | -------------------------------------------------------------------------------- /src/structures/error/BaseError.ts: -------------------------------------------------------------------------------- 1 | export abstract class BaseError extends Error { 2 | name = `BaseError`; 3 | message = `An unknown error occurred.`; 4 | isClientFacing = true; 5 | header = `Error`; 6 | } 7 | -------------------------------------------------------------------------------- /src/structures/error/WishlistError.ts: -------------------------------------------------------------------------------- 1 | import { GameIdol } from "../game/Idol"; 2 | import { ZephyrError } from "./ZephyrError"; 3 | 4 | export class WishlistFullError extends ZephyrError { 5 | constructor(prefix: string) { 6 | super( 7 | `**You have no more room on your wishlist!**\nRemove some idols or \`${prefix}donate\` to get more slots!` 8 | ); 9 | } 10 | } 11 | 12 | export class WishlistEmptyError extends ZephyrError { 13 | constructor(prefix: string) { 14 | super( 15 | `**Your wishlist is empty!**\nYou can add idols by using \`${prefix}wa \`.` 16 | ); 17 | } 18 | } 19 | 20 | export class WishlistDuplicateError extends ZephyrError { 21 | constructor(idol: GameIdol, prefix: string) { 22 | super( 23 | `**${ 24 | idol.name 25 | } is already on your wishlist!**\nYou can remove them by using \`${prefix}wr ${idol.name.toLowerCase()}\`!` 26 | ); 27 | } 28 | } 29 | 30 | export class IdolNotOnWishlistError extends ZephyrError { 31 | constructor() { 32 | super( 33 | `**No idols on your wishlist match that name!**\nMake sure you spelled everything correctly!` 34 | ); 35 | } 36 | } 37 | 38 | export * as WishlistError from "./WishlistError"; 39 | -------------------------------------------------------------------------------- /src/structures/game/Album.ts: -------------------------------------------------------------------------------- 1 | import { Image } from "canvas"; 2 | import { AlbumService } from "../../lib/database/services/game/AlbumService"; 3 | 4 | export interface Album { 5 | readonly id: number; 6 | readonly discord_id: string; 7 | readonly album_name: string; 8 | readonly pages: number; 9 | readonly background_id: number; 10 | readonly background_name: string; 11 | readonly image_url: string; 12 | } 13 | 14 | export interface AlbumCard { 15 | readonly id: number; 16 | readonly card_id: number; 17 | readonly album_id: number; 18 | readonly slot: number; 19 | } 20 | 21 | export interface AlbumBackground { 22 | readonly id: number; 23 | readonly background_name: string; 24 | readonly image_url: string; 25 | } 26 | 27 | export class GameAlbumCard { 28 | readonly id: number; 29 | readonly cardId: number; 30 | readonly albumId: number; 31 | readonly slot: number; 32 | 33 | constructor(data: AlbumCard) { 34 | this.id = data.id; 35 | this.cardId = data.card_id; 36 | this.albumId = data.album_id; 37 | this.slot = data.slot; 38 | } 39 | } 40 | 41 | export class GameAlbum { 42 | readonly id: number; 43 | readonly discordId: string; 44 | readonly name: string; 45 | readonly pages: number; 46 | 47 | readonly backgroundId: number; 48 | readonly backgroundName: string; 49 | readonly backgroundUrl: string; 50 | constructor(data: Album) { 51 | this.id = data.id; 52 | this.discordId = data.discord_id; 53 | this.name = data.album_name; 54 | this.pages = data.pages; 55 | 56 | this.backgroundId = data.background_id; 57 | this.backgroundName = data.background_name; 58 | this.backgroundUrl = data.image_url; 59 | } 60 | 61 | public async fetch(): Promise { 62 | return await AlbumService.getAlbumById(this.id); 63 | } 64 | } 65 | 66 | export interface IntermediateBackground { 67 | readonly id: number; 68 | readonly background_name: string; 69 | readonly image: Image; 70 | } 71 | 72 | export class GameAlbumBackground { 73 | readonly id: number; 74 | readonly name: string; 75 | readonly image: Image; 76 | 77 | constructor(data: IntermediateBackground) { 78 | this.id = data.id; 79 | this.name = data.background_name; 80 | this.image = data.image; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/structures/game/Autotag.ts: -------------------------------------------------------------------------------- 1 | import { AutotagService } from "../../lib/database/services/game/AutotagService"; 2 | 3 | export interface Autotag { 4 | id: number; 5 | discord_id: string; 6 | autotag_key: `idol` | `group` | `wear` | `issue`; 7 | autotag_value: number; 8 | priority: number; 9 | tag_id: number; 10 | } 11 | 12 | export class GameAutotag { 13 | id: number; 14 | discordId: string; 15 | key: `idol` | `group` | `wear` | `issue`; 16 | value: number; 17 | priority: number; 18 | tagId: number; 19 | constructor(autotag: Autotag) { 20 | this.id = autotag.id; 21 | this.discordId = autotag.discord_id; 22 | this.key = autotag.autotag_key; 23 | this.value = autotag.autotag_value; 24 | this.priority = autotag.priority; 25 | this.tagId = autotag.tag_id; 26 | } 27 | 28 | public async fetch(): Promise { 29 | return await AutotagService.getAutotagById(this.id); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/structures/game/Badge.ts: -------------------------------------------------------------------------------- 1 | import { BadgeService } from "../../lib/database/services/game/BadgeService"; 2 | 3 | export interface Badge { 4 | readonly id: number; 5 | readonly badge_name: string; 6 | readonly badge_emoji: string; 7 | readonly badge_description: string | null; 8 | } 9 | 10 | export interface UserBadge { 11 | readonly id: number; 12 | readonly discord_id: string; 13 | readonly badge_id: number; 14 | readonly created_at: string; 15 | readonly badge_name: string; 16 | readonly badge_emoji: string; 17 | readonly badge_description: string | null; 18 | } 19 | 20 | export class GameBadge { 21 | readonly id: number; 22 | readonly badgeName: string; 23 | readonly badgeEmoji: string; 24 | readonly badgeDescription: string | undefined; 25 | 26 | constructor(badge: Badge) { 27 | this.id = badge.id; 28 | this.badgeName = badge.badge_name; 29 | this.badgeEmoji = badge.badge_emoji; 30 | this.badgeDescription = badge.badge_description || undefined; 31 | } 32 | 33 | public async fetch(): Promise { 34 | return await BadgeService.getBadgeById(this.id); 35 | } 36 | } 37 | 38 | export class GameUserBadge { 39 | readonly id: number; 40 | readonly discordId: string; 41 | readonly badgeId: number; 42 | readonly createdAt: string; 43 | readonly badgeName: string; 44 | readonly badgeEmoji: string; 45 | readonly badgeDescription: string | undefined; 46 | 47 | constructor(userBadge: UserBadge) { 48 | this.id = userBadge.id; 49 | this.discordId = userBadge.discord_id; 50 | this.badgeId = userBadge.badge_id; 51 | this.badgeName = userBadge.badge_name; 52 | this.badgeEmoji = userBadge.badge_emoji; 53 | this.createdAt = userBadge.created_at; 54 | this.badgeDescription = userBadge.badge_description || undefined; 55 | } 56 | 57 | public async fetch(): Promise { 58 | return await BadgeService.getUserBadgeById(this.id); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/structures/game/BaseCard.ts: -------------------------------------------------------------------------------- 1 | import dayjs from "dayjs"; 2 | 3 | export interface BaseCard { 4 | readonly id: number; 5 | readonly group_name: string | null; 6 | readonly group_id: number | null; 7 | readonly idol_id: number; 8 | readonly subgroup_id: number | null; 9 | readonly idol_name: string; 10 | readonly birthday: string; 11 | readonly subgroup_name: string | null; 12 | readonly image_url: string; 13 | readonly rarity: number; 14 | readonly serial_total: number; 15 | readonly serial_limit: number; 16 | readonly num_generated: number; 17 | readonly emoji: string | null; 18 | readonly archived: boolean; 19 | readonly activated: boolean; 20 | } 21 | 22 | export class GameBaseCard { 23 | readonly id: number; 24 | readonly group?: string; 25 | readonly groupId?: number; 26 | readonly subgroup?: string; 27 | readonly idolId: number; 28 | readonly subgroupId?: number; 29 | readonly name: string; 30 | readonly image: string; 31 | readonly rarity: number; 32 | serialTotal: number; 33 | readonly serialLimit: number; 34 | readonly totalGenerated: number; 35 | readonly emoji?: string; 36 | readonly archived: boolean; 37 | readonly birthday?: string; 38 | readonly activated: boolean; 39 | 40 | constructor(card: BaseCard) { 41 | this.id = card.id; 42 | 43 | this.group = card.group_name || undefined; 44 | this.groupId = card.group_id || undefined; 45 | 46 | this.subgroup = card.subgroup_name || undefined; 47 | this.subgroupId = card.subgroup_id || undefined; 48 | 49 | this.name = card.idol_name; 50 | this.idolId = card.idol_id; 51 | 52 | this.image = card.image_url; 53 | this.rarity = card.rarity; 54 | this.serialLimit = card.serial_limit; 55 | this.serialTotal = card.serial_total; 56 | this.totalGenerated = card.num_generated; 57 | this.emoji = card.emoji || undefined; 58 | this.archived = card.archived; 59 | this.activated = card.activated; 60 | if (card.birthday) 61 | this.birthday = dayjs(card.birthday).format(`YYYY-MM-DD`); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/structures/game/Dust.ts: -------------------------------------------------------------------------------- 1 | export type Dust = 1 | 2 | 3 | 4 | 5; 2 | -------------------------------------------------------------------------------- /src/structures/game/Dye.ts: -------------------------------------------------------------------------------- 1 | import { ProfileService } from "../../lib/database/services/game/ProfileService"; 2 | 3 | export interface Dye { 4 | readonly id: number; 5 | readonly discord_id: string; 6 | readonly name: string; 7 | readonly dye_r: number; 8 | readonly dye_g: number; 9 | readonly dye_b: number; 10 | readonly charges: number; 11 | } 12 | 13 | export class GameDye { 14 | readonly id: number; 15 | readonly discordId: string; 16 | readonly name: string; 17 | readonly dyeR: number; 18 | readonly dyeG: number; 19 | readonly dyeB: number; 20 | readonly charges: number; 21 | constructor(data: Dye) { 22 | this.id = data.id; 23 | this.discordId = data.discord_id; 24 | this.name = data.name; 25 | this.dyeR = data.dye_r; 26 | this.dyeG = data.dye_g; 27 | this.dyeB = data.dye_b; 28 | this.charges = data.charges; 29 | } 30 | 31 | public async fetch(): Promise { 32 | return await ProfileService.getDyeById(this.id); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/structures/game/Frame.ts: -------------------------------------------------------------------------------- 1 | import { Image } from "canvas"; 2 | 3 | export interface Frame { 4 | readonly id: number; 5 | readonly frame_name: string | null; 6 | readonly frame_url: string | null; 7 | readonly dye_mask_url: string | null; 8 | readonly overlay: 0 | 1; 9 | readonly text_color_hex: string | null; 10 | } 11 | 12 | export interface IntermediateFrame { 13 | readonly id: number; 14 | readonly name: string; 15 | readonly frame: Image; 16 | readonly frameUrl: string; 17 | readonly mask: Buffer; 18 | readonly maskUrl: string; 19 | readonly overlay: 0 | 1; 20 | readonly textColor: string; 21 | } 22 | 23 | export class GameFrame { 24 | readonly id: number; 25 | readonly name: string; 26 | readonly frame: Image; 27 | readonly frameUrl: string; 28 | readonly mask: Buffer; 29 | readonly maskUrl: string; 30 | readonly overlay: boolean; 31 | readonly textColor: string; 32 | 33 | constructor(data: IntermediateFrame) { 34 | this.id = data.id; 35 | this.name = data.name; 36 | this.frame = data.frame; 37 | this.frameUrl = data.frameUrl; 38 | this.mask = data.mask; 39 | this.maskUrl = data.maskUrl; 40 | this.overlay = data.overlay === 1 ? true : false; 41 | this.textColor = data.textColor; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/structures/game/Idol.ts: -------------------------------------------------------------------------------- 1 | interface Idol { 2 | readonly id: number; 3 | readonly idol_name: string; 4 | readonly birthday: string | null | undefined; 5 | } 6 | 7 | export class GameIdol { 8 | readonly id: number; 9 | readonly name: string; 10 | readonly birthday: string | undefined; 11 | constructor(idol: Idol) { 12 | this.id = idol.id; 13 | this.name = idol.idol_name; 14 | this.birthday = idol.birthday || undefined; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/structures/game/Item.ts: -------------------------------------------------------------------------------- 1 | export interface Item { 2 | readonly id: number; 3 | readonly discord_id: string; 4 | readonly item_id: number; 5 | readonly quantity: number; 6 | } 7 | 8 | export class GameItem { 9 | readonly id: number; 10 | readonly discordId: string; 11 | readonly itemId: number; 12 | readonly quantity: number; 13 | constructor(data: Item) { 14 | this.id = data.id; 15 | this.discordId = data.discord_id; 16 | this.itemId = data.item_id; 17 | this.quantity = data.quantity; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/structures/game/Recipe.ts: -------------------------------------------------------------------------------- 1 | type RecipeItem = { 2 | readonly itemId: number; 3 | readonly count: number; 4 | }; 5 | 6 | export type Recipe = { 7 | readonly name: string; 8 | readonly query: string; 9 | readonly ingredients: RecipeItem[]; 10 | readonly result: RecipeItem[]; 11 | }; 12 | -------------------------------------------------------------------------------- /src/structures/game/Sticker.ts: -------------------------------------------------------------------------------- 1 | import { Image } from "canvas"; 2 | 3 | export interface Sticker { 4 | readonly id: number; 5 | readonly name: string; 6 | readonly image_url: string; 7 | readonly item_id: number; 8 | readonly pack_id: number; 9 | readonly rarity: number; 10 | } 11 | 12 | export interface CardSticker { 13 | readonly id: number; 14 | readonly card_id: number; 15 | readonly sticker_id: number; 16 | readonly position: number; 17 | } 18 | 19 | export interface IntermediateSticker { 20 | readonly id: number; 21 | readonly name: string; 22 | readonly image: Image; 23 | readonly itemId: number; 24 | readonly packId: number; 25 | readonly rarity: number; 26 | } 27 | 28 | export class GameSticker { 29 | readonly id: number; 30 | readonly name: string; 31 | readonly image: Image; 32 | readonly itemId: number; 33 | readonly packId: number; 34 | readonly rarity: number; 35 | 36 | constructor(data: IntermediateSticker) { 37 | this.id = data.id; 38 | this.name = data.name; 39 | this.image = data.image; 40 | this.itemId = data.itemId; 41 | this.packId = data.packId; 42 | this.rarity = data.rarity; 43 | } 44 | } 45 | 46 | export class GameCardSticker { 47 | readonly id: number; 48 | readonly cardId: number; 49 | readonly stickerId: number; 50 | readonly position: number; 51 | 52 | constructor(data: CardSticker) { 53 | this.id = data.id; 54 | this.cardId = data.card_id; 55 | this.stickerId = data.sticker_id; 56 | this.position = data.position; 57 | } 58 | } 59 | 60 | export interface BuiltSticker { 61 | readonly id: number; 62 | readonly name: string; 63 | readonly image: Image; 64 | readonly itemId: number; 65 | } 66 | -------------------------------------------------------------------------------- /src/structures/game/Tag.ts: -------------------------------------------------------------------------------- 1 | import { ProfileService } from "../../lib/database/services/game/ProfileService"; 2 | 3 | export interface Tag { 4 | readonly id: number; 5 | readonly discord_id: string; 6 | readonly tag_name: string; 7 | readonly emoji: string; 8 | } 9 | 10 | export class GameTag { 11 | readonly id: number; 12 | readonly discordId: string; 13 | readonly name: string; 14 | readonly emoji: string; 15 | constructor(data: Tag) { 16 | this.id = data.id; 17 | this.discordId = data.discord_id; 18 | this.name = data.tag_name; 19 | this.emoji = data.emoji; 20 | } 21 | 22 | public async fetch(): Promise { 23 | return await ProfileService.getTagById(this.id); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/structures/game/Wishlist.ts: -------------------------------------------------------------------------------- 1 | export interface Wishlist { 2 | readonly id: number; 3 | readonly discord_id: string; 4 | readonly idol_id: number; 5 | } 6 | 7 | export class GameWishlist { 8 | readonly id: number; 9 | readonly discordId: string; 10 | readonly idolId: number; 11 | constructor(data: Wishlist) { 12 | this.id = data.id; 13 | this.discordId = data.discord_id; 14 | this.idolId = data.idol_id; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/structures/game/blacklist/Blacklist.ts: -------------------------------------------------------------------------------- 1 | import { BlacklistService } from "../../../lib/database/services/meta/BlacklistService"; 2 | 3 | export interface Blacklist { 4 | readonly id: number; 5 | readonly discord_id: string; 6 | readonly reason: string; 7 | readonly moderator_id: string; 8 | readonly active: boolean; 9 | readonly created_at: string; 10 | readonly quashed_by: string; 11 | readonly quash_note: string; 12 | } 13 | 14 | export class GameBlacklist { 15 | readonly id: number; 16 | readonly discordId: string; 17 | readonly reason: string; 18 | readonly moderator: string; 19 | readonly active: boolean; 20 | readonly createdAt: string; 21 | readonly quasher: string; 22 | readonly quashNote: string; 23 | 24 | constructor(blacklist: Blacklist) { 25 | this.id = blacklist.id; 26 | this.discordId = blacklist.discord_id; 27 | this.reason = blacklist.reason; 28 | this.moderator = blacklist.moderator_id; 29 | this.active = blacklist.active; 30 | this.createdAt = blacklist.created_at; 31 | this.quasher = blacklist.quashed_by; 32 | this.quashNote = blacklist.quash_note; 33 | } 34 | 35 | public async fetch(): Promise { 36 | return await BlacklistService.getBlacklistById(this.id); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/structures/game/club/database/DBClub.ts: -------------------------------------------------------------------------------- 1 | import { getClubById } from "../../../../lib/database/sql/game/club/ClubGetter"; 2 | 3 | export interface DBClub { 4 | id: number; 5 | club_name: string; 6 | blurb: string | null; 7 | owner_id: string; 8 | member_limit: number; 9 | vanity_code: string | null; 10 | club_open: boolean; 11 | } 12 | 13 | export class GameDBClub { 14 | id: number; 15 | name: string; 16 | blurb: string | undefined; 17 | ownerId: string; 18 | memberLimit: number; 19 | vanityCode: string | undefined; 20 | open: boolean; 21 | 22 | constructor(club: DBClub) { 23 | this.id = club.id; 24 | this.name = club.club_name; 25 | this.blurb = club.blurb || undefined; 26 | this.ownerId = club.owner_id; 27 | this.memberLimit = club.member_limit; 28 | this.vanityCode = club.vanity_code || undefined; 29 | this.open = club.club_open; 30 | } 31 | 32 | public async fetch(): Promise { 33 | return await getClubById(this.id); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/structures/game/club/database/DBClubMember.ts: -------------------------------------------------------------------------------- 1 | import { getClubMemberById } from "../../../../lib/database/sql/game/club/ClubGetter"; 2 | 3 | export interface DBClubMember { 4 | id: number; 5 | discord_id: string; 6 | club_id: number; 7 | is_moderator: boolean; 8 | } 9 | 10 | export class GameDBClubMember { 11 | id: number; 12 | discordId: string; 13 | clubId: number; 14 | isMod: boolean; 15 | 16 | constructor(clubMember: DBClubMember) { 17 | this.id = clubMember.id; 18 | this.discordId = clubMember.discord_id; 19 | this.clubId = clubMember.club_id; 20 | this.isMod = clubMember.is_moderator; 21 | } 22 | 23 | public async fetch() { 24 | return await getClubMemberById(this.id); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/structures/game/quest/BaseQuest.ts: -------------------------------------------------------------------------------- 1 | import { QuestObjective } from "./QuestObjective"; 2 | import { QuestReward, RewardCount } from "./QuestReward"; 3 | 4 | export interface BaseQuest { 5 | id: number; 6 | type: QuestType; 7 | description: string; 8 | objective: QuestObjective; 9 | completion: number[]; 10 | xpReward: RewardCount; 11 | rewards: QuestReward[]; 12 | } 13 | 14 | export type QuestType = `daily` | `weekly`; 15 | -------------------------------------------------------------------------------- /src/structures/game/quest/QuestObjective.ts: -------------------------------------------------------------------------------- 1 | export enum QuestObjective { 2 | BURN_CARD, 3 | CLAIM_CARD, 4 | DROP, 5 | VOTE, 6 | TRADE, 7 | // CLAIM_GROUP, 8 | GIFT, 9 | // BURN_CONDITION, 10 | UPGRADE_TO_MINT, 11 | DYE, 12 | } 13 | -------------------------------------------------------------------------------- /src/structures/game/quest/QuestProgression.ts: -------------------------------------------------------------------------------- 1 | import { GameDBQuest } from "./database/DBQuest"; 2 | 3 | export type QuestProgression = GameDBQuest & { increment: number }; 4 | -------------------------------------------------------------------------------- /src/structures/game/quest/QuestReward.ts: -------------------------------------------------------------------------------- 1 | import { PrefabItem } from "../../item/PrefabItem"; 2 | 3 | export type RewardCount = { min: number; max: number }; 4 | 5 | export type XPReward = { xp: RewardCount }; 6 | export type ItemReward = { item: PrefabItem; count: RewardCount }; 7 | 8 | export type BaseQuestReward = XPReward | ItemReward; 9 | export type RewardChance = { chance: number }; 10 | 11 | export type QuestReward = BaseQuestReward & RewardChance; 12 | -------------------------------------------------------------------------------- /src/structures/game/quest/database/DBQuest.ts: -------------------------------------------------------------------------------- 1 | import { QuestGetter } from "../../../../lib/database/sql/game/quest/QuestGetter"; 2 | import { Quests } from "../../../../lib/quest/Quest"; 3 | import { BaseQuest, QuestType } from "../BaseQuest"; 4 | 5 | export interface DBQuest { 6 | id: number; 7 | discord_id: string; 8 | created_at: string; 9 | 10 | quest_id: number; 11 | quest_type: QuestType; 12 | progress: number; 13 | completion: number; 14 | } 15 | 16 | export class GameDBQuest { 17 | id: number; 18 | discordId: string; 19 | createdAt: string; 20 | 21 | quest: BaseQuest | undefined; // ?? weird typing 22 | progress: number; 23 | completion: number; 24 | 25 | constructor(quest: DBQuest) { 26 | this.id = quest.id; 27 | this.discordId = quest.discord_id; 28 | this.createdAt = quest.created_at; 29 | 30 | this.quest = Quests.getQuestById(quest.quest_id); 31 | this.progress = quest.progress; 32 | this.completion = quest.completion; 33 | } 34 | 35 | public async fetch(): Promise { 36 | return await QuestGetter.getDBQuestById(this.id); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/structures/item/PrefabItem.ts: -------------------------------------------------------------------------------- 1 | import { Message } from "eris"; 2 | import { GameProfile } from "../game/Profile"; 3 | 4 | export interface PrefabItem { 5 | id: number; 6 | names: string[]; 7 | useCost?: number; 8 | requiredArguments?: number; 9 | confirmation?: boolean; 10 | description?: string; 11 | usage?: string; 12 | emoji: string; 13 | soulbound?: boolean; 14 | use?: ( 15 | msg: Message, 16 | profile: GameProfile, 17 | parameters: string[] 18 | ) => Promise; 19 | } 20 | -------------------------------------------------------------------------------- /src/structures/meta/Patron.ts: -------------------------------------------------------------------------------- 1 | export interface Patron { 2 | discord_id: string; 3 | next_frame_time: string | null; 4 | } 5 | 6 | export class GamePatron { 7 | discordId: string; 8 | nextFrameTime: string | null; 9 | constructor(data: Patron) { 10 | this.discordId = data.discord_id; 11 | this.nextFrameTime = data.next_frame_time; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/structures/orpheus/OrpheusBitTransaction.ts: -------------------------------------------------------------------------------- 1 | export interface OrpheusBitTransaction { 2 | id: number; 3 | giver: string; 4 | recipient: string; 5 | amount: number; 6 | guild_id: string; 7 | transaction_time: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/structures/orpheus/OrpheusClaim.ts: -------------------------------------------------------------------------------- 1 | export interface OrpheusClaim { 2 | id: number; 3 | claimer: string; 4 | dropper: string; 5 | card_id: number; 6 | guild_id: string; 7 | claim_time: string; 8 | drop_time: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/structures/orpheus/OrpheusCommand.ts: -------------------------------------------------------------------------------- 1 | export interface OrpheusCommand { 2 | id: number; 3 | command_id: string; 4 | discord_id: string; 5 | parameters: string; 6 | guild_id: string | null; 7 | channel_id: string; 8 | message_id: string; 9 | use_time: string; 10 | error: boolean; 11 | } 12 | -------------------------------------------------------------------------------- /src/structures/orpheus/OrpheusGift.ts: -------------------------------------------------------------------------------- 1 | export interface OrpheusGift { 2 | id: number; 3 | giver: string; 4 | recipient: string; 5 | card_id: number; 6 | guild_id: string; 7 | gift_time: string; 8 | } 9 | -------------------------------------------------------------------------------- /src/structures/orpheus/OrpheusMultitrade.ts: -------------------------------------------------------------------------------- 1 | export interface OrpheusMultitrade { 2 | id: number; 3 | trade_uuid: string; 4 | sender: string; 5 | receiver: string; 6 | item_type: string; 7 | item_value: string; 8 | quantity: number; 9 | trade_time: string; 10 | guild_id: string | null; 11 | } 12 | -------------------------------------------------------------------------------- /src/structures/orpheus/OrpheusTrade.ts: -------------------------------------------------------------------------------- 1 | export interface OrpheusTrade { 2 | id: number; 3 | sender: string; 4 | receiver: string; 5 | sender_card_id: number; 6 | receiver_card_id: number; 7 | guild_id: string; 8 | trade_time: string; 9 | } 10 | -------------------------------------------------------------------------------- /src/structures/poll/Poll.ts: -------------------------------------------------------------------------------- 1 | import { PollService } from "../../lib/database/services/meta/PollService"; 2 | 3 | export interface Poll { 4 | id: number; 5 | active: boolean; 6 | title: string; 7 | body: string; 8 | created_at: string; 9 | ends_at: string | null; 10 | } 11 | 12 | export interface Answer { 13 | id: number; 14 | poll_id: number; 15 | discord_id: string; 16 | answer: `yes` | `no`; 17 | created_at: string; 18 | } 19 | 20 | export class GamePoll { 21 | id: number; 22 | active: boolean; 23 | title: string; 24 | body: string; 25 | 26 | createdAt: string; 27 | endsAt: string | undefined; 28 | constructor(poll: Poll) { 29 | this.id = poll.id; 30 | this.active = poll.active; 31 | this.title = poll.title; 32 | this.body = poll.body; 33 | 34 | this.createdAt = poll.created_at; 35 | this.endsAt = poll.ends_at || undefined; 36 | } 37 | 38 | public async fetch(): Promise { 39 | return await PollService.getPollById(this.id); 40 | } 41 | } 42 | 43 | export class GameAnswer { 44 | id: number; 45 | pollId: number; 46 | discordId: string; 47 | answer: `yes` | `no`; 48 | createdAt: string; 49 | constructor(answer: Answer) { 50 | this.id = answer.id; 51 | this.pollId = answer.poll_id; 52 | this.discordId = answer.discord_id; 53 | this.answer = answer.answer; 54 | this.createdAt = answer.created_at; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/structures/shop/Shop.ts: -------------------------------------------------------------------------------- 1 | import { PrefabItem } from "../item/PrefabItem"; 2 | 3 | export interface DBShop { 4 | id: number; 5 | item_id: number; 6 | price: number; 7 | currency: `bits` | `cubits`; 8 | featured: boolean; 9 | } 10 | 11 | export interface IntermediateShop { 12 | id: number; 13 | item: PrefabItem; 14 | price: number; 15 | currency: `bits` | `cubits`; 16 | featured: boolean; 17 | } 18 | 19 | export class GameShop { 20 | id: number; 21 | item: PrefabItem; 22 | price: number; 23 | currency: `bits` | `cubits`; 24 | featured: boolean; 25 | 26 | constructor(data: IntermediateShop) { 27 | this.id = data.id; 28 | this.item = data.item; 29 | this.price = data.price; 30 | this.currency = data.currency; 31 | this.featured = data.featured; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/structures/shop/StickerPack.ts: -------------------------------------------------------------------------------- 1 | import { GameSticker } from "../game/Sticker"; 2 | 3 | export interface StickerPack { 4 | readonly id: number; 5 | readonly pack_name: string; 6 | readonly price: number; 7 | readonly currency: `bits` | `cubits`; 8 | readonly item_id: number; 9 | readonly featured: boolean; 10 | readonly shoppable: boolean; 11 | readonly selection: boolean; 12 | readonly pulls: number; 13 | } 14 | 15 | export class GameStickerPack { 16 | readonly id: number; 17 | readonly name: string; 18 | readonly price: number; 19 | readonly currency: `bits` | `cubits`; 20 | readonly itemId: number; 21 | readonly featured: boolean; 22 | readonly shoppable: boolean; 23 | readonly selection: boolean; 24 | readonly pulls: number; 25 | 26 | readonly stickers: GameSticker[]; 27 | 28 | constructor(data: StickerPack, stickers: GameSticker[]) { 29 | this.id = data.id; 30 | this.name = data.pack_name; 31 | this.price = 32 | data.price - (data.featured ? Math.floor(data.price * 0.25) : 0); 33 | this.currency = data.currency; 34 | this.featured = data.featured; 35 | this.shoppable = data.shoppable; 36 | this.itemId = data.item_id; 37 | this.selection = data.selection; 38 | this.pulls = data.pulls; 39 | 40 | this.stickers = stickers; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/webhook/index.ts: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import { Logger } from "../lib/logger/Logger"; 3 | import { Zephyr } from "../structures/client/Zephyr"; 4 | 5 | export class WebhookListener { 6 | public async init() { 7 | const app = express(); 8 | const port = Zephyr.config.topgg.webhook.port; 9 | const auth = Zephyr.config.topgg.webhook.auth; 10 | 11 | app.use(express.json()); 12 | app.listen(port, () => 13 | Logger.info(`Top.gg listener is running on port ${port}.`) 14 | ); 15 | 16 | app.post(`/vote`, async (req, res) => { 17 | if (req.headers.authorization !== auth) { 18 | Logger.error( 19 | "Authorization header did not match." + 20 | `\n- ${req.headers.authorization}` 21 | ); 22 | return; 23 | } 24 | 25 | const body = req.body as { 26 | bot: string; 27 | user: string; 28 | type: "upvote" | "test"; 29 | query: string; 30 | isWeekend: boolean; 31 | }; 32 | 33 | if (body.type === "test") { 34 | Logger.info("Test request received, authorization OK."); 35 | // return; 36 | } 37 | 38 | if (!body.user) return; 39 | 40 | res.status(200).end(); 41 | 42 | await Zephyr.handleVote(body.user, body.isWeekend); 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /types/eris-collector/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "eris-collector"; 2 | -------------------------------------------------------------------------------- /types/eris/index.d.ts: -------------------------------------------------------------------------------- 1 | import Eris from "eris"; 2 | declare module "eris" { 3 | interface User { 4 | tag: string; 5 | } 6 | interface Message { 7 | textChannel: TextChannel; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /types/global.d.ts: -------------------------------------------------------------------------------- 1 | import { GameDye } from "../src/structures/game/Dye"; 2 | import { GameUserCard } from "../src/structures/game/UserCard"; 3 | import { PrefabItem } from "../src/structures/item/PrefabItem"; 4 | 5 | declare type InteractableBits = { bits: number }; 6 | declare type InteractableCubits = { cubits: number }; 7 | declare type InteractableItem = { item: PrefabItem; count: number }; 8 | declare type TradeItemResolvable = 9 | | GameUserCard 10 | | GameDye 11 | | InteractableBits 12 | | InteractableCubits 13 | | InteractableItem; 14 | 15 | declare global { 16 | interface TradeItemResolvable {} 17 | interface InteractableBits { 18 | bits: number; 19 | } 20 | interface InteractableCubits { 21 | cubits: number; 22 | } 23 | interface InteractableItem { 24 | item: PrefabItem; 25 | count: number; 26 | } 27 | type AutotagKey = `idol` | `group` | `wear` | `issue`; 28 | } 29 | -------------------------------------------------------------------------------- /types/nearest-color/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "nearest-color"; 2 | --------------------------------------------------------------------------------