Select the types of activity you want to include in your feed.
[READ-ONLY] Mirror of https://github.com/maybeanerd/MagiBot. MagiBot - the community building discord bot that adds joinsounds to your voice channels!
discord.gg/2Evcf4T
···11+---
22+name: Bug report
33+about: Create a report to help us improve
44+title: ''
55+labels: ''
66+assignees: ''
77+88+---
99+1010+**Describe the bug**
1111+A clear and concise description of what the bug is.
1212+1313+**To Reproduce**
1414+Steps to reproduce the behavior:
1515+1. Go to '...'
1616+2. Click on '....'
1717+3. Scroll down to '....'
1818+4. See error
1919+2020+**Expected behavior**
2121+A clear and concise description of what you expected to happen.
2222+2323+**Screenshots**
2424+If applicable, add screenshots to help explain your problem.
2525+2626+**Desktop (please complete the following information):**
2727+ - OS: [e.g. iOS]
2828+ - Browser [e.g. chrome, safari]
2929+ - Version [e.g. 22]
3030+3131+**Smartphone (please complete the following information):**
3232+ - Device: [e.g. iPhone6]
3333+ - OS: [e.g. iOS8.1]
3434+ - Browser [e.g. stock browser, safari]
3535+ - Version [e.g. 22]
3636+3737+**Additional context**
3838+Add any other context about the problem here.
+20
.github/ISSUE_TEMPLATE/feature_request.md
···11+---
22+name: Feature request
33+about: Create an issue for a feature
44+title: ''
55+labels: ''
66+assignees: ''
77+88+---
99+1010+**Is your feature request related to a problem? Please describe.**
1111+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
1212+1313+**Describe the solution you'd like**
1414+A clear and concise description of what you want to happen.
1515+1616+**Describe alternatives you've considered**
1717+A clear and concise description of any alternative solutions or features you've considered.
1818+1919+**Additional context**
2020+Add any other context or screenshots about the feature request here.
+25
.github/workflows/nodejs.yml
···11+name: Node CI
22+33+on: [push]
44+55+jobs:
66+ build:
77+88+ runs-on: ubuntu-latest
99+1010+ strategy:
1111+ matrix:
1212+ node-version: [10.x]
1313+1414+ steps:
1515+ - uses: actions/checkout@v1
1616+ - name: Use Node.js ${{ matrix.node-version }}
1717+ uses: actions/setup-node@v1
1818+ with:
1919+ node-version: ${{ matrix.node-version }}
2020+ - name: npm install, build, and test
2121+ run: |
2222+ npm install
2323+ npm run lint --if-present
2424+ npm run build --if-present
2525+ npm run test --if-present
+291
.gitignore
···11+## Ignore Visual Studio temporary files, build results, and
22+## files generated by popular Visual Studio add-ons.
33+##
44+## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
55+66+# User-specific files
77+*.suo
88+*.user
99+*.userosscache
1010+*.sln.docstates
1111+1212+# User-specific files (MonoDevelop/Xamarin Studio)
1313+*.userprefs
1414+1515+# Build results
1616+[Dd]ebug/
1717+[Dd]ebugPublic/
1818+[Rr]elease/
1919+[Rr]eleases/
2020+x64/
2121+x86/
2222+bld/
2323+[Bb]in/
2424+[Oo]bj/
2525+[Ll]og/
2626+2727+# Visual Studio 2015 cache/options directory
2828+.vs/
2929+# Uncomment if you have tasks that create the project's static files in wwwroot
3030+#wwwroot/
3131+3232+# MSTest test Results
3333+[Tt]est[Rr]esult*/
3434+[Bb]uild[Ll]og.*
3535+3636+# NUNIT
3737+*.VisualState.xml
3838+TestResult.xml
3939+4040+# Build Results of an ATL Project
4141+[Dd]ebugPS/
4242+[Rr]eleasePS/
4343+dlldata.c
4444+4545+# .NET Core
4646+project.lock.json
4747+project.fragment.lock.json
4848+artifacts/
4949+**/Properties/launchSettings.json
5050+5151+*_i.c
5252+*_p.c
5353+*_i.h
5454+*.ilk
5555+*.meta
5656+*.obj
5757+*.pch
5858+*.pdb
5959+*.pgc
6060+*.pgd
6161+*.rsp
6262+*.sbr
6363+*.tlb
6464+*.tli
6565+*.tlh
6666+*.tmp
6767+*.tmp_proj
6868+*.log
6969+*.vspscc
7070+*.vssscc
7171+.builds
7272+*.pidb
7373+*.svclog
7474+*.scc
7575+7676+# Chutzpah Test files
7777+_Chutzpah*
7878+7979+# Visual C++ cache files
8080+ipch/
8181+*.aps
8282+*.ncb
8383+*.opendb
8484+*.opensdf
8585+*.sdf
8686+*.cachefile
8787+*.VC.db
8888+*.VC.VC.opendb
8989+9090+# Visual Studio profiler
9191+*.psess
9292+*.vsp
9393+*.vspx
9494+*.sap
9595+9696+# TFS 2012 Local Workspace
9797+$tf/
9898+9999+# Guidance Automation Toolkit
100100+*.gpState
101101+102102+# ReSharper is a .NET coding add-in
103103+_ReSharper*/
104104+*.[Rr]e[Ss]harper
105105+*.DotSettings.user
106106+107107+# JustCode is a .NET coding add-in
108108+.JustCode
109109+110110+# TeamCity is a build add-in
111111+_TeamCity*
112112+113113+# DotCover is a Code Coverage Tool
114114+*.dotCover
115115+116116+# Visual Studio code coverage results
117117+*.coverage
118118+*.coveragexml
119119+120120+# NCrunch
121121+_NCrunch_*
122122+.*crunch*.local.xml
123123+nCrunchTemp_*
124124+125125+# MightyMoose
126126+*.mm.*
127127+AutoTest.Net/
128128+129129+# Web workbench (sass)
130130+.sass-cache/
131131+132132+# Installshield output folder
133133+[Ee]xpress/
134134+135135+# DocProject is a documentation generator add-in
136136+DocProject/buildhelp/
137137+DocProject/Help/*.HxT
138138+DocProject/Help/*.HxC
139139+DocProject/Help/*.hhc
140140+DocProject/Help/*.hhk
141141+DocProject/Help/*.hhp
142142+DocProject/Help/Html2
143143+DocProject/Help/html
144144+145145+# Click-Once directory
146146+publish/
147147+148148+# Publish Web Output
149149+*.[Pp]ublish.xml
150150+*.azurePubxml
151151+# TODO: Comment the next line if you want to checkin your web deploy settings
152152+# but database connection strings (with potential passwords) will be unencrypted
153153+*.pubxml
154154+*.publishproj
155155+156156+# Microsoft Azure Web App publish settings. Comment the next line if you want to
157157+# checkin your Azure Web App publish settings, but sensitive information contained
158158+# in these scripts will be unencrypted
159159+PublishScripts/
160160+161161+# NuGet Packages
162162+*.nupkg
163163+# The packages folder can be ignored because of Package Restore
164164+**/packages/*
165165+# except build/, which is used as an MSBuild target.
166166+!**/packages/build/
167167+# Uncomment if necessary however generally it will be regenerated when needed
168168+#!**/packages/repositories.config
169169+# NuGet v3's project.json files produces more ignorable files
170170+*.nuget.props
171171+*.nuget.targets
172172+173173+# Microsoft Azure Build Output
174174+csx/
175175+*.build.csdef
176176+177177+# Microsoft Azure Emulator
178178+ecf/
179179+rcf/
180180+181181+# Windows Store app package directories and files
182182+AppPackages/
183183+BundleArtifacts/
184184+Package.StoreAssociation.xml
185185+_pkginfo.txt
186186+187187+# Visual Studio cache files
188188+# files ending in .cache can be ignored
189189+*.[Cc]ache
190190+# but keep track of directories ending in .cache
191191+!*.[Cc]ache/
192192+193193+# Others
194194+ClientBin/
195195+~$*
196196+*~
197197+*.dbmdl
198198+*.dbproj.schemaview
199199+*.jfm
200200+*.pfx
201201+*.publishsettings
202202+orleans.codegen.cs
203203+204204+# Since there are multiple workflows, uncomment next line to ignore bower_components
205205+# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
206206+#bower_components/
207207+208208+# RIA/Silverlight projects
209209+Generated_Code/
210210+211211+# Backup & report files from converting an old project file
212212+# to a newer Visual Studio version. Backup files are not needed,
213213+# because we have git ;-)
214214+_UpgradeReport_Files/
215215+Backup*/
216216+UpgradeLog*.XML
217217+UpgradeLog*.htm
218218+219219+# SQL Server files
220220+*.mdf
221221+*.ldf
222222+*.ndf
223223+224224+# Business Intelligence projects
225225+*.rdl.data
226226+*.bim.layout
227227+*.bim_*.settings
228228+229229+# Microsoft Fakes
230230+FakesAssemblies/
231231+232232+# GhostDoc plugin setting file
233233+*.GhostDoc.xml
234234+235235+# Node.js Tools for Visual Studio
236236+.ntvs_analysis.dat
237237+node_modules/
238238+239239+# Typescript v1 declaration files
240240+typings/
241241+242242+# Visual Studio 6 build log
243243+*.plg
244244+245245+# Visual Studio 6 workspace options file
246246+*.opt
247247+248248+# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
249249+*.vbw
250250+251251+# Visual Studio LightSwitch build output
252252+**/*.HTMLClient/GeneratedArtifacts
253253+**/*.DesktopClient/GeneratedArtifacts
254254+**/*.DesktopClient/ModelManifest.xml
255255+**/*.Server/GeneratedArtifacts
256256+**/*.Server/ModelManifest.xml
257257+_Pvt_Extensions
258258+259259+# Paket dependency manager
260260+.paket/paket.exe
261261+paket-files/
262262+263263+# FAKE - F# Make
264264+.fake/
265265+266266+# JetBrains Rider
267267+.idea/
268268+*.sln.iml
269269+270270+# CodeRush
271271+.cr/
272272+273273+# Python Tools for Visual Studio (PTVS)
274274+__pycache__/
275275+*.pyc
276276+277277+# Cake - Uncomment if you are using it
278278+# tools/**
279279+# !tools/packages.config
280280+281281+# Telerik's JustMock configuration file
282282+*.jmconfig
283283+284284+# BizTalk build output
285285+*.btp.cs
286286+*.btm.cs
287287+*.odx.cs
288288+*.xsd.cs
289289+startForLinAMI.txt
290290+startForWin.txt
291291+# src/token.ts
···11+// const avroles = { Switch: 'Switch' };
22+module.exports = {
33+ main(bot, msg) {
44+ /* var role=(msg.content.split(" "))[0];
55+ if(avroles[role]){
66+ msg.member.addRole(avroles[role]);
77+ msg.reply("dir wurde erfolgreich die Rolle "+role+" zugewiesen.");
88+ }
99+ else{
1010+ if(role!==''){
1111+ msg.reply("die Rolle "+role+" existiert nicht.");
1212+ }else{ */
1313+ msg.channel.send("Getting other roles is as simple as this: Type t!selfroles get role in <#198764451132997632>\nDont @mention the role, you'll only spam notifications!\nExample usage: t!selfroles get Switch\nAvailable roles:\nSwitch, League of Legends, Dauntless, Town of Salem, Fortnite, Civilization, PUBG\n☝ if <@172002275412279296> is offline this won't work❗\nthe roles are mainly to find people to play with in <#365179633857200138>");
1414+ // }
1515+ // }
1616+ },
1717+ admin: false,
1818+};
+25
src/commands/@dblist.ts
···11+import { bot } from '../bot';
22+import data from '../db';
33+import { asyncForEach } from '../bamands';
44+import { commandCategories } from '../types/enums';
55+66+export const dblist: magibotCommand = {
77+ name: 'dblist',
88+ async main(content, msg) {
99+ const users = await data.getDBLSubs();
1010+ let str = `There are ${users.length} users currently subscribed to the DBL vote reminder:\n\n`;
1111+ asyncForEach(users, async (u, i) => {
1212+ // eslint-disable-next-line no-underscore-dangle
1313+ str += `${i}. ${(await bot.users.fetch(u._id)).username} - voted: ${u.voted}\n`;
1414+ });
1515+ msg.channel.send(str);
1616+ },
1717+ ehelp() {
1818+ return [{ name: '', value: 'get all users that are subscribed to the dbl reminder.' }];
1919+ },
2020+ perm: 'SEND_MESSAGES',
2121+ dev: true,
2222+ admin: true,
2323+ hide: true,
2424+ category: commandCategories.misc,
2525+};
···11+import { TextChannel, VoiceChannel, User } from 'discord.js';
22+import { bot } from '../bot';
33+import data from '../db';
44+import { yesOrNo } from '../bamands';
55+import { user, queueVoiceChannels } from '../shared_assets';
66+import { commandCategories } from '../types/enums';
77+88+const used: { [k: string]: { date: Date; msg: string; cid: string } } = {};
99+1010+function messageEdit(
1111+ voiceChannel: VoiceChannel | null | undefined,
1212+ activeUser: User | undefined,
1313+ qU: Array<User>,
1414+ topic: string,
1515+) {
1616+ let msg = `Queue: **${topic}**`;
1717+ if (voiceChannel) {
1818+ msg += `\n*with voicemode activated in* ${voiceChannel}`;
1919+ }
2020+ let tmpms = '\n';
2121+ if (qU.length > 0) {
2222+ for (let i = 0; i < 10 && i < qU.length; i++) {
2323+ tmpms += `- ${qU[i]}\n`;
2424+ }
2525+ } else {
2626+ tmpms = ' no more queued users\n';
2727+ }
2828+ msg += `\n*${qU.length} queued users left*\n\nCurrent user: **${activeUser}**\n\nNext up are:${tmpms}\nUse ☑ to join and ❌ to leave the queue!`;
2929+ return msg;
3030+}
3131+3232+export const queue: magibotCommand = {
3333+ hide: false,
3434+ name: 'queue',
3535+ async main(content, msg) {
3636+ if (!msg.guild) {
3737+ return;
3838+ }
3939+ let voiceChannel: VoiceChannel | null | undefined;
4040+ if (used[msg.guild.id]) {
4141+ const d = new Date();
4242+ if (d.getTime() - used[msg.guild.id].date.getTime() <= 0) {
4343+ // check if its already 2hours old
4444+ if (used[msg.guild.id].msg && used[msg.guild.id].cid) {
4545+ const testchann = msg.guild.channels.cache.get(
4646+ used[msg.guild.id].cid,
4747+ );
4848+ if (
4949+ testchann
5050+ && (await (testchann as TextChannel).messages
5151+ .fetch(used[msg.guild.id].msg)
5252+ .catch(() => {}))
5353+ ) {
5454+ msg.channel.send(
5555+ "There's already an ongoing queue on this guild. For performance reasons only one queue per guild is allowed.",
5656+ );
5757+ return;
5858+ }
5959+ } else {
6060+ msg.channel.send(
6161+ "There's currently a queue being created on this guild. For performance reasons only one queue per guild is allowed.",
6262+ );
6363+ return;
6464+ }
6565+ }
6666+ }
6767+ used[msg.guild.id] = {
6868+ date: new Date(Date.now() + 3600000),
6969+ msg: '',
7070+ cid: '',
7171+ };
7272+ const authorID = msg.author.id;
7373+ msg.channel.send('What do you want the queue to be about?').then((mess) => {
7474+ msg.delete();
7575+ msg.channel
7676+ .awaitMessages((m) => m.author.id === authorID, {
7777+ max: 1,
7878+ time: 60000,
7979+ })
8080+ .then((collected) => {
8181+ if (!collected.first()) {
8282+ msg.channel.send('Cancelled queue creation due to timeout.');
8383+ delete used[msg.guild!.id];
8484+ return;
8585+ }
8686+ const topic = collected.first()!.content;
8787+ collected.first()!.delete();
8888+ mess.delete();
8989+ msg.channel
9090+ .send(
9191+ 'How long is this queue supposed to last? *(in minutes, maximum of 120)*',
9292+ )
9393+ .then((mess2) => {
9494+ msg.channel
9595+ .awaitMessages((m) => m.author.id === authorID, {
9696+ max: 1,
9797+ time: 60000,
9898+ })
9999+ .then(async (collected2) => {
100100+ if (!collected2.first()) {
101101+ msg.channel.send(
102102+ 'Cancelled queue creation due to timeout.',
103103+ );
104104+ delete used[msg.guild!.id];
105105+ return;
106106+ }
107107+ let time = parseInt(collected2.first()!.content, 10);
108108+ if (!time) {
109109+ msg.channel.send(
110110+ 'Thats not a real number duh. Cancelled queue creation, try again.',
111111+ );
112112+ delete used[msg.guild!.id];
113113+ return;
114114+ }
115115+ if (time > 120) {
116116+ time = 120;
117117+ } else if (time < 1) {
118118+ time = 1;
119119+ }
120120+ collected2.first()!.delete();
121121+ mess2.delete();
122122+ const authorMember = await msg.guild?.members.fetch(
123123+ msg.author
124124+ );
125125+ voiceChannel = authorMember?.voice.channel;
126126+ let remMessage;
127127+ if (voiceChannel) {
128128+ const botMember = await msg.guild?.members.fetch(user());
129129+ if (!botMember?.hasPermission('MUTE_MEMBERS')) {
130130+ remMessage = await msg.channel.send(
131131+ 'If i had MUTE_MEMBERS permission i would be able to (un)mute users in the voice channel automatically. If you want to use that feature restart the command after giving me the additional permissions.',
132132+ );
133133+ voiceChannel = null;
134134+ } else if (
135135+ await yesOrNo(
136136+ msg,
137137+ `Do you want to automatically (un)mute users based on their turn in ${voiceChannel}? `,
138138+ )
139139+ ) {
140140+ remMessage = await msg.channel.send(
141141+ `Automatically (un)muting users in ${voiceChannel}. This means everyone except users that are considered admin by MagiBot is muted by default.`,
142142+ );
143143+ } else {
144144+ remMessage = await msg.channel.send(
145145+ `Deactivated automatic (un)muting in ${voiceChannel}.`,
146146+ );
147147+ voiceChannel = null;
148148+ }
149149+ } else {
150150+ remMessage = await msg.channel.send(
151151+ 'If you were in a voice channel while setting this up i could automatically (un)mute users. Restart the whole process to do so, if you wish to.',
152152+ );
153153+ }
154154+ msg.channel
155155+ .send(
156156+ `Do you want to start the queue **${topic}** lasting **${time} minutes** ?`,
157157+ )
158158+ .then(async (mess3) => {
159159+ const filter = (reaction, usr) => (reaction.emoji.name === '☑'
160160+ || reaction.emoji.name === '❌')
161161+ && usr.id === authorID;
162162+ await mess3.react('☑');
163163+ await mess3.react('❌');
164164+ mess3
165165+ .awaitReactions(filter, {
166166+ max: 1,
167167+ time: 20000,
168168+ })
169169+ .then(async (reacts) => {
170170+ await mess3.delete();
171171+ await remMessage.delete();
172172+ if (
173173+ reacts.first()
174174+ && reacts.first()!.emoji.name === '☑'
175175+ ) {
176176+ msg.channel
177177+ .send(
178178+ `Queue: **${topic}:**\n\nUse ☑ to join the queue!`,
179179+ )
180180+ .then(async (mess4) => {
181181+ const chann = await bot.channels.fetch(
182182+ '433357857937948672',
183183+ );
184184+ await mess4.react('➡');
185185+ await mess4.react('☑');
186186+ await mess4.react('❌');
187187+ await mess4.react('🔚');
188188+ const fil = (reaction, usr) => reaction.emoji.name === '☑'
189189+ || reaction.emoji.name === '❌'
190190+ || ((reaction.emoji.name === '➡'
191191+ || reaction.emoji.name === '🔚')
192192+ && usr.id === authorID);
193193+ time *= 60000;
194194+ const queuedUsers: Array<User> = [];
195195+ let activeUser: User | undefined;
196196+ const collector = mess4.createReactionCollector(
197197+ fil,
198198+ {
199199+ time,
200200+ },
201201+ );
202202+ const deleteme = await (chann as TextChannel).send(
203203+ `Started queue **${topic}** on server **${mess4.guild}**`,
204204+ );
205205+ used[msg.guild!.id] = {
206206+ date: new Date(Date.now() + time),
207207+ cid: mess4.channel.id,
208208+ msg: mess4.id,
209209+ };
210210+211211+ if (voiceChannel) {
212212+ // add the vc to the global variable so joins get muted
213213+ queueVoiceChannels[msg.guild!.id] = voiceChannel.id;
214214+ // servermute all users in voiceChannel
215215+ const memArray = voiceChannel.members.array();
216216+ memArray.forEach(async (val) => {
217217+ const mem = voiceChannel!.members.get(
218218+ val.id,
219219+ );
220220+ if (
221221+ mem
222222+ && !(await data.isAdmin(msg.guild!.id, mem))
223223+ ) {
224224+ mem.voice.setMute(
225225+ true,
226226+ 'Queue started in this voice channel',
227227+ );
228228+ }
229229+ });
230230+ }
231231+232232+ collector.on(
233233+ 'collect',
234234+ async (r, userThatReacted) => {
235235+ switch (r.emoji.name) {
236236+ case '☑':
237237+ if (
238238+ !queuedUsers.includes(
239239+ userThatReacted,
240240+ )
241241+ && userThatReacted !== activeUser
242242+ && userThatReacted.id !== user().id
243243+ ) {
244244+ if (activeUser) {
245245+ queuedUsers.push(userThatReacted);
246246+ const reactn = mess4.reactions.cache.get(
247247+ '❌',
248248+ );
249249+ if (reactn) {
250250+ reactn.users.remove(
251251+ userThatReacted,
252252+ );
253253+ }
254254+ } else {
255255+ activeUser = userThatReacted;
256256+ r.users.remove(activeUser);
257257+ msg.channel
258258+ .send(
259259+ `It's your turn ${activeUser}!`,
260260+ )
261261+ .then((ms) => {
262262+ ms.delete({ timeout: 1000 });
263263+ });
264264+ if (voiceChannel) {
265265+ // unmute currentUser
266266+ const currentMember = await msg.guild?.members.fetch(
267267+ activeUser
268268+ );
269269+ if (currentMember) {
270270+ currentMember.voice.setMute(
271271+ false,
272272+ 'Its their turn in the queue',
273273+ );
274274+ }
275275+ }
276276+ }
277277+ mess4.edit(
278278+ messageEdit(
279279+ voiceChannel,
280280+ activeUser,
281281+ queuedUsers,
282282+ topic,
283283+ ),
284284+ );
285285+ }
286286+ break;
287287+ case '➡':
288288+ if (queuedUsers[0]) {
289289+ if (voiceChannel) {
290290+ // mute old current user
291291+ if (activeUser) {
292292+ const currentMember = await msg.guild!.members.fetch(
293293+ activeUser,
294294+ );
295295+ currentMember.voice.setMute(
296296+ true,
297297+ 'its not your turn in the queue anymore',
298298+ );
299299+ }
300300+ }
301301+ activeUser = queuedUsers.shift()!;
302302+ r.users.remove(userThatReacted);
303303+ mess4.edit(
304304+ messageEdit(
305305+ voiceChannel,
306306+ activeUser,
307307+ queuedUsers,
308308+ topic,
309309+ ),
310310+ );
311311+ const reactn = mess4.reactions.cache.get(
312312+ '☑',
313313+ );
314314+ if (reactn) {
315315+ reactn.users.remove(activeUser);
316316+ }
317317+ msg.channel
318318+ .send(
319319+ `It's your turn ${activeUser}!`,
320320+ )
321321+ .then((ms) => {
322322+ ms.delete({ timeout: 1000 });
323323+ });
324324+ if (voiceChannel) {
325325+ // unmute currentUser
326326+ const currentMember = await msg.guild?.members.fetch(
327327+ activeUser
328328+ );
329329+ if (currentMember) {
330330+ currentMember.voice.setMute(
331331+ false,
332332+ 'Its their turn in the queue',
333333+ );
334334+ }
335335+ }
336336+ } else {
337337+ msg.channel
338338+ .send('No users left in queue.')
339339+ .then((ms) => {
340340+ ms.delete({ timeout: 2000 });
341341+ });
342342+ }
343343+ r.users.remove(authorID);
344344+ break;
345345+ case '❌':
346346+ if (
347347+ queuedUsers.includes(
348348+ userThatReacted,
349349+ )
350350+ && userThatReacted.id !== user().id
351351+ ) {
352352+ r.users.remove(userThatReacted);
353353+ const rctn = mess4.reactions.cache.get(
354354+ '☑',
355355+ );
356356+ if (rctn) {
357357+ rctn.users.remove(userThatReacted);
358358+ }
359359+ const ind = queuedUsers.findIndex(
360360+ (obj) => obj.id === userThatReacted.id,
361361+ );
362362+ queuedUsers.splice(ind, 1);
363363+ mess4.edit(
364364+ messageEdit(
365365+ voiceChannel,
366366+ activeUser,
367367+ queuedUsers,
368368+ topic,
369369+ ),
370370+ );
371371+ }
372372+ break;
373373+ case '🔚':
374374+ msg.channel
375375+ .send('Successfully ended queue.')
376376+ .then((ms) => {
377377+ ms.delete({ timeout: 5000 });
378378+ });
379379+ collector.stop();
380380+ break;
381381+ default:
382382+ break;
383383+ }
384384+ },
385385+ );
386386+ collector.on('end', () => {
387387+ deleteme.delete();
388388+ delete used[msg.guild!.id];
389389+ if (voiceChannel) {
390390+ delete queueVoiceChannels[msg.guild!.id];
391391+ // remove all mutes
392392+ voiceChannel.members
393393+ .array()
394394+ .forEach((mem) => {
395395+ mem.voice.setMute(false, 'queue ended');
396396+ });
397397+ }
398398+ mess4
399399+ .edit(`**${topic}** ended.`)
400400+ .catch(() => {});
401401+ mess4.reactions.removeAll().catch(() => {});
402402+ });
403403+ });
404404+ } else if (reacts.first()) {
405405+ msg.channel.send(
406406+ `successfully canceled queue **${topic}**`,
407407+ );
408408+ delete used[msg.guild!.id];
409409+ } else {
410410+ msg.channel.send(
411411+ `Cancelled queue creation of **${topic}** due to timeout.`,
412412+ );
413413+ delete used[msg.guild!.id];
414414+ }
415415+ });
416416+ });
417417+ });
418418+ });
419419+ });
420420+ });
421421+ },
422422+ ehelp() {
423423+ return [
424424+ {
425425+ name: '',
426426+ value:
427427+ 'Start a queue that can last up to 2h. There is only a single queue allowed per guild.\nYou can activate an optional voicemode which will automatically (un)mute users if you start the queue while connected to a voicechannel.\nYou get all the setup instructions when using the command.',
428428+ },
429429+ ];
430430+ },
431431+ admin: true,
432432+ perm: ['SEND_MESSAGES', 'MANAGE_MESSAGES'],
433433+ dev: false,
434434+ category: commandCategories.util,
435435+};
+128
src/commands/@salt.ts
···11+import { GuildMember } from 'discord.js';
22+import { commandCategories } from '../types/enums';
33+import { PREFIXES } from '../shared_assets';
44+import data from '../db';
55+import { findMember, yesOrNo } from '../bamands';
66+77+function printHelp() {
88+ const info: Array<{ name: string; value: string }> = [];
99+1010+ info.push({
1111+ name: 'add <@user|userid|nickname>',
1212+ value: 'Report a user for being salty',
1313+ });
1414+1515+ info.push({
1616+ name: 'rem <@user|userid|nickname>',
1717+ value: 'Remove the oldest salt report of a user',
1818+ });
1919+2020+ info.push({
2121+ name: 'clr <@user|userid|nickname>',
2222+ value: 'Clear all salt of a user',
2323+ });
2424+2525+ info.push({
2626+ name: 'reset',
2727+ value: 'Reset all salt of this guild. Use with caution',
2828+ });
2929+3030+ return info;
3131+}
3232+3333+export const salt: magibotCommand = {
3434+ dev: false,
3535+ name: 'salt',
3636+ main: async function main(bot, msg) {
3737+ const args = msg.content.split(/ +/);
3838+ const command = args[0].toLowerCase();
3939+ if (msg.guild) {
4040+ const mention = args[1];
4141+ const uid = await findMember(msg.guild, mention);
4242+ if (!(mention && uid)) {
4343+ if (command === 'reset') {
4444+ if (
4545+ await yesOrNo(
4646+ msg,
4747+ 'Do you really want to reset all salt on this server?',
4848+ 'Successfully canceled salt reset.',
4949+ )
5050+ ) {
5151+ data.resetSalt(msg.guild);
5252+ msg.channel.send(
5353+ `Successfully reset all salt on **${msg.guild.name}**!`,
5454+ );
5555+ }
5656+ return;
5757+ }
5858+ msg.reply('you need to mention a user you want to use this on!');
5959+ return;
6060+ }
6161+ let mem: GuildMember;
6262+ switch (command) {
6363+ case 'add':
6464+ mem = await msg.guild.members.fetch(uid);
6565+ if (!mem) {
6666+ msg.reply("the user with this ID doesn't exist on this guild.");
6767+ return;
6868+ }
6969+ if (mem.user.bot) {
7070+ msg.reply("you can't report bots!");
7171+ return;
7272+ }
7373+ await data.saltUpAdmin(uid, msg.author.id, msg.guild);
7474+ msg.channel.send(
7575+ `Successfully reported ${mem} for being a salty bitch!`,
7676+ );
7777+ break;
7878+ case 'rem':
7979+ mem = await msg.guild.members.fetch(uid);
8080+ if (!mem) {
8181+ msg.reply("the user with this ID doesn't exist on this guild.");
8282+ return;
8383+ }
8484+ if (mem.user.bot) {
8585+ msg.reply('bots are never salty!');
8686+ return;
8787+ }
8888+ if (await data.remOldestSalt(uid, msg.guild)) {
8989+ msg.channel.send(
9090+ `Successfully removed the oldest salt from ${mem}!`,
9191+ );
9292+ } else {
9393+ msg.channel.send(`${mem} has no salt that could be removed!`);
9494+ }
9595+ break;
9696+ case 'clr':
9797+ mem = await msg.guild.members.fetch(uid);
9898+ if (!mem) {
9999+ msg.reply("the user with this ID doesn't exist on this guild.");
100100+ return;
101101+ }
102102+ if (mem.user.bot) {
103103+ msg.reply('bots are never salty!');
104104+ return;
105105+ }
106106+ await data.clrSalt(uid, msg.guild);
107107+ msg.channel.send(`Successfully cleared all salt from ${mem}!`);
108108+ break;
109109+ default:
110110+ msg.reply(
111111+ `this command doesn't exist. Use \`${
112112+ PREFIXES[msg.guild.id]
113113+ }:help salt\` to get more info.`,
114114+ );
115115+ break;
116116+ }
117117+ } else {
118118+ msg.reply('Commands are only available on guilds.');
119119+ }
120120+ },
121121+ ehelp() {
122122+ return printHelp();
123123+ },
124124+ perm: 'SEND_MESSAGES',
125125+ admin: true,
126126+ hide: false,
127127+ category: commandCategories.util,
128128+};
+407
src/commands/@setup.ts
···11+import { MessageEmbedOptions } from 'discord.js';
22+import { COLOR, PREFIXES } from '../shared_assets';
33+44+import data from '../db';
55+import { findMember, yesOrNo, findRole } from '../bamands';
66+import { commandCategories } from '../types/enums';
77+88+function printHelp() {
99+ const info: Array<{ name: string; value: string }> = [];
1010+1111+ info.push({
1212+ name: 'ban <@User>',
1313+ value: 'Deactivate all functions of the bot for a user',
1414+ });
1515+ info.push({
1616+ name: 'unban <@User>',
1717+ value: 'Reactivate all functions of the bot for a user',
1818+ });
1919+ info.push({
2020+ name: 'join',
2121+ value: "(De)activate joinsounds for the voicechannel you're connected to",
2222+ });
2323+ info.push({
2424+ name: 'admin <@Role>',
2525+ value: '(Un)set a role to be considered admin by the bot',
2626+ });
2727+ info.push({
2828+ name: 'command',
2929+ value:
3030+ "(De)activate bot commands for the text channel you're sending this in",
3131+ });
3232+ info.push({
3333+ name: 'notification',
3434+ value: '(Un)set a textchannel to be notification channel',
3535+ });
3636+ info.push({
3737+ name: 'info',
3838+ value: 'Displays current settings',
3939+ });
4040+ info.push({
4141+ name: 'prefix <prefix>',
4242+ value: 'Set a custom character or string as prefix',
4343+ });
4444+4545+ return info;
4646+}
4747+4848+export const setup: magibotCommand = {
4949+ dev: false,
5050+ name: 'setup',
5151+ main: async function main(content, msg) {
5252+ const args = msg.content.split(/ +/);
5353+ const command = args[0].toLowerCase();
5454+5555+ const mention = args[1];
5656+5757+ // Create variables to use in cases
5858+ let uid;
5959+ let de;
6060+ switch (command) {
6161+ case 'ban':
6262+ uid = await findMember(msg.guild!, mention);
6363+ if (mention && uid) {
6464+ if (
6565+ await yesOrNo(
6666+ msg,
6767+ `Do you really want to ban <@!${uid}> from using the bot?`,
6868+ 'Successfully canceled ban.',
6969+ )
7070+ ) {
7171+ data.setBlacklistedUser(msg.guild!.id, uid, true);
7272+ msg.channel.send(
7373+ `Successfully banned <@!${uid}> from using the bot.`,
7474+ );
7575+ }
7676+ } else {
7777+ msg.reply('you need to mention a user you want to use this on!');
7878+ }
7979+ break;
8080+ case 'unban':
8181+ uid = await findMember(msg.guild!, mention);
8282+ if (mention && uid) {
8383+ if (
8484+ await yesOrNo(
8585+ msg,
8686+ `Do you really want to reactivate bot usage for <@!${uid}>?`,
8787+ 'Successfully canceled unban.',
8888+ )
8989+ ) {
9090+ data.setBlacklistedUser(msg.guild!.id, uid, false);
9191+ msg.channel.send(
9292+ `Successfully banned <@!${uid}> from using the bot.`,
9393+ );
9494+ }
9595+ } else {
9696+ msg.reply('you need to mention a user you want to use this on!');
9797+ }
9898+ break;
9999+ case 'join':
100100+ // eslint-disable-next-line no-case-declarations
101101+ const voiceChannel = msg.member?.voice.channel;
102102+103103+ if (voiceChannel) {
104104+ const isJoinable = await data.isJoinable(
105105+ msg.guild!.id,
106106+ voiceChannel.id,
107107+ );
108108+ de = '';
109109+ if (isJoinable) {
110110+ de = 'de';
111111+ }
112112+ if (
113113+ await yesOrNo(
114114+ msg,
115115+ `Do you want to ${de}activate joinsounds in **${voiceChannel.name}**?`,
116116+ `Cancelled ${de}activating joinsounds in **${voiceChannel.name}**.`,
117117+ )
118118+ ) {
119119+ await data.setJoinable(msg.guild!.id, voiceChannel.id, !isJoinable);
120120+ msg.channel.send(
121121+ `Successfully ${de}activated joinsounds in **${voiceChannel.name}**.`,
122122+ );
123123+ }
124124+ } else {
125125+ msg.channel.send("You're connected to no voice channel!");
126126+ }
127127+ break;
128128+ case 'admin':
129129+ // eslint-disable-next-line no-case-declarations
130130+ const rid = await findRole(msg.guild!, mention);
131131+ if (mention && rid) {
132132+ if (!(await data.isAdminRole(msg.guild!.id, rid))) {
133133+ if (
134134+ await yesOrNo(
135135+ msg,
136136+ `Do you want to set <@&${rid}> as admin role?`,
137137+ `Cancelled setting <@&${rid}> as admin role`,
138138+ )
139139+ ) {
140140+ await data.setAdmin(msg.guild!.id, rid, true);
141141+ msg.channel.send(`Successfully set <@&${rid}> as admin role!`);
142142+ }
143143+ } else if (
144144+ await yesOrNo(
145145+ msg,
146146+ `Do you want to remove <@&${rid}> from the admin roles?`,
147147+ `Cancelled removing <@&${rid}> from the admin roles`,
148148+ )
149149+ ) {
150150+ await data.setAdmin(msg.guild!.id, rid, false);
151151+ msg.channel.send(
152152+ `Successfully removed <@&${rid}> from the admin roles!`,
153153+ );
154154+ }
155155+ } else {
156156+ msg.channel.send('You need to mention a role!');
157157+ }
158158+ break;
159159+ case 'command':
160160+ // eslint-disable-next-line no-case-declarations
161161+ const isCommandChannel = await data.isCommandChannel(
162162+ msg.guild!.id,
163163+ msg.channel.id,
164164+ );
165165+ de = '';
166166+ if (isCommandChannel) {
167167+ de = 'de';
168168+ }
169169+ if (
170170+ await yesOrNo(
171171+ msg,
172172+ `Do you want to ${de}activate commands in <#${msg.channel.id}>?`,
173173+ `Cancelled ${de}activating commands in <#${msg.channel.id}>`,
174174+ )
175175+ ) {
176176+ await data.setCommandChannel(
177177+ msg.guild!.id,
178178+ msg.channel.id,
179179+ !isCommandChannel,
180180+ );
181181+ msg.channel.send(
182182+ `Successfully ${de}activated commands in <#${msg.channel.id}>.`,
183183+ );
184184+ }
185185+ break;
186186+ case 'notification':
187187+ // eslint-disable-next-line no-case-declarations
188188+ const isNotChann = await data.isNotChannel(
189189+ msg.guild!.id,
190190+ msg.channel.id,
191191+ );
192192+ if (!isNotChann) {
193193+ if (
194194+ await yesOrNo(
195195+ msg,
196196+ `Do you want to activate MagiBot notifications in <#${msg.channel.id}>?`,
197197+ `Cancelled activating notifications in <#${msg.channel.id}>`,
198198+ )
199199+ ) {
200200+ await data.setNotification(msg.guild!.id, msg.channel.id);
201201+ msg.channel
202202+ .send(
203203+ `Successfully activated notifications in <#${msg.channel.id}>.`,
204204+ )
205205+ .then((mess) => {
206206+ mess.delete({ timeout: 5000 }).catch(() => {});
207207+ msg.delete();
208208+ });
209209+ }
210210+ } else if (
211211+ await yesOrNo(
212212+ msg,
213213+ `Do you want to deactivate MagiBot notifications in <#${msg.channel.id}>?`,
214214+ `Cancelled deactivating notifications in <#${msg.channel.id}>`,
215215+ )
216216+ ) {
217217+ await data.setNotification(msg.guild!.id, false);
218218+ msg.channel.send('Successfully deactivated notifications.');
219219+ }
220220+ break;
221221+ case 'prefix':
222222+ if (mention) {
223223+ if (
224224+ await yesOrNo(
225225+ msg,
226226+ `Do you want to change the prefix in ${msg.guild!.name} from \`${
227227+ PREFIXES[msg.guild!.id]
228228+ }.\` to \`${mention}.\` ?`,
229229+ 'Cancelled changing the prefix.',
230230+ )
231231+ ) {
232232+ const newpref = await data.setPrefixE(msg.guild!.id, mention);
233233+ if (newpref) {
234234+ msg.channel.send(
235235+ `Successfully changed prefix to \`${newpref}.\` !`,
236236+ );
237237+ } else {
238238+ msg.channel.send('Something bad happened...');
239239+ }
240240+ }
241241+ } else {
242242+ msg.reply('you need to provide a prefix you want to use.');
243243+ }
244244+ break;
245245+ case 'info':
246246+ /* eslint-disable no-case-declarations */
247247+ const info: Array<{
248248+ name: string;
249249+ value: string;
250250+ inline: boolean;
251251+ }> = [];
252252+ const set = await data.getSettings(msg.guild!.id);
253253+254254+ info.push({
255255+ name: 'Prefix',
256256+ value: `${await data.getPrefixE(msg.guild!.id)}.`,
257257+ inline: false,
258258+ });
259259+260260+ let str = '';
261261+ let cmd = set.commandChannels;
262262+ /* eslint-enable no-case-declarations */
263263+264264+ if (!cmd.toString()) {
265265+ str = 'no whitelist, so every channel is allowed';
266266+ } else {
267267+ cmd.forEach((com) => {
268268+ str += `<#${com}> `;
269269+ });
270270+ }
271271+ info.push({
272272+ name: 'Command channels',
273273+ value: str,
274274+ inline: false,
275275+ });
276276+277277+ str = '';
278278+ cmd = set.adminRoles;
279279+ if (!cmd.toString()) {
280280+ str = 'Empty';
281281+ } else {
282282+ cmd.forEach((com) => {
283283+ str += `<@&${com}> `;
284284+ });
285285+ }
286286+ info.push({
287287+ name: 'Admin roles',
288288+ value: str,
289289+ inline: false,
290290+ });
291291+292292+ str = '';
293293+ cmd = set.joinChannels;
294294+ if (!cmd.toString()) {
295295+ str = 'Empty';
296296+ } else {
297297+ const { guild } = msg;
298298+ cmd.forEach((com) => {
299299+ const chann = guild!.channels.cache.get(com);
300300+ if (chann) {
301301+ str += `${chann.name}, `;
302302+ } else {
303303+ data.setJoinable(guild!.id, com, false);
304304+ }
305305+ });
306306+ str = str.substring(0, str.length - 2);
307307+ }
308308+ info.push({
309309+ name: 'Joinsound channels',
310310+ value: str,
311311+ inline: false,
312312+ });
313313+314314+ str = '';
315315+ cmd = set.blacklistedUsers;
316316+ if (!cmd.toString()) {
317317+ str = 'Empty';
318318+ } else {
319319+ cmd.forEach((com) => {
320320+ str += `<@!${com}>, `;
321321+ });
322322+ str = str.substring(0, str.length - 2);
323323+ }
324324+ info.push({
325325+ name: 'Blacklisted users',
326326+ value: str,
327327+ inline: false,
328328+ });
329329+330330+ str = '';
331331+ cmd = set.blacklistedEveryone;
332332+ if (!cmd.toString()) {
333333+ str = 'Empty';
334334+ } else {
335335+ cmd.forEach((com) => {
336336+ str += `<#${com}>, `;
337337+ });
338338+ str = str.substring(0, str.length - 2);
339339+ }
340340+ info.push({
341341+ name: 'Channel with @everyone blacklist',
342342+ value: str,
343343+ inline: false,
344344+ });
345345+346346+ if (!set.saltKing) {
347347+ str = 'None';
348348+ } else {
349349+ str = `<@${set.saltKing}>`;
350350+ }
351351+ info.push({
352352+ name: 'SaltKing',
353353+ value: str,
354354+ inline: false,
355355+ });
356356+ if (!set.saltRole) {
357357+ str = 'None';
358358+ } else {
359359+ str = `<@&${set.saltRole}>`;
360360+ }
361361+ info.push({
362362+ name: 'SaltKing role',
363363+ value: str,
364364+ inline: false,
365365+ });
366366+ if (!set.notChannel) {
367367+ str = 'None';
368368+ } else {
369369+ str = `<#${set.notChannel}>`;
370370+ }
371371+ info.push({
372372+ name: 'Notification channel',
373373+ value: str,
374374+ inline: false,
375375+ });
376376+ /* eslint-disable no-case-declarations */
377377+ /* eslint-disable camelcase */
378378+ const embed: MessageEmbedOptions = {
379379+ color: COLOR,
380380+ description: `Guild settings of ${msg.guild!.name}:`,
381381+ fields: info,
382382+ footer: {
383383+ iconURL: msg.guild!.iconURL() || '',
384384+ text: msg.guild!.name,
385385+ },
386386+ };
387387+ /* eslint-enable no-case-declarations */
388388+ /* eslint-enable camelcase */
389389+ msg.channel.send('', { embed });
390390+ break;
391391+ default:
392392+ msg.reply(
393393+ `this command doesn't exist. Use \`${
394394+ PREFIXES[msg.guild!.id]
395395+ }:help setup\` for more info.`,
396396+ );
397397+ break;
398398+ }
399399+ },
400400+ ehelp() {
401401+ return printHelp();
402402+ },
403403+ perm: 'SEND_MESSAGES',
404404+ admin: true,
405405+ hide: false,
406406+ category: commandCategories.util,
407407+};
+56
src/commands/@sound.ts
···11+import { PREFIXES } from '../shared_assets';
22+import data from '../db';
33+import { commandCategories } from '../types/enums';
44+import { findMember } from '../bamands';
55+66+function printHelp() {
77+ const info: Array<{ name: string; value: string }> = [];
88+99+ info.push({
1010+ name: 'rem <@User|userid|nickname>',
1111+ value:
1212+ 'Remove the joinsound of a user. If you use nickname it has to be at least three characters long',
1313+ });
1414+1515+ return info;
1616+}
1717+1818+export const sound: magibotCommand = {
1919+ name: 'sound',
2020+ dev: false,
2121+ main: async function main(content, msg) {
2222+ const args = msg.content.split(/ +/);
2323+ const command = args[0].toLowerCase();
2424+ const mention = args[1];
2525+ switch (command) {
2626+ case 'rem':
2727+ /* eslint-disable no-case-declarations */
2828+ const uid = await findMember(msg.guild!, mention);
2929+ /* eslint-enable no-case-declarations */
3030+ if (mention && uid) {
3131+ if (await data.addSound(uid, false, msg.guild!.id)) {
3232+ msg.reply(`you successfully removed <@!${uid}>s joinsound!`);
3333+ } else {
3434+ msg.reply('Aaaaaand you failed.');
3535+ }
3636+ } else {
3737+ msg.reply('you need to mention a user you want to use this on!');
3838+ }
3939+ break;
4040+ default:
4141+ msg.reply(
4242+ `this command doesn't exist. Use \`${
4343+ PREFIXES[msg.guild!.id]
4444+ }:help sound\` for more info.`,
4545+ );
4646+ break;
4747+ }
4848+ },
4949+ ehelp() {
5050+ return printHelp();
5151+ },
5252+ perm: 'SEND_MESSAGES',
5353+ admin: true,
5454+ hide: false,
5555+ category: commandCategories.util,
5656+};
···11+import { TextChannel } from 'discord.js';
22+import { bot } from '../bot';
33+import { PREFIXES } from '../shared_assets';
44+import { commandCategories } from '../types/enums';
55+66+export const bug: magibotCommand = {
77+ name: 'bug',
88+ async main(content, msg) {
99+ if (!(msg.content.length > 0)) {
1010+ msg.reply(
1111+ `you need to add info about the report after the command. Use \`${
1212+ PREFIXES[msg.guild!.id]
1313+ }.help bug\` to get more info.`,
1414+ );
1515+ return;
1616+ }
1717+ msg.channel
1818+ .send(`Do you want to send this bugreport?\n${msg.content}`)
1919+ .then((mess) => {
2020+ const filter = (reaction, user) => (reaction.emoji.name === '☑' || reaction.emoji.name === '❌')
2121+ && user.id === msg.author.id;
2222+ mess.react('☑');
2323+ mess.react('❌');
2424+ mess
2525+ .awaitReactions(filter, { max: 1, time: 20000 })
2626+ .then(async (reacts) => {
2727+ mess.delete();
2828+ if (reacts.first() && reacts.first()?.emoji.name === '☑') {
2929+ const chann = await bot.channels.fetch('444529194949672972');
3030+ (chann as TextChannel)
3131+ .send(
3232+ `**Bugreport** by ${msg.author.username} (<@${
3333+ msg.author.id
3434+ }>) on server ${msg.guild!.name}( ${msg.guild!.id} ) :\n${
3535+ msg.content
3636+ }`,
3737+ )
3838+ .then(() => {
3939+ msg.channel.send('Succesfully sent bugreport.');
4040+ });
4141+ } else if (reacts.first()) {
4242+ msg.channel.send('Successfully canceled bugreport.');
4343+ }
4444+ });
4545+ });
4646+ },
4747+ admin: false,
4848+ ehelp() {
4949+ return [
5050+ {
5151+ name:
5252+ '<bugreport with information about what you did, what was expected, and what went wrong>',
5353+ value: 'Report a bug concerning MagiBot',
5454+ },
5555+ ];
5656+ },
5757+ perm: 'SEND_MESSAGES',
5858+ hide: false,
5959+ dev: false,
6060+ category: commandCategories.misc,
6161+};
+220
src/commands/help.ts
···11+import { MessageEmbedOptions } from 'discord.js';
22+import { PREFIXES, COLOR, user } from '../shared_assets';
33+import { commandCategories } from '../types/enums';
44+// we allow this cycle once, as the help command also needs to list itself
55+import { commands } from '../commandHandler'; // eslint-disable-line import/no-cycle
66+import data from '../db';
77+88+export const help: magibotCommand = {
99+ name: 'help',
1010+ category: commandCategories.misc,
1111+ admin: false,
1212+ perm: 'SEND_MESSAGES',
1313+ main: async function main(content, msg) {
1414+ if (!msg.guild) {
1515+ return;
1616+ }
1717+ const args = content.split(/ +/);
1818+ let command = args[0].toLowerCase();
1919+ // extended help
2020+ if (command) {
2121+ const acommand = `@${command}`;
2222+ if (!(commands[command] || commands[acommand])) {
2323+ msg.reply(
2424+ `this command does not exist. Use \`${
2525+ PREFIXES[msg.guild.id]
2626+ }.help\` to get a full list of the commands available.`,
2727+ );
2828+ } else if (commands[command]) {
2929+ if (commands[command].ehelp) {
3030+ const info: Array<{
3131+ name: string;
3232+ value: string;
3333+ inline: boolean;
3434+ }> = [];
3535+ let ehelps = commands[command].ehelp(msg);
3636+ ehelps.forEach((ehelp) => {
3737+ if (msg.guild) {
3838+ info.push({
3939+ name: `${PREFIXES[msg.guild.id]}.${command} ${ehelp.name}`,
4040+ value: ehelp.value,
4141+ inline: false,
4242+ });
4343+ }
4444+ });
4545+ let embed: MessageEmbedOptions = {
4646+ color: COLOR,
4747+ description: `Commands available via the prefix \`${
4848+ PREFIXES[msg.guild.id]
4949+ }.${command}\`:`,
5050+ fields: info,
5151+ footer: {
5252+ iconURL: user().avatarURL() || '',
5353+ text:
5454+ '<required input> , [optional input] , choose|one|of|these , (comment on the command)',
5555+ },
5656+ };
5757+ msg.channel.send('', { embed });
5858+ // admin variant?
5959+ if (msg.member && (await data.isAdmin(msg.guild.id, msg.member))) {
6060+ if (commands[acommand] && commands[acommand].ehelp) {
6161+ const inf: Array<{
6262+ name: string;
6363+ value: string;
6464+ inline: boolean;
6565+ }> = [];
6666+ ehelps = commands[acommand].ehelp(msg);
6767+ ehelps.forEach((ehelp) => {
6868+ if (msg.guild) {
6969+ inf.push({
7070+ name: `${PREFIXES[msg.guild.id]}:${acommand.slice(1)} ${
7171+ ehelp.name
7272+ }`,
7373+ value: ehelp.value,
7474+ inline: false,
7575+ });
7676+ }
7777+ });
7878+ embed = {
7979+ color: COLOR,
8080+ description: `Admin commands available via the prefix \`${
8181+ PREFIXES[msg.guild.id]
8282+ }:${command}\`:`,
8383+ fields: inf,
8484+ footer: {
8585+ iconURL: user().avatarURL() || '',
8686+ text:
8787+ '<required input> , [optional input] , choose|one|of|these , (comment on the command)',
8888+ },
8989+ };
9090+ msg.channel.send('', { embed });
9191+ }
9292+ }
9393+ } else {
9494+ msg.reply('there is no extended help available for this command.');
9595+ }
9696+ } else if (msg.member && (await data.isAdmin(msg.guild.id, msg.member))) {
9797+ // Only Admin command
9898+ command = acommand;
9999+ if (commands[command].ehelp) {
100100+ const inf: Array<{
101101+ name: string;
102102+ value: string;
103103+ inline: boolean;
104104+ }> = [];
105105+ const ehelps = commands[command].ehelp(msg);
106106+ ehelps.forEach((ehelp) => {
107107+ if (msg.guild) {
108108+ inf.push({
109109+ name: `${PREFIXES[msg.guild.id]}:${command.slice(1)} ${
110110+ ehelp.name
111111+ }`,
112112+ value: ehelp.value,
113113+ inline: false,
114114+ });
115115+ }
116116+ });
117117+ const embed: MessageEmbedOptions = {
118118+ color: COLOR,
119119+ description: `Admin commands available via the prefix \`${
120120+ PREFIXES[msg.guild.id]
121121+ }:${command.slice(1)}\`:`,
122122+ fields: inf,
123123+ footer: {
124124+ iconURL: user().avatarURL() || '',
125125+ text:
126126+ '<required input> , [optional input] , choose|one|of|these , (comment on the command)',
127127+ },
128128+ };
129129+130130+ msg.channel.send('', { embed });
131131+ } else {
132132+ msg.reply('there is no extended help available for this command.');
133133+ }
134134+ }
135135+ } else {
136136+ // normal help, sort by categories
137137+ const cmds: Array<{ name: string; value: string; inline: boolean }> = [];
138138+ Object.values(commandCategories).forEach((cat) => {
139139+ let coms = '';
140140+ Object.values(commands).forEach((commnd) => {
141141+ if (commnd.category === cat && !(commnd.hide || commnd.admin)) {
142142+ let nm = commnd.name;
143143+ if (commnd.dev) {
144144+ nm += '(dev only)';
145145+ }
146146+ coms += `${nm}, `;
147147+ }
148148+ });
149149+ if (coms !== '') {
150150+ coms = coms.slice(0, -2);
151151+ cmds.push({
152152+ name: `${cat} commands`,
153153+ value: coms,
154154+ inline: false,
155155+ });
156156+ }
157157+ });
158158+ let embed: MessageEmbedOptions = {
159159+ color: COLOR,
160160+ description: `Commands available via the prefix \`${
161161+ PREFIXES[msg.guild.id]
162162+ }.\` :\nto get more info on a single command use \`${
163163+ PREFIXES[msg.guild.id]
164164+ }.help <command>\``,
165165+ fields: cmds,
166166+ footer: {
167167+ iconURL: user().avatarURL() || '',
168168+ text: `admins can override commands with ${
169169+ PREFIXES[msg.guild.id]
170170+ }: instead of ${
171171+ PREFIXES[msg.guild.id]
172172+ }. to ignore command channel restrictions`,
173173+ },
174174+ };
175175+ msg.channel.send('', { embed });
176176+ if (msg.member && (await data.isAdmin(msg.guild.id, msg.member))) {
177177+ const cmd: Array<{ name: string; value: string; inline: boolean }> = [];
178178+ let coms = '';
179179+ Object.values(commands).forEach((commnd) => {
180180+ if (commnd.admin && !commnd.hide) {
181181+ let nm = commnd.name;
182182+ if (commnd.dev) {
183183+ nm += '(dev only)';
184184+ }
185185+ coms += `${nm}, `;
186186+ }
187187+ });
188188+ if (coms !== '') {
189189+ coms = coms.slice(0, -2);
190190+ cmd.push({
191191+ name: 'Admin commands',
192192+ value: coms,
193193+ inline: false,
194194+ });
195195+ }
196196+ embed = {
197197+ color: COLOR,
198198+ description: `Admin commands available via the prefix \`${
199199+ PREFIXES[msg.guild.id]
200200+ }:\` :\nto get more info on a single command use \`${
201201+ PREFIXES[msg.guild.id]
202202+ }.help <command>\``,
203203+ fields: cmd,
204204+ footer: {
205205+ iconURL: user().avatarURL() || '',
206206+ text: `admins can override commands with ${
207207+ PREFIXES[msg.guild.id]
208208+ }: instead of ${
209209+ PREFIXES[msg.guild.id]
210210+ }. to ignore command channel restrictions`,
211211+ },
212212+ };
213213+ msg.channel.send('', { embed });
214214+ }
215215+ }
216216+ },
217217+ hide: false,
218218+ dev: false,
219219+ ehelp: () => [],
220220+};
+55
src/commands/info.ts
···11+import { MessageEmbedOptions } from 'discord.js';
22+import {
33+ PREFIXES, COLOR, user, SIGN,
44+} from '../shared_assets';
55+66+import { commandCategories } from '../types/enums';
77+88+export const inf: magibotCommand = {
99+ dev: false,
1010+ name: 'info',
1111+ main: (content, msg) => {
1212+ const info: Array<{
1313+ name: string;
1414+ value: string;
1515+ inline: boolean;
1616+ }> = [];
1717+1818+ info.push({
1919+ name: 'Links',
2020+ value: '[Invite me to your guild](https://discordapp.com/oauth2/authorize?client_id=384820232583249921&permissions=8&redirect_uri=https%3A%2F%2Fdiscord.gg%2F2Evcf4T&scope=bot)\n[Official support Discord](https://discord.gg/2Evcf4T)',
2121+ inline: false,
2222+ });
2323+2424+ info.push({
2525+ name: 'How to support MagiBot',
2626+ value: `Donate a buck via [Paypal](https://paypal.me/pools/c/8be5ok31vB)\nPledge on [MagiBots Patreon](https://www.patreon.com/MagiBot)\nVote on [discordbots.org](https://discordbots.org/bot/384820232583249921) and [bots.ondiscord.xyz](https://bots.ondiscord.xyz/bots/384820232583249921)!\n(daily vote reminder via *${PREFIXES[msg.guild!.id]}.dbl*)`,
2727+ inline: false,
2828+ });
2929+3030+ info.push({
3131+ name: 'A bit of background',
3232+ value: "MagiBot is being developed in Germany by T0TProduction#0001 as a hobby project.\nIt was originally a private bot for a Discord guild themed after the Pokemon Magikarp which is the reason it's called MagiBot.",
3333+ inline: false,
3434+ });
3535+3636+ const embed:MessageEmbedOptions = {
3737+ color: COLOR,
3838+ description: 'Some information about the bot:',
3939+ fields: info,
4040+ footer: {
4141+ iconURL: user().avatarURL() || '',
4242+ text: SIGN,
4343+ },
4444+ };
4545+4646+ msg.channel.send({ embed });
4747+ },
4848+ ehelp() {
4949+ return [{ name: '', value: 'Get some info about the bot as well as links to official MagiBot stuff.' }];
5050+ },
5151+ perm: 'SEND_MESSAGES',
5252+ admin: false,
5353+ hide: false,
5454+ category: commandCategories.misc,
5555+};
+22
src/commands/invite.ts
···11+import { TextChannel } from 'discord.js';
22+import { commandCategories } from '../types/enums';
33+44+export const inv: magibotCommand = {
55+ name: 'invite',
66+ hide: false,
77+ dev: false,
88+ async main(bot, msg) {
99+ // TODO check if invite activated on server
1010+ // TODO let user define invite length
1111+ const invite = await (msg.channel as TextChannel).createInvite(
1212+ { reason: `member ${msg.author} used invite command` },
1313+ );
1414+ msg.channel.send(`Here's an invite link to this channel: ${invite}`);
1515+ },
1616+ ehelp() {
1717+ return [{ name: '', value: 'Create and get an invite link to the guild.' }];
1818+ },
1919+ perm: ['SEND_MESSAGES', 'CREATE_INSTANT_INVITE'],
2020+ admin: false,
2121+ category: commandCategories.util,
2222+};
···11+import { COLOR, PREFIXES } from '../shared_assets';
22+import { commandCategories } from '../types/enums';
33+44+/** definition of calculation of dice, use parse(input)
55+ returns array of throws with last index being sum,
66+ second last being multiplier and third last being add
77+ */
88+function comp(s, mI, nI, fI, aI) {
99+ const m = parseInt(mI, 10) || 1;
1010+ const n = parseInt(nI, 10) || 1;
1111+ const f = parseInt(fI, 10);
1212+ if (!f) {
1313+ return false;
1414+ }
1515+ const a = typeof aI === 'string' ? parseInt(aI.replace(/\s/g, ''), 10) || 0 : 0;
1616+1717+ const ret: Array<number | boolean> = [];
1818+ let r = 0;
1919+ let overload = false;
2020+ for (let i = 0; i < n; i++) {
2121+ if (i > 23) {
2222+ overload = true;
2323+ break;
2424+ }
2525+ const tmp = Math.floor(Math.random() * f) + 1;
2626+ ret.push(tmp);
2727+ r += tmp;
2828+ }
2929+ ret.push(overload);
3030+ ret.push(m);
3131+ ret.push(n);
3232+ ret.push(f);
3333+ ret.push(a);
3434+ ret.push(r * m + a);
3535+3636+ return ret;
3737+}
3838+function parse(de:string) {
3939+ // eslint-disable-next-line prefer-spread
4040+ return comp.apply(
4141+ null,
4242+ de.match(/(?:(\d+)\s*\*\s*)?(\d*)d(\d+)(?:\s*([+-]\s*\d+))?/i) as any,
4343+ );
4444+}
4545+4646+export const roll: magibotCommand = {
4747+ name: 'roll',
4848+ hide: false,
4949+ dev: false,
5050+ main(bot, msg) {
5151+ const args = msg.content.split(/ +/);
5252+ const input = args[0];
5353+5454+ const throws = parse(input);
5555+ if (!throws) {
5656+ msg.channel.send(
5757+ `Your inputs could not be interpreted. Use \`${
5858+ PREFIXES[msg.guild!.id]
5959+ }.help roll\` for more info.`,
6060+ );
6161+ return;
6262+ }
6363+ const info: Array<{
6464+ name: string;
6565+ value: string;
6666+ inline: boolean;
6767+ }> = [];
6868+ const size = throws.length;
6969+ if (throws[size - 6]) {
7070+ msg.reply('the dungeon master can only roll 24 dice at a time!');
7171+ }
7272+ let thro = throws[size - 4];
7373+ if (thro > 24) {
7474+ thro = 24;
7575+ }
7676+ info.push({
7777+ name: `Sum of ${throws[size - 5]} * ${thro}d${throws[size - 3]} + ${
7878+ throws[size - 2]
7979+ }`,
8080+ value: String(throws[size - 1]),
8181+ inline: false,
8282+ });
8383+ for (let i = 0; i < size - 6; i++) {
8484+ info.push({
8585+ name: `${i + 1}. roll`,
8686+ value: String(throws[i]),
8787+ inline: true,
8888+ });
8989+ }
9090+ const embed = {
9191+ color: COLOR,
9292+ description: `:game_die: <@!${msg.author.id}>s dice have been rolled:`,
9393+ fields: info,
9494+ footer: {
9595+ /* eslint-disable camelcase */
9696+ icon_url:
9797+ 'https://cdn0.iconfinder.com/data/icons/video-game-items-concepts-line-art/128/dd-dice-512.png',
9898+ /* eslint-enable camelcase */
9999+ text: 'The real dungeon master',
100100+ },
101101+ };
102102+ msg.channel.send('', { embed });
103103+ },
104104+ ehelp() {
105105+ const ret: Array<{ name: string; value: string }> = [];
106106+ ret.push({
107107+ name: '[multiplier]*[number of rolls]d<die number>[+ <modifier>]',
108108+ value:
109109+ 'Roll dice with standard DnD syntax.\nExamples:\n`5*2d6+1`,`3d6 + 12`, `4*d12 + 3`, `d100`',
110110+ });
111111+ return ret;
112112+ },
113113+ perm: 'SEND_MESSAGES',
114114+ admin: false,
115115+ category: commandCategories.fun,
116116+};
+124
src/commands/salt.ts
···11+import { Message, MessageEmbedOptions } from 'discord.js';
22+import data from '../db';
33+import * as cmds from '../bamands';
44+import { commandCategories } from '../types/enums';
55+import { PREFIXES } from '../shared_assets';
66+77+function printHelp(msg: Message) {
88+ const info: Array<{ name: string; value: string }> = [];
99+ info.push({
1010+ name: 'add <@user|userid|nickname>',
1111+ value:
1212+ 'Report a user being salty. If you use nickname it has to be at least three characters long and unique.\nThis has a 1h cooldown for reporting the same user.',
1313+ });
1414+ info.push({
1515+ name: 'top',
1616+ value: `Displays the top 5 salter in ${msg.guild?.name}`,
1717+ });
1818+ return info;
1919+}
2020+2121+export const salt: magibotCommand = {
2222+ main: async function main(content: string, msg: Message) {
2323+ const args = content.split(/ +/);
2424+ const command = args[0].toLowerCase();
2525+ if (msg.guild) {
2626+ switch (command) {
2727+ case 'add':
2828+ /* eslint-disable no-case-declarations */
2929+ const mention = args[1];
3030+ const uid = await cmds.findMember(msg.guild, mention);
3131+ /* eslint-enable no-case-declarations */
3232+ if (mention && uid) {
3333+ if (uid === msg.author.id) {
3434+ msg.reply("you can't report yourself!");
3535+ return;
3636+ }
3737+ const mem = await msg.guild.members.fetch(uid).catch(() => {});
3838+ if (!mem) {
3939+ msg.reply("the user with this ID doesn't exist on this guild.");
4040+ return;
4141+ }
4242+ if (mem.user.bot) {
4343+ msg.reply("you can't report bots!");
4444+ return;
4545+ }
4646+ const time = await data.saltUp(uid, msg.author.id, msg.guild);
4747+ if (time === 0) {
4848+ msg.channel.send(
4949+ `Successfully reported ${mem} for being a salty bitch!`,
5050+ );
5151+ } else {
5252+ msg.reply(
5353+ `you can report ${mem} again in ${59
5454+ - Math.floor((time * 60) % 60)} min and ${60
5555+ - Math.floor((time * 60 * 60) % 60)} sec!`,
5656+ );
5757+ }
5858+ } else {
5959+ msg.reply('you need to mention a user you want to report!');
6060+ }
6161+ break;
6262+ case 'top':
6363+ /* eslint-disable no-case-declarations */
6464+ const salters = await data.topSalt(msg.guild.id);
6565+ const info: Array<{
6666+ name: string;
6767+ value: string;
6868+ inline: boolean;
6969+ }> = [];
7070+ /* eslint-enable no-case-declarations */
7171+ for (let i = 0; i < 5; i++) {
7272+ let mname = 'User left guild';
7373+ if (salters[i]) {
7474+ // eslint-disable-next-line no-await-in-loop
7575+ const member = await msg.guild.members
7676+ .fetch(salters[i].salter)
7777+ .catch(() => {});
7878+ if (member) {
7979+ mname = member.displayName;
8080+ }
8181+ info.push({
8282+ name: `${i + 1}. place: ${mname}`,
8383+ value: `${salters[i].salt} salt`,
8484+ inline: false,
8585+ });
8686+ } else {
8787+ break;
8888+ }
8989+ }
9090+ /* eslint-disable no-case-declarations */
9191+ const embed: MessageEmbedOptions = {
9292+ color: 0xffffff,
9393+ description: `Top 5 salter in ${msg.guild.name}:`,
9494+ fields: info,
9595+ footer: {
9696+ iconURL: msg.guild.iconURL() || '',
9797+ text: msg.guild.name,
9898+ },
9999+ };
100100+ /* eslint-enable no-case-declarations */
101101+ msg.channel.send('', { embed });
102102+ break;
103103+ default:
104104+ msg.reply(
105105+ `this command doesn't exist. Use \`${
106106+ PREFIXES[msg.guild.id]
107107+ }.help salt\` for more info.`,
108108+ );
109109+ break;
110110+ }
111111+ } else {
112112+ msg.reply('commands are only functional when used in a guild.');
113113+ }
114114+ },
115115+ name: 'salt',
116116+ ehelp(msg: Message) {
117117+ return printHelp(msg);
118118+ },
119119+ perm: 'SEND_MESSAGES',
120120+ admin: false,
121121+ hide: false,
122122+ category: commandCategories.fun,
123123+ dev: false,
124124+};
+82
src/commands/sound.ts
···11+import ffprobe from 'ffprobe';
22+import ffprobeStatic from 'ffprobe-static';
33+import { PREFIXES } from '../shared_assets';
44+import data from '../db';
55+import { commandCategories } from '../types/enums';
66+77+88+function printHelp() {
99+ const info:Array<{name:string, value:string}> = [];
1010+1111+ info.push({
1212+ name: '<Link to audio file>',
1313+ value: "Setup a joinsound for yourself. The link shouldn't link to a website, but directly to the file.\nOnly .mp3 and .wav are being supported at the moment.",
1414+ });
1515+1616+ info.push({
1717+ name: '(attach soundfile to this command)',
1818+ value: 'Setup a joinsound for yourself. Only .mp3 and .wav are being supported at the moment.\nRemember to attach the sound file to the message you use this command in.',
1919+ });
2020+2121+ info.push({
2222+ name: 'rem',
2323+ value: 'Remove your joinsound',
2424+ });
2525+2626+ return info;
2727+}
2828+2929+export const sound: magibotCommand = {
3030+ name: 'sound',
3131+ main: async function main(content, msg) {
3232+ if (!msg.guild) {
3333+ return;
3434+ }
3535+ const args = content.split(/ +/);
3636+ const command = args[0].toLowerCase();
3737+ if (command === 'rem') {
3838+ if (await data.addSound(msg.author.id, false, msg.guild.id)) {
3939+ msg.reply('you successfully removed your joinsound!');
4040+ } else {
4141+ msg.reply('Aaaaaand you failed.');
4242+ }
4343+ } else {
4444+ let mention = args[0];
4545+ const file = msg.attachments.array()[0];
4646+ if (mention || file) {
4747+ if (file) {
4848+ mention = file.url;
4949+ }
5050+5151+ let snd = await ffprobe(mention, { path: ffprobeStatic.path }).catch(() => { });
5252+ if (!snd) {
5353+ msg.reply(`you need to use a compatible link or upload the file with the command! For more info use \`${PREFIXES[msg.guild.id]}.help sound\``);
5454+ return;
5555+ }
5656+ // eslint-disable-next-line prefer-destructuring
5757+ snd = snd.streams[0];
5858+ if (snd.codec_name !== 'mp3' && snd.codec_name !== 'pcm_s16le' && snd.codec_name !== 'pcm_f32le') {
5959+ msg.reply(`you need to use a compatible file! For more info use \`${PREFIXES[msg.guild.id]}.help sound\``);
6060+ return;
6161+ }
6262+ if (snd.duration > 8) {
6363+ msg.reply("the joinsound you're trying to add is longer than 8 seconds.");
6464+ return;
6565+ }
6666+ if (await data.addSound(msg.author.id, mention, msg.guild.id)) {
6767+ msg.reply('you successfully changed your joinsound!');
6868+ } else {
6969+ msg.reply('Something went wrong...');
7070+ }
7171+ } else {
7272+ msg.reply(`this is not a valid command. If you tried adding a sound, remember to attach the file to the command. Use \`${PREFIXES[msg.guild.id]}.help sound\` for more info.`);
7373+ }
7474+ }
7575+ },
7676+ ehelp() { return printHelp(); },
7777+ admin: false,
7878+ perm: 'SEND_MESSAGES',
7979+ hide: false,
8080+ category: commandCategories.fun,
8181+ dev: false,
8282+};
+89
src/commands/stats.ts
···11+import { MessageEmbedOptions } from 'discord.js';
22+import si from 'systeminformation';
33+import { commandCategories } from '../types/enums';
44+import {
55+ OWNERID, COLOR, SIGN, user,
66+} from '../shared_assets';
77+// eslint-disable-next-line import/no-cycle
88+import { bot } from '../bot';
99+1010+export const stats: magibotCommand = {
1111+ name: 'stats',
1212+ main: async (content, msg) => {
1313+ const info:Array<{name:string, value:string|number, inline:boolean}> = [];
1414+ const guilds = bot.guilds.cache.array();
1515+1616+ info.push({
1717+ name: 'Number of guilds currently being served',
1818+ value: guilds.length,
1919+ inline: false,
2020+ });
2121+ info.push({
2222+ name: 'Number of users currently being served',
2323+ value: bot.users.cache.size,
2424+ inline: false,
2525+ });
2626+2727+2828+ // uptime calc
2929+ const u = bot.uptime || 0;
3030+ let uptime = '';
3131+ // days
3232+ let x = Math.floor(u / 3600000 / 24);
3333+ if (x > 0) {
3434+ uptime += `${x}d : `;
3535+ }
3636+ // hours
3737+ x = Math.floor(u / 3600000) % 24;
3838+ if (x > 0) {
3939+ uptime += `${x}h : `;
4040+ }
4141+ // mins
4242+ x = Math.floor(u / 60000) % 60;
4343+ if (x > 0) {
4444+ uptime += `${x}m : `;
4545+ }
4646+ // secs
4747+ x = Math.floor(u / 1000) % 60;
4848+ if (x > 0) {
4949+ uptime += `${x}s`;
5050+ }
5151+ // enbettung in ausgabe
5252+ info.push({
5353+ name: 'Time since last restart',
5454+ value: uptime,
5555+ inline: false,
5656+ });
5757+ if (msg.author.id === OWNERID) {
5858+ const memInfo = await si.mem();
5959+ const memUsedByProccess = process.memoryUsage().rss;
6060+ info.push({
6161+ name: 'Memory usage:',
6262+ value: `Memory used by this: ${Math.round(memUsedByProccess / 1048576)} MB (${Math.round((memUsedByProccess / memInfo.used) * 100)}% of used memory)
6363+ Total available memory: ${Math.round(memInfo.total / 1048576)} MB
6464+ Used memory: ${Math.round(memInfo.used / 1048576)} MB (${Math.round((memInfo.used / memInfo.total) * 100)}%)`,
6565+ inline: false,
6666+ });
6767+ }
6868+6969+ const embed :MessageEmbedOptions = {
7070+ color: COLOR,
7171+ description: 'Here are some stats:',
7272+ fields: info,
7373+ footer: {
7474+ iconURL: user().avatarURL() || '',
7575+ text: SIGN,
7676+ },
7777+ };
7878+7979+ msg.channel.send({ embed });
8080+ },
8181+ ehelp() {
8282+ return [{ name: '', value: 'Get some stats from the bot.' }];
8383+ },
8484+ perm: 'SEND_MESSAGES',
8585+ admin: false,
8686+ hide: false,
8787+ category: commandCategories.misc,
8888+ dev: false,
8989+};
+223
src/commands/vote.ts
···11+import data from '../db';
22+import { asyncForEach } from '../bamands';
33+import { commandCategories } from '../types/enums';
44+55+const reactions = [
66+ '🇦',
77+ '🇧',
88+ '🇨',
99+ '🇩',
1010+ '🇪',
1111+ '🇫',
1212+ '🇬',
1313+ '🇭',
1414+ '🇮',
1515+ '🇯',
1616+ '🇰',
1717+ '🇱',
1818+ '🇲',
1919+ '🇳',
2020+ '🇴',
2121+ '🇵',
2222+ '🇶',
2323+ '🇷',
2424+ '🇸',
2525+ '🇹',
2626+];
2727+2828+function getTime(content: string) {
2929+ const regex = /^(?:(\d+)d\s*?)?(?:(\d+)h\s*?)?(?:(\d+)m\s*?)?$/;
3030+ const matched = content.match(regex);
3131+ if (matched) {
3232+ const d = parseInt(matched[1], 10) || 0;
3333+ const h = parseInt(matched[2], 10) || 0;
3434+ const m = parseInt(matched[3], 10) || 0;
3535+ if (d + h + m > 0) {
3636+ return [d, h, m];
3737+ }
3838+ return false;
3939+ }
4040+ return false;
4141+}
4242+4343+export const vote: magibotCommand = {
4444+ name: 'vote',
4545+ dev: false,
4646+ main(content, msg) {
4747+ const authorID = msg.author.id;
4848+ msg.channel.send('What do you want the vote to be about?').then((mess) => {
4949+ msg.delete();
5050+ msg.channel
5151+ .awaitMessages((m) => m.author.id === authorID, { max: 1, time: 60000 })
5252+ .then((collected) => {
5353+ if (collected.first()) {
5454+ const topic = collected.first()!.content;
5555+ collected.first()!.delete();
5656+ mess.delete();
5757+ msg.channel
5858+ .send(
5959+ 'How long is this vote supposed to last? (use d h m format, e.g.: `2d 3h 5m`)',
6060+ )
6161+ .then((mess1) => {
6262+ msg.channel
6363+ .awaitMessages((m) => m.author.id === authorID, {
6464+ max: 1,
6565+ time: 60000,
6666+ })
6767+ .then((collected2) => {
6868+ if (collected2.first() && collected2.first()!.content) {
6969+ // time as array of values
7070+ const time = getTime(collected2.first()!.content);
7171+ collected2.first()!.delete();
7272+ mess1.delete();
7373+ // do checks on time validity
7474+ if (!time) {
7575+ msg.channel.send('Please use a valid time.');
7676+ return;
7777+ }
7878+ if (
7979+ time[0] > 7
8080+ || (time[0] > 6 && (time[1] > 0 || time[2] > 0))
8181+ ) {
8282+ msg.channel.send(
8383+ 'Votes are not allowed to last longer than 7 days, please use a valid time.',
8484+ );
8585+ return;
8686+ }
8787+ msg.channel
8888+ .send(
8989+ `What do you want the options to be for **${topic}**? Use \`option1|option2[|etc...]\``,
9090+ )
9191+ .then((mess2) => {
9292+ msg.channel
9393+ .awaitMessages((m) => m.author.id === authorID, {
9494+ max: 1,
9595+ time: 60000,
9696+ })
9797+ .then((collected3) => {
9898+ if (collected3.first()) {
9999+ const args = collected3
100100+ .first()!
101101+ .content.split('|');
102102+ collected3.first()!.delete();
103103+ mess2.delete();
104104+ if (args[0] && args.length <= 20) {
105105+ let str = '';
106106+ args.forEach((v, i) => {
107107+ str += `${reactions[i]} ${v}\n`;
108108+ });
109109+ let timestr = '';
110110+ const times = ['days ', 'hours ', 'minutes '];
111111+ time.forEach((v, s) => {
112112+ if (v > 0) {
113113+ timestr += `${v} ${times[s]}`;
114114+ }
115115+ });
116116+ msg.channel
117117+ .send(
118118+ `Do you want to start the vote **${topic}** lasting **${timestr}**with the options\n${str}`,
119119+ )
120120+ .then((mess3) => {
121121+ const filter = (reaction, user) => (reaction.emoji.name === '☑'
122122+ || reaction.emoji.name === '❌')
123123+ && user.id === authorID;
124124+ mess3.react('☑');
125125+ mess3.react('❌');
126126+ mess3
127127+ .awaitReactions(filter, {
128128+ max: 1,
129129+ time: 20000,
130130+ })
131131+ .then((reacts) => {
132132+ mess3.delete();
133133+ if (
134134+ reacts.first()
135135+ && reacts.first()!.emoji.name === '☑'
136136+ ) {
137137+ const dat = new Date();
138138+ const date = new Date(
139139+ dat.getFullYear(),
140140+ dat.getMonth(),
141141+ dat.getDate() + time[0],
142142+ dat.getHours() + time[1],
143143+ dat.getMinutes() + time[2],
144144+ dat.getSeconds(),
145145+ 0,
146146+ );
147147+ msg.channel
148148+ .send(
149149+ `**${topic}**\n*by ${msg.author}, ends on ${date}*\n\n${str}`,
150150+ )
151151+ .then(async (ms) => {
152152+ asyncForEach(
153153+ args,
154154+ async (val, i) => {
155155+ await ms.react(
156156+ reactions[i],
157157+ );
158158+ },
159159+ );
160160+ // vote structure
161161+ const vt = {
162162+ messageID: ms.id,
163163+ channelID: ms.channel.id,
164164+ options: args,
165165+ topic,
166166+ date,
167167+ guildid: ms.guild?.id,
168168+ authorID,
169169+ };
170170+ data.addVote(vt);
171171+ });
172172+ } else if (reacts.first()) {
173173+ msg.channel.send(
174174+ `successfully canceled vote **${topic}**`,
175175+ );
176176+ } else {
177177+ msg.channel.send(
178178+ 'canceled vote due to timeout.',
179179+ );
180180+ }
181181+ });
182182+ });
183183+ } else if (!args) {
184184+ msg.channel.send(
185185+ 'Please try again and add some options',
186186+ );
187187+ } else {
188188+ msg.channel.send(
189189+ 'There are only up to 20 options allowed, please try again with less options',
190190+ );
191191+ }
192192+ } else {
193193+ msg.channel.send(
194194+ 'canceled vote due to timeout.',
195195+ );
196196+ }
197197+ });
198198+ });
199199+ } else {
200200+ msg.channel.send('canceled vote due to timeout.');
201201+ }
202202+ });
203203+ });
204204+ } else {
205205+ msg.channel.send('canceled vote due to timeout.');
206206+ }
207207+ });
208208+ });
209209+ },
210210+ ehelp() {
211211+ return [
212212+ {
213213+ name: '',
214214+ value:
215215+ 'Start a vote with up to 20 different options. The maximum duration is 7 days.\nThe setup includes multiple steps which will be explained when you use the command.',
216216+ },
217217+ ];
218218+ },
219219+ admin: false,
220220+ hide: false,
221221+ perm: ['SEND_MESSAGES', 'MANAGE_MESSAGES'],
222222+ category: commandCategories.util,
223223+};
+1111
src/db.ts
···11+import DBL from 'dblapi.js';
22+import { MongoClient } from 'mongodb';
33+import {
44+ Client, TextChannel, Message, Guild, GuildMember,
55+} from 'discord.js';
66+import { OWNERID, PREFIXES, resetPrefixes } from './shared_assets';
77+import { asyncForEach } from './bamands';
88+99+const config = process.env;
1010+const dbl = new DBL(
1111+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjM4NDgyMDIzMjU4MzI0OTkyMSIsImJvdCI6dHJ1ZSwiaWF0IjoxNTE5NTgyMjYyfQ.df01BPWTU8O711eB_hive_T6RUjgzpBtXEcVSj63RW0',
1212+);
1313+1414+if (!config.dburl) {
1515+ throw new Error('Missing DB connection URL');
1616+}
1717+const url = config.dburl;
1818+1919+// Define Methods:
2020+function getuser(userid: string, guildID: string) {
2121+ return MongoClient.connect(url).then(async (mclient) => {
2222+ const db = await mclient.db('MagiBot');
2323+ const result = await db.collection('users').findOneAndUpdate(
2424+ { userID: userid, guildID },
2525+ {
2626+ $setOnInsert: {
2727+ warnings: 0,
2828+ kicks: 0,
2929+ bans: 0,
3030+ botusage: 0,
3131+ sound: false,
3232+ },
3333+ },
3434+ { returnOriginal: false, upsert: true },
3535+ );
3636+ mclient.close();
3737+ return result.value;
3838+ });
3939+}
4040+// eslint-disable-next-line require-await
4141+async function saltGuild(salter, guildID: string, add = 1, reset = false) {
4242+ return MongoClient.connect(url).then(async (mclient) => {
4343+ const db = mclient.db('MagiBot');
4444+ const user = await db
4545+ .collection('saltrank')
4646+ .findOne({ salter, guild: guildID });
4747+ if (!user) {
4848+ const myobj = { salter, salt: 1, guild: guildID };
4949+ await db.collection('saltrank').insertOne(myobj);
5050+ } else {
5151+ const slt = user.salt + add;
5252+ if (slt <= 0 || reset) {
5353+ await db.collection('saltrank').deleteOne({ salter, guild: guildID });
5454+ } else {
5555+ const update = { $set: { salt: slt } };
5656+ await db
5757+ .collection('saltrank')
5858+ .updateOne({ salter, guild: guildID }, update);
5959+ }
6060+ }
6161+ mclient.close();
6262+ });
6363+}
6464+// eslint-disable-next-line require-await
6565+async function addSalt(userid: string, reporter: string, guildID: string) {
6666+ return MongoClient.connect(url).then((mclient) => {
6767+ const db = mclient.db('MagiBot');
6868+ const date = new Date();
6969+ const myobj = {
7070+ salter: userid,
7171+ reporter,
7272+ date,
7373+ guild: guildID,
7474+ };
7575+ return db
7676+ .collection('salt')
7777+ .insertOne(myobj)
7878+ .then(async () => {
7979+ await saltGuild(userid, guildID, 1);
8080+ mclient.close();
8181+ return 0;
8282+ });
8383+ });
8484+}
8585+function updateUser(userid: string, update, guildID: string) {
8686+ MongoClient.connect(url, (err, mclient) => {
8787+ if (err) throw err;
8888+ const db = mclient.db('MagiBot');
8989+ db.collection('users').updateOne(
9090+ { userID: userid, guildID },
9191+ update,
9292+ (err2) => {
9393+ if (err2) throw err2;
9494+ mclient.close();
9595+ },
9696+ );
9797+ });
9898+}
9999+function saltDowntimeDone(userid1: string, userid2: string) {
100100+ // get newest entry in salt
101101+ return MongoClient.connect(url).then(async (mclient) => {
102102+ const db = mclient.db('MagiBot');
103103+ const d2 = await db
104104+ .collection('salt')
105105+ .find<{ date: Date }>({ salter: userid1, reporter: userid2 })
106106+ .sort({ date: -1 })
107107+ .limit(1)
108108+ .toArray();
109109+ mclient.close();
110110+ if (d2[0]) {
111111+ const d1 = new Date();
112112+ const ret = (d1.getTime() - d2[0].date.getTime()) / 1000 / 60 / 60;
113113+ return ret;
114114+ }
115115+ return 2;
116116+ });
117117+}
118118+function firstSettings(guildID: string) {
119119+ return MongoClient.connect(url).then(async (mclient) => {
120120+ const db = mclient.db('MagiBot');
121121+ await db.collection('settings').insertOne({
122122+ _id: guildID,
123123+ commandChannels: [],
124124+ adminRoles: [],
125125+ joinChannels: [],
126126+ blacklistedUsers: [],
127127+ blacklistedEveryone: [],
128128+ saltKing: false,
129129+ saltRole: false,
130130+ notChannel: false,
131131+ prefix: config.prefix,
132132+ lastConnected: new Date(),
133133+ });
134134+ const ret = await db.collection('settings').findOne({ _id: guildID });
135135+ mclient.close();
136136+ return ret;
137137+ });
138138+}
139139+function getSettings(guildID: string) {
140140+ return MongoClient.connect(url).then(async (mclient) => {
141141+ const db = mclient.db('MagiBot');
142142+ let result = await db.collection('settings').findOne({ _id: guildID });
143143+ if (!result) {
144144+ result = await firstSettings(guildID);
145145+ }
146146+ mclient.close();
147147+ return result;
148148+ });
149149+}
150150+async function checkGuild(id: string) {
151151+ // create settings
152152+ if (await getSettings(id)) {
153153+ return true;
154154+ }
155155+ return false;
156156+}
157157+function toggleDBLvoted(userid: string, votd: boolean) {
158158+ MongoClient.connect(url).then(async (mclient) => {
159159+ const db = await mclient.db('MagiBot');
160160+ await db
161161+ .collection('DBLreminder')
162162+ .updateOne({ _id: userid }, { $set: { voted: votd } });
163163+ mclient.close();
164164+ });
165165+}
166166+167167+// automatic deletion of reports:
168168+async function onHour(bot: Client, isFirst: boolean) {
169169+ const d = new Date();
170170+ const h = new Date(
171171+ d.getFullYear(),
172172+ d.getMonth(),
173173+ d.getDate() + 1,
174174+ 0,
175175+ 0,
176176+ 0,
177177+ 0,
178178+ );
179179+ const e = h.getTime() - d.getTime();
180180+ if (e > 100) {
181181+ // some arbitrary time period
182182+ setTimeout(onHour.bind(null, bot, false), e);
183183+ }
184184+ let msg: Message;
185185+ if (isFirst) {
186186+ const chann = bot.channels.fetch('382233880469438465');
187187+ msg = await ((await chann) as TextChannel)?.send('0 %');
188188+ }
189189+190190+ const t0 = process.hrtime();
191191+ const nd = new Date();
192192+ nd.setDate(nd.getDate() - 7);
193193+ const guilds = bot.guilds.cache.array();
194194+ await MongoClient.connect(url).then(async (mclient) => {
195195+ const db = mclient.db('MagiBot');
196196+ let counter = 0;
197197+ let percCounter = 0;
198198+ await asyncForEach(guilds, async (G) => {
199199+ const guildID = G.id;
200200+ await checkGuild(guildID);
201201+ // update the guild settings entry so that it does NOT get deleted
202202+ await db
203203+ .collection('settings')
204204+ .updateOne({ _id: guildID }, { $set: { lastConnected: d } });
205205+206206+ const ranking = await db
207207+ .collection('saltrank')
208208+ .find({ guild: guildID })
209209+ .toArray();
210210+211211+ asyncForEach(ranking, async (report) => {
212212+ const removeData = await db.collection('salt').deleteMany({
213213+ date: { $lt: nd },
214214+ guild: guildID,
215215+ salter: report.salter,
216216+ });
217217+ if (removeData.deletedCount && removeData.deletedCount > 0) {
218218+ const slt = report.salt - removeData.deletedCount;
219219+ if (slt <= 0) {
220220+ await db
221221+ .collection('saltrank')
222222+ .deleteOne({ salter: report.salter, guild: guildID });
223223+ } else {
224224+ await db
225225+ .collection('saltrank')
226226+ .updateOne(
227227+ { salter: report.salter, guild: guildID },
228228+ { $set: { salt: slt } },
229229+ );
230230+ }
231231+ }
232232+ });
233233+ });
234234+ // await updateSaltKing(G); this shouldnt be needed anymore
235235+236236+ // update percentage message
237237+ if (msg) {
238238+ const percentage = Math.round((++counter / guilds.length) * 100);
239239+ if (percentage - percCounter > 0) {
240240+ let uptime = '';
241241+ const u = process.hrtime(t0);
242242+ // mins
243243+ let x = Math.floor(u[0] / 60);
244244+ if (x > 0) {
245245+ uptime += `${x}m : `;
246246+ }
247247+ // secs
248248+ x = u[0] % 60;
249249+ if (x >= 0) {
250250+ uptime += `${x}s`;
251251+ }
252252+ percCounter = percentage;
253253+ msg.edit(`${percentage} % with ${uptime} passed`);
254254+ }
255255+ }
256256+ await mclient.close();
257257+ });
258258+259259+ // delete every guild where lastConnected < nd from the DB TODO
260260+ await MongoClient.connect(url).then(async (mclient) => {
261261+ const db = await mclient.db('MagiBot');
262262+ // find all guilds that have not connected for a week
263263+ // or dont have the lastConnected attribute at all
264264+ const guilds2 = await db
265265+ .collection('settings')
266266+ .find({
267267+ $or: [
268268+ { lastConnected: { $lt: nd } },
269269+ { lastConnected: { $exists: false } },
270270+ ],
271271+ })
272272+ .toArray();
273273+274274+ await asyncForEach(guilds2, async (guild) => {
275275+ // ignore salt and saltrank, as they are removed after 7 days anyways
276276+ // eslint-disable-next-line no-underscore-dangle
277277+ const guildID = guild._id;
278278+ // remove all data saved for those guilds
279279+ await db.collection('stillmuted').deleteMany({ guildid: guildID });
280280+ await db.collection('users').deleteMany({ guildID });
281281+ await db.collection('votes').deleteMany({ guildid: guildID });
282282+ await db.collection('settings').deleteOne({ _id: guildID });
283283+ });
284284+ mclient.close();
285285+ });
286286+}
287287+288288+async function dblReminder(bot: Client) {
289289+ const d = new Date();
290290+ const h = new Date(
291291+ d.getFullYear(),
292292+ d.getMonth(),
293293+ d.getDate(),
294294+ d.getHours(),
295295+ d.getMinutes() + 5,
296296+ 0,
297297+ 0,
298298+ );
299299+ const e = h.getTime() - d.getTime();
300300+ if (e > 100) {
301301+ // some arbitrary time period
302302+ setTimeout(dblReminder.bind(null, bot), e);
303303+ }
304304+ await MongoClient.connect(url).then(async (mclient) => {
305305+ const db = mclient.db('MagiBot');
306306+ const users = await db
307307+ .collection('DBLreminder')
308308+ .find()
309309+ .toArray();
310310+ mclient.close();
311311+ if (users) {
312312+ await asyncForEach(users, async (u) => {
313313+ // eslint-disable-next-line no-underscore-dangle
314314+ const user = await bot.users.fetch(u._id);
315315+ let dblFailed = false;
316316+ const hasVoted = await dbl.hasVoted(user.id).catch(() => {
317317+ dblFailed = true;
318318+ });
319319+ if (!dblFailed) {
320320+ if (!hasVoted && !u.voted) {
321321+ await user
322322+ .send(
323323+ `Hey there ${user} you can now vote for me again! (<https://discordbots.org/bot/384820232583249921> and <https://bots.ondiscord.xyz/bots/384820232583249921>)\nIf you don't want these reminders anymore use \`k.dbl\` in a server im on.`,
324324+ )
325325+ .catch(() => {});
326326+ toggleDBLvoted(user.id, true);
327327+ } else if (hasVoted && u.voted) {
328328+ toggleDBLvoted(user.id, false);
329329+ }
330330+ }
331331+ });
332332+ }
333333+ });
334334+}
335335+const reactions = [
336336+ '🇦',
337337+ '🇧',
338338+ '🇨',
339339+ '🇩',
340340+ '🇪',
341341+ '🇫',
342342+ '🇬',
343343+ '🇭',
344344+ '🇮',
345345+ '🇯',
346346+ '🇰',
347347+ '🇱',
348348+ '🇲',
349349+ '🇳',
350350+ '🇴',
351351+ '🇵',
352352+ '🇶',
353353+ '🇷',
354354+ '🇸',
355355+ '🇹',
356356+];
357357+// this should take care of everything that needs to be done when a vote ends
358358+async function endVote(
359359+ vote: {
360360+ messageID: Message['id'];
361361+ channelID: Message['channel']['id'];
362362+ authorID: string;
363363+ options: any;
364364+ topic: string;
365365+ date: Date;
366366+ },
367367+ bot: Client,
368368+) {
369369+ const chann = (await bot.channels.fetch(vote.channelID)) as TextChannel;
370370+ if (chann) {
371371+ const msg = await chann.messages.fetch(vote.messageID);
372372+ if (msg) {
373373+ const reacts = msg.reactions;
374374+ let finalReact: Array<{ reaction: string; count: number }> = [];
375375+ asyncForEach(reactions, async (x) => {
376376+ if (x >= vote.options.length) {
377377+ return;
378378+ }
379379+ const react = reacts.cache.get(reactions[x]);
380380+ if (react && react.count) {
381381+ if (!finalReact[0] || finalReact[0].count <= react.count) {
382382+ if (!finalReact[0] || finalReact[0].count < react.count) {
383383+ finalReact = [{ reaction: x, count: react.count }];
384384+ } else {
385385+ finalReact.push({ reaction: x, count: react.count });
386386+ }
387387+ }
388388+ }
389389+ });
390390+ if (finalReact[0]) {
391391+ if (finalReact.length > 1) {
392392+ let str = `**${vote.topic}** ended.\n\nThere was a tie between `;
393393+ if (vote.authorID) {
394394+ str = `**${vote.topic}** by <@${vote.authorID}> ended.\n\nThere was a tie between `;
395395+ }
396396+ finalReact.forEach((react, i) => {
397397+ str += `**${vote.options[react.reaction]}**`;
398398+ if (i < finalReact.length - 2) {
399399+ str += ', ';
400400+ } else if (i === finalReact.length - 2) {
401401+ str += ' and ';
402402+ }
403403+ });
404404+ str += ` with each having ** ${finalReact[0].count - 1} ** votes.`;
405405+ await msg.edit(str);
406406+ } else {
407407+ let str = `**${vote.topic}** ended.\n\nThe result is **${
408408+ vote.options[finalReact[0].reaction]
409409+ }** with **${finalReact[0].count - 1}** votes.`;
410410+ if (vote.authorID) {
411411+ str = `**${vote.topic}** by <@${
412412+ vote.authorID
413413+ }> ended.\n\nThe result is **${
414414+ vote.options[finalReact[0].reaction]
415415+ }** with **${finalReact[0].count - 1}** votes.`;
416416+ }
417417+ await msg.edit(str);
418418+ }
419419+ } else {
420420+ let str = `**${vote.topic}** ended.\n\nCould not compute a result.`;
421421+ if (vote.authorID) {
422422+ str = `**${vote.topic}** by <@${vote.authorID}> ended.\n\nCould not compute a result.`;
423423+ }
424424+ await msg.edit(str);
425425+ }
426426+ await msg.reactions.removeAll();
427427+ }
428428+ }
429429+}
430430+function voteCheck(bot: Client) {
431431+ const d = new Date();
432432+ const h = new Date(
433433+ d.getFullYear(),
434434+ d.getMonth(),
435435+ d.getDate(),
436436+ d.getHours(),
437437+ d.getMinutes(),
438438+ d.getSeconds() + 10,
439439+ 0,
440440+ );
441441+ const e = h.getTime() - d.getTime();
442442+ if (e > 100) {
443443+ // some arbitrary time period
444444+ setTimeout(voteCheck.bind(null, bot), e);
445445+ }
446446+ // do vote stuff
447447+ MongoClient.connect(url).then(async (mclient) => {
448448+ const db = mclient.db('MagiBot');
449449+ const nd = new Date();
450450+ const votes = await db
451451+ .collection('votes')
452452+ .find({ date: { $lte: nd } })
453453+ .toArray();
454454+ await asyncForEach(votes, async (vote) => {
455455+ await endVote(vote, bot);
456456+ await db.collection('votes').deleteOne(vote);
457457+ });
458458+ mclient.close();
459459+ });
460460+ // endof vote stuff
461461+}
462462+463463+function isInDBL(userID: string) {
464464+ return MongoClient.connect(url).then(async (mclient) => {
465465+ const db = await mclient.db('MagiBot');
466466+ const ret = await db
467467+ .collection('DBLreminder')
468468+ .find({ _id: userID })
469469+ .count();
470470+ mclient.close();
471471+ return ret;
472472+ });
473473+}
474474+475475+function toggleDBL(userID: string, add: boolean) {
476476+ MongoClient.connect(url).then(async (mclient) => {
477477+ const db = await mclient.db('MagiBot');
478478+ if (add && !(await isInDBL(userID))) {
479479+ await db
480480+ .collection('DBLreminder')
481481+ .insertOne({ _id: userID, voted: false });
482482+ } else if (!add) {
483483+ await db.collection('DBLreminder').deleteOne({ _id: userID });
484484+ }
485485+ mclient.close();
486486+ });
487487+}
488488+489489+function toggleStillMuted(userID: string, guildID: string, add: boolean) {
490490+ MongoClient.connect(url).then(async (mclient) => {
491491+ const db = await mclient.db('MagiBot');
492492+ if (
493493+ add
494494+ && !(
495495+ (await db
496496+ .collection('stillMuted')
497497+ .find({ userid: userID, guildid: guildID })
498498+ .count()) > 0
499499+ )
500500+ ) {
501501+ await db
502502+ .collection('stillMuted')
503503+ .insertOne({ userid: userID, guildid: guildID });
504504+ } else if (!add) {
505505+ await db
506506+ .collection('stillMuted')
507507+ .deleteMany({ userid: userID, guildid: guildID });
508508+ }
509509+ mclient.close();
510510+ });
511511+}
512512+async function getSaltKing(guildID: string) {
513513+ const settings = await getSettings(guildID);
514514+ return settings.saltKing;
515515+}
516516+async function getSaltRole(guildID: string) {
517517+ const set = await getSettings(guildID);
518518+ return set.saltRole;
519519+}
520520+function setSettings(guildID: string, settings) {
521521+ return MongoClient.connect(url).then(async (mclient) => {
522522+ const db = mclient.db('MagiBot');
523523+ if (await getSettings(guildID)) {
524524+ await db
525525+ .collection('settings')
526526+ .updateOne({ _id: guildID }, { $set: settings });
527527+ }
528528+ mclient.close();
529529+ return true;
530530+ });
531531+}
532532+async function setSaltRole(guildID: string, roleID: string) {
533533+ await setSettings(guildID, { saltRole: roleID });
534534+}
535535+async function getNotChannel(guildID:string) {
536536+ const set = await getSettings(guildID);
537537+ return set.notChannel;
538538+}
539539+// top 5 salty people
540540+function topSalt(guildID:string) {
541541+ return MongoClient.connect(url).then(async (mclient) => {
542542+ const db = mclient.db('MagiBot');
543543+ const result = await db
544544+ .collection('saltrank')
545545+ .find({ guild: guildID })
546546+ .sort({ salt: -1 })
547547+ .limit(5)
548548+ .toArray();
549549+ mclient.close();
550550+ if (!result) {
551551+ return [];
552552+ }
553553+ return result;
554554+ });
555555+}
556556+function setSaltKing(guildID: string, userID: string) {
557557+ return setSettings(guildID, { saltKing: userID });
558558+}
559559+async function updateSaltKing(G: Guild) {
560560+ if (G.available && G.me) {
561561+ if (
562562+ G.me.hasPermission('MANAGE_ROLES', {
563563+ checkAdmin: true,
564564+ checkOwner: false,
565565+ })
566566+ ) {
567567+ const SaltKing = await getSaltKing(G.id);
568568+ let SaltRole = await getSaltRole(G.id);
569569+ const groles = await G.roles;
570570+ if (!SaltRole || !groles.cache.has(SaltRole)) {
571571+ if (G.roles.cache.size < 250) {
572572+ await G.roles
573573+ .create({
574574+ data: {
575575+ name: 'SaltKing',
576576+ color: '#FFFFFF',
577577+ position: 0,
578578+ permissions: [],
579579+ mentionable: true,
580580+ },
581581+ reason:
582582+ 'SaltKing role needed for Saltranking to work. You can adjust this role if you like.',
583583+ })
584584+ .then(async (role) => {
585585+ await setSaltRole(G.id, role.id);
586586+ SaltRole = role.id;
587587+ });
588588+ } else {
589589+ const channel = await getNotChannel(G.id);
590590+ if (channel) {
591591+ const chan = G.channels.cache.get(channel);
592592+ if (chan?.permissionsFor(G.me)?.has('SEND_MESSAGES')) {
593593+ (chan as TextChannel).send(
594594+ `Hey there ${G.owner}!\nI regret to inform you that this server has 250 roles and I therefore can't add SaltKing. If you want to manage the role yourself delete one and then just change the settings of the role i create automatically.`,
595595+ );
596596+ }
597597+ }
598598+ return;
599599+ }
600600+ }
601601+ const sltID = await topSalt(G.id);
602602+ let saltID: string | undefined;
603603+ if (sltID[0]) {
604604+ saltID = sltID[0].salter;
605605+ }
606606+ const role = await groles.fetch(SaltRole);
607607+ if (role && role.position < G.me.roles.highest.position) {
608608+ if (SaltKing && saltID !== SaltKing) {
609609+ const user = await G.members.fetch(SaltKing).catch(() => {});
610610+ if (user) {
611611+ user.roles.remove(SaltRole, 'Is not as salty anymore');
612612+ }
613613+ }
614614+ if (saltID) {
615615+ const nuser = await G.members.fetch(saltID).catch(() => {});
616616+ if (nuser) {
617617+ if (!nuser.roles.cache.has(SaltRole)) {
618618+ await nuser.roles.add(SaltRole, 'Saltiest user');
619619+ }
620620+ }
621621+ if (saltID !== SaltKing) {
622622+ await setSaltKing(G.id, saltID);
623623+ }
624624+ }
625625+ } else {
626626+ const channel = await getNotChannel(G.id);
627627+ if (channel) {
628628+ const chan = G.channels.cache.get(channel);
629629+ if (chan?.permissionsFor(G.me)?.has('SEND_MESSAGES')) {
630630+ (chan as TextChannel).send(
631631+ `Hey there ${G.owner}!\nI regret to inform you that my highest role is beneath <@&${SaltRole}>, which has the effect that i cannot give or take if from users.`,
632632+ );
633633+ }
634634+ }
635635+ }
636636+ } else {
637637+ const channel = await getNotChannel(G.id);
638638+ if (channel) {
639639+ const chan = G.channels.cache.get(channel);
640640+ if (chan?.permissionsFor(G.me)?.has('SEND_MESSAGES')) {
641641+ (chan as TextChannel).send(
642642+ `Hey there ${G.owner}!\nI regret to inform you that i have no permission to manage roles and therefore can't manage the SaltKing role.`,
643643+ );
644644+ }
645645+ }
646646+ }
647647+ }
648648+}
649649+function setNotChannel(guildID: string, channelID: String | false) {
650650+ return setSettings(guildID, { notChannel: channelID });
651651+}
652652+async function sendUpdate(update: string, bot: Client) {
653653+ await asyncForEach(bot.guilds.cache.array(), async (G) => {
654654+ if (G.available) {
655655+ const cid = await getNotChannel(G.id);
656656+ if (cid) {
657657+ const channel = G.channels.cache.get(cid) as TextChannel;
658658+ if (channel && G.me) {
659659+ if (channel.permissionsFor(G.me)?.has('SEND_MESSAGES')) {
660660+ if (G.id === '380669498014957569') {
661661+ channel.send(`<@&460218236185739264> ${update}`);
662662+ } else {
663663+ channel.send(update);
664664+ }
665665+ }
666666+ } else {
667667+ setNotChannel(G.id, false);
668668+ }
669669+ }
670670+ }
671671+ });
672672+}
673673+674674+async function getSalt(userid: string, guildID: string) {
675675+ return MongoClient.connect(url).then(async (mclient) => {
676676+ const db = mclient.db('MagiBot');
677677+ const result = await db
678678+ .collection('saltrank')
679679+ .findOne({ salter: userid, guild: guildID });
680680+ mclient.close();
681681+ if (!result) {
682682+ return 0;
683683+ }
684684+ return result.salt;
685685+ });
686686+}
687687+688688+async function saltUp(
689689+ userid1: string,
690690+ userid2: string,
691691+ ad: boolean,
692692+ guildID: string,
693693+) {
694694+ const time = await saltDowntimeDone(userid1, userid2);
695695+ if (time > 1 || ad) {
696696+ return addSalt(userid1, userid2, guildID);
697697+ }
698698+ return time;
699699+}
700700+701701+async function usageUp(userid: string, guildID: string) {
702702+ const user = await getuser(userid, guildID);
703703+ const updateval = user.botusage + 1;
704704+ updateUser(userid, { $set: { botusage: updateval } }, guildID);
705705+}
706706+707707+async function checks(userid: string, guildID: string) {
708708+ // maybe add more checks
709709+ if (await getuser(userid, guildID)) {
710710+ return true;
711711+ }
712712+ // else
713713+ return false;
714714+}
715715+function setPrefix(guildID: string, pref?: string) {
716716+ return setSettings(guildID, { prefix: pref });
717717+}
718718+async function getPrefix(guildID: string) {
719719+ let settings = await getSettings(guildID);
720720+ settings = settings.prefix;
721721+ if (!settings) {
722722+ await setPrefix(guildID, config.prefix);
723723+ return config.prefix;
724724+ }
725725+ return settings;
726726+}
727727+728728+async function getAdminRole(guildID: string) {
729729+ const settings = await getSettings(guildID);
730730+ return settings.adminRoles as Array<string>;
731731+}
732732+733733+async function setAdminRole(guildID: string, roleID: string, insert: boolean) {
734734+ const roles = await getAdminRole(guildID);
735735+ if (insert) {
736736+ if (!roles.includes(roleID)) {
737737+ roles.push(roleID);
738738+ }
739739+ } else {
740740+ const index = roles.indexOf(roleID);
741741+ if (index > -1) {
742742+ roles.splice(index, 1);
743743+ }
744744+ }
745745+ const settings = { adminRoles: roles };
746746+ return setSettings(guildID, settings);
747747+}
748748+749749+async function getCommandChannel(guildID: string) {
750750+ const settings = await getSettings(guildID);
751751+ return settings.commandChannels;
752752+}
753753+754754+async function setCommandChannel(
755755+ guildID: string,
756756+ cid: string,
757757+ insert: boolean,
758758+) {
759759+ const channels = await getCommandChannel(guildID);
760760+ if (insert) {
761761+ if (!channels.includes(cid)) {
762762+ channels.push(cid);
763763+ }
764764+ } else {
765765+ const index = channels.indexOf(cid);
766766+ if (index > -1) {
767767+ channels.splice(index, 1);
768768+ }
769769+ }
770770+ const settings = { commandChannels: channels };
771771+ return setSettings(guildID, settings);
772772+}
773773+774774+async function getJoinChannel(guildID: string) {
775775+ const settings = await getSettings(guildID);
776776+ return settings.joinChannels;
777777+}
778778+779779+async function setJoinChannel(guildID: string, cid: string, insert: boolean) {
780780+ const channels = await getJoinChannel(guildID);
781781+ if (insert) {
782782+ if (!channels.includes(cid)) {
783783+ channels.push(cid);
784784+ }
785785+ } else {
786786+ const index = channels.indexOf(cid);
787787+ if (index > -1) {
788788+ channels.splice(index, 1);
789789+ }
790790+ }
791791+ const settings = { joinChannels: channels };
792792+ return setSettings(guildID, settings);
793793+}
794794+795795+async function getBlacklistedUser(guildID: string) {
796796+ const settings = await getSettings(guildID);
797797+ return settings.blacklistedUsers;
798798+}
799799+800800+async function isBlacklistedUser(userid: string, guildID: string) {
801801+ const users = await getBlacklistedUser(guildID);
802802+ return users.includes(userid);
803803+}
804804+805805+async function setBlacklistedUser(
806806+ userid: string,
807807+ guildID: string,
808808+ insert: boolean,
809809+) {
810810+ const users = await getBlacklistedUser(guildID);
811811+ if (insert) {
812812+ if (!users.includes(userid)) {
813813+ users.push(userid);
814814+ }
815815+ } else {
816816+ const index = users.indexOf(userid);
817817+ if (index > -1) {
818818+ users.splice(index, 1);
819819+ }
820820+ }
821821+ const settings = { blacklistedUsers: users };
822822+ return setSettings(guildID, settings);
823823+}
824824+/* eslint-disable */
825825+// TODO some time later , blacklist @everyone in these channels
826826+async function getBlacklistedEveryone(guildID: string) {
827827+ const settings = await getSettings(guildID);
828828+ return settings.blacklistedEveryone;
829829+}
830830+831831+async function setBlacklistedEveryone(
832832+ guildID: string,
833833+ cid: string,
834834+ insert: boolean
835835+) {}
836836+/* eslint-enable */
837837+838838+async function joinsound(
839839+ userid: string,
840840+ surl: string | false,
841841+ guildID: string,
842842+) {
843843+ return MongoClient.connect(url).then(async (mclient) => {
844844+ const db = mclient.db('MagiBot');
845845+ if (await checks(userid, guildID)) {
846846+ const update = { $set: { sound: surl } };
847847+ await db
848848+ .collection('users')
849849+ .updateOne({ userID: userid, guildID }, update);
850850+ }
851851+ mclient.close();
852852+ return true;
853853+ });
854854+}
855855+856856+export default {
857857+ async startup(bot: Client) {
858858+ // create Collection
859859+ await MongoClient.connect(url).then(async (mclient) => {
860860+ const db = mclient.db('MagiBot');
861861+ if (!db.collection('settings')) {
862862+ await db.createCollection('settings').then(() => {});
863863+ }
864864+ // Dataset of salt
865865+ if (!db.collection('salt')) {
866866+ db.createCollection('salt', (err) => {
867867+ if (err) throw err;
868868+ });
869869+ }
870870+ if (!db.collection('saltrank')) {
871871+ db.createCollection('saltrank', (err) => {
872872+ if (err) throw err;
873873+ });
874874+ }
875875+ if (!db.collection('users')) {
876876+ db.createCollection('users', (err) => {
877877+ if (err) throw err;
878878+ });
879879+ }
880880+ if (!db.collection('votes')) {
881881+ db.createCollection('votes', (err) => {
882882+ if (err) throw err;
883883+ });
884884+ }
885885+ mclient.close();
886886+ });
887887+ // repeating functions:
888888+ onHour(bot, true);
889889+ dblReminder(bot);
890890+ voteCheck(bot);
891891+ },
892892+ async getUser(userid: string, guildID: string) {
893893+ const result = await getuser(userid, guildID);
894894+ return result;
895895+ },
896896+ usageUp(userid: string, guildID: string) {
897897+ usageUp(userid, guildID);
898898+ },
899899+ async saltUp(userid1: string, userid2: string, G: Guild) {
900900+ const ret = await saltUp(userid1, userid2, false, G.id);
901901+ updateSaltKing(G);
902902+ return ret;
903903+ },
904904+ async saltUpAdmin(userid1: string, userid2: string, G: Guild) {
905905+ const ret = await saltUp(userid1, userid2, true, G.id);
906906+ updateSaltKing(G);
907907+ return ret;
908908+ },
909909+ getSalt(userid: string, guildID: string) {
910910+ return getSalt(userid, guildID);
911911+ },
912912+ async getUsage(userid: string, guildID: string) {
913913+ const user = await getuser(userid, guildID);
914914+ return parseInt(user.botusage, 10);
915915+ },
916916+ async remOldestSalt(userid: string, G: Guild) {
917917+ return MongoClient.connect(url).then(async (mclient) => {
918918+ const guildID = G.id;
919919+ const db = mclient.db('MagiBot');
920920+ const id = await db
921921+ .collection('salt')
922922+ .find({ salter: userid, guild: guildID })
923923+ .sort({ date: 1 })
924924+ .limit(1)
925925+ .toArray();
926926+ if (id[0]) {
927927+ // eslint-disable-next-line no-underscore-dangle
928928+ await db.collection('salt').deleteOne({ _id: id[0]._id });
929929+ saltGuild(userid, guildID, -1);
930930+ mclient.close();
931931+ updateSaltKing(G);
932932+ return true;
933933+ }
934934+ mclient.close();
935935+ return false;
936936+ });
937937+ },
938938+ async addGuild(guildID: string) {
939939+ await checkGuild(guildID);
940940+ },
941941+ topSalt(guildID: string) {
942942+ return topSalt(guildID);
943943+ },
944944+ async joinable(guildID: string, cid: string) {
945945+ const channels = await getJoinChannel(guildID);
946946+ return channels.includes(cid);
947947+ },
948948+ async isAdmin(guildID: string, user: GuildMember) {
949949+ // checks for admin and Owner, they can always use
950950+ if (user.hasPermission('ADMINISTRATOR', { checkAdmin: true, checkOwner: true })) {
951951+ return true;
952952+ }
953953+ // Owner is always admin hehe
954954+ if (user.id === OWNERID) {
955955+ return true;
956956+ }
957957+ const roles = await getAdminRole(guildID);
958958+ let ret = false;
959959+ roles.forEach((role) => {
960960+ if (user.roles.cache.has(role)) {
961961+ ret = true;
962962+ }
963963+ });
964964+ return ret;
965965+ },
966966+ isAdminRole: async (guildID: string, adminRole: string) => {
967967+ const roles = await getAdminRole(guildID);
968968+ let ret = false;
969969+ roles.forEach((role) => {
970970+ if (adminRole === role) {
971971+ ret = true;
972972+ }
973973+ });
974974+ return ret;
975975+ },
976976+ async commandAllowed(guildID: string, cid: string) {
977977+ const channels = await getCommandChannel(guildID);
978978+ return channels.length === 0 || channels.includes(cid);
979979+ },
980980+ async isCommandChannel(guildID: string, cid: string) {
981981+ const channels = await getCommandChannel(guildID);
982982+ return channels.includes(cid);
983983+ },
984984+ async commandChannel(guildID: string) {
985985+ const channels = await getCommandChannel(guildID);
986986+ let out = '';
987987+ channels.forEach((channel: string) => {
988988+ out += ` <#${channel}>`;
989989+ });
990990+ return out;
991991+ },
992992+ async getSound(userid: string, guildID: string) {
993993+ const user = await getuser(userid, guildID);
994994+ return user.sound;
995995+ },
996996+ addSound(userid: string, surl: string | false, guildID: string) {
997997+ return joinsound(userid, surl, guildID);
998998+ },
999999+ async isBlacklistedUser(userID: string, guildID: string) {
10001000+ if (await checks(userID, guildID)) {
10011001+ return isBlacklistedUser(userID, guildID);
10021002+ }
10031003+ return false;
10041004+ },
10051005+ setJoinable(guildID: string, channelID: string, insert: boolean) {
10061006+ return setJoinChannel(guildID, channelID, insert);
10071007+ },
10081008+ isJoinable: async (guildID: string, channelID: string) => {
10091009+ const channels = await getJoinChannel(guildID);
10101010+ return channels.includes(channelID);
10111011+ },
10121012+ setCommandChannel(guildID: string, channelID: string, insert: boolean) {
10131013+ return setCommandChannel(guildID, channelID, insert);
10141014+ },
10151015+ setAdmin(guildID: string, roleID: string, insert: boolean) {
10161016+ return setAdminRole(guildID, roleID, insert);
10171017+ },
10181018+ async setBlacklistedUser(guildID: string, userID: string, insert: boolean) {
10191019+ if (await checks(userID, guildID)) {
10201020+ return setBlacklistedUser(userID, guildID, insert);
10211021+ }
10221022+ return false;
10231023+ },
10241024+ getSettings(guildID: string) {
10251025+ return getSettings(guildID);
10261026+ },
10271027+ async clrSalt(userid: string, G: Guild) {
10281028+ return MongoClient.connect(url).then(async (mclient) => {
10291029+ const guildID = G.id;
10301030+ const db = mclient.db('MagiBot');
10311031+ await db
10321032+ .collection('salt')
10331033+ .deleteMany({ guild: guildID, salter: userid });
10341034+ await saltGuild(userid, guildID, 1, true);
10351035+ mclient.close();
10361036+ await updateSaltKing(G);
10371037+ });
10381038+ },
10391039+ async resetSalt(G: Guild) {
10401040+ await MongoClient.connect(url).then(async (mclient) => {
10411041+ const guildID = G.id;
10421042+ const db = mclient.db('MagiBot');
10431043+ await db.collection('saltrank').deleteMany({ guild: guildID });
10441044+ await db.collection('salt').deleteMany({ guild: guildID });
10451045+ await updateSaltKing(G);
10461046+ mclient.close();
10471047+ });
10481048+ },
10491049+ async setNotification(guildID: string, cid: string | false) {
10501050+ await setNotChannel(guildID, cid);
10511051+ },
10521052+ isNotChannel: async (guildID: string, channID: string) => {
10531053+ const notChann = await getNotChannel(guildID);
10541054+ return channID === notChann;
10551055+ },
10561056+ sendUpdate(update: string, bot: Client) {
10571057+ sendUpdate(update, bot);
10581058+ },
10591059+ getPrefixE(guildID: string) {
10601060+ return getPrefix(guildID);
10611061+ },
10621062+ async setPrefixE(guildID: string, pref: string) {
10631063+ await setPrefix(guildID, pref);
10641064+ PREFIXES[guildID] = pref;
10651065+ return pref;
10661066+ },
10671067+ async getPrefixesE(bot: Client) {
10681068+ resetPrefixes();
10691069+ const guilds = bot.guilds.cache.array();
10701070+ asyncForEach(guilds, async (G) => {
10711071+ PREFIXES[G.id] = await getPrefix(G.id);
10721072+ });
10731073+ },
10741074+ toggleDBLE(userID: string, add: boolean) {
10751075+ toggleDBL(userID, add);
10761076+ },
10771077+ getDBLE(userID: string) {
10781078+ return isInDBL(userID);
10791079+ },
10801080+ addVote(vote) {
10811081+ MongoClient.connect(url).then(async (mclient) => {
10821082+ const db = mclient.db('MagiBot');
10831083+ db.collection('votes').insertOne(vote);
10841084+ mclient.close();
10851085+ });
10861086+ },
10871087+ toggleStillMuted(userID: string, guildID: string, add: boolean) {
10881088+ toggleStillMuted(userID, guildID, add);
10891089+ },
10901090+ async isStillMuted(userID: string, guildID: string) {
10911091+ return MongoClient.connect(url).then(async (mclient) => {
10921092+ const db = mclient.db('MagiBot');
10931093+ const find = await db
10941094+ .collection('stillMuted')
10951095+ .findOne({ userid: userID, guildid: guildID });
10961096+ mclient.close();
10971097+ return find;
10981098+ });
10991099+ },
11001100+ async getDBLSubs() {
11011101+ return MongoClient.connect(url).then(async (mclient) => {
11021102+ const db = mclient.db('MagiBot');
11031103+ const users = await db
11041104+ .collection('DBLreminder')
11051105+ .find()
11061106+ .toArray();
11071107+ mclient.close();
11081108+ return users;
11091109+ });
11101110+ },
11111111+};
+9
src/renameto_token.ts
···11+// access to bot
22+// rename this file to token.ts
33+export default {
44+ tk: 'discordbottoken',
55+ owner: 'discordbotownerID',
66+ prefix: 'a', // has to be single char only, otherwise the assumption is false for a lot of functions
77+ dburl: 'databaseurl',
88+ blapis: [], // botlist access token array (format as in BLAPI)
99+};
+15
src/sendToMyDiscord.ts
···11+// commands made by Basti for use of the Bot
22+import Discord from 'discord.js';
33+import { bot } from './bot';
44+55+66+export async function catchErrorOnDiscord(err) {
77+ try {
88+ const chann = await bot.channels.fetch('414809410448261132');
99+ if (chann) {
1010+ (chann as Discord.TextChannel).send(`Caught error: ${err}`);
1111+ }
1212+ } catch (error) {
1313+ console.error(error);
1414+ }
1515+}
···11+{
22+ "include": ["**/*.ts", "**/*.js"],
33+ "exclude": ["node_modules"],
44+ "compilerOptions": {
55+ "target": "esnext",
66+ "module": "commonjs",
77+ // "module": "esnext", // older node.js is not able to use esnext modules
88+ "moduleResolution": "node",
99+ "lib": ["esnext", "esnext.asynciterable", "dom"],
1010+ "allowJs": true,
1111+ // "checkJs": true, maybe deactivating this allows us to have correct CI whilst porting..
1212+ "sourceMap": true,
1313+ "strict": true,
1414+ "noImplicitAny": false,
1515+ "noEmit": true,
1616+ "isolatedModules": true,
1717+ "baseUrl": ".src",
1818+ "strictNullChecks": true,
1919+ // not sure if both of these should be defined:
2020+ "typeRoots": ["types", "node_modules/@types"],
2121+ "types": ["@types/node"],
2222+ "allowSyntheticDefaultImports": true /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */,
2323+ /* Additional Checks */
2424+ // for now lets keep this out:
2525+ // "noUnusedLocals": true /* Report errors on unused locals. */,
2626+ // "noUnusedParameters": true /* Report errors on unused parameters. */,
2727+ "noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
2828+ "noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,
2929+ "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,
3030+ /* Experimental Options */
3131+ "experimentalDecorators": true /* Enables experimental support for ES7 decorators. */,
3232+ "outDir": "dist",
3333+ /* Module Resolution Options */
3434+ // not sure if this breaks things...
3535+ "paths": {
3636+ "*": ["node_modules/*"],
3737+ "~/*": ["./*"],
3838+ "@/*": ["./types/*"]
3939+ } /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
4040+ }
4141+}