mirror of
https://github.com/BluemediaGER/muse.git
synced 2024-11-09 11:45:29 +01:00
Inital commit
This commit is contained in:
parent
afadcb9ee5
commit
eca84c8b69
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -1,3 +1,8 @@
|
||||||
|
data
|
||||||
|
.DS_Store
|
||||||
|
dist
|
||||||
|
dts
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
logs
|
logs
|
||||||
*.log
|
*.log
|
||||||
|
|
6
nodemon.json
Normal file
6
nodemon.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"ignore": ["**/*.test.ts", "**/*.spec.ts", ".git", "node_modules"],
|
||||||
|
"watch": ["src"],
|
||||||
|
"exec": "npm start",
|
||||||
|
"ext": "ts"
|
||||||
|
}
|
73
package.json
Normal file
73
package.json
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
{
|
||||||
|
"name": "muse",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"description": "🎧 a self-hosted Discord music bot that doesn't suck ",
|
||||||
|
"main": "dist/index.js",
|
||||||
|
"repository": "git@github.com:codetheweb/muse.git",
|
||||||
|
"author": "Max Isom <hi@maxisom.me>",
|
||||||
|
"license": "MIT",
|
||||||
|
"private": true,
|
||||||
|
"types": "dts/types",
|
||||||
|
"files": [
|
||||||
|
"dist",
|
||||||
|
"dts"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint 'src/**/*.ts'",
|
||||||
|
"lint-fix": "eslint 'src/**/*.ts' --fix",
|
||||||
|
"clean": "rm -rf dist dts",
|
||||||
|
"test": "npm run lint",
|
||||||
|
"build": "tsc",
|
||||||
|
"watch": "tsc --watch",
|
||||||
|
"prepack": "npm run clean && npm run build",
|
||||||
|
"start": "node dist/index.js",
|
||||||
|
"dev": "nodemon"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bluebird": "^3.5.30",
|
||||||
|
"@types/node": "^13.9.0",
|
||||||
|
"@types/validator": "^12.0.1",
|
||||||
|
"@types/ws": "^7.2.2",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^2.22.0",
|
||||||
|
"@typescript-eslint/parser": "^2.22.0",
|
||||||
|
"eslint": "^6.8.0",
|
||||||
|
"eslint-config-xo": "^0.29.1",
|
||||||
|
"eslint-config-xo-typescript": "^0.26.0",
|
||||||
|
"husky": "^4.2.3",
|
||||||
|
"nodemon": "^2.0.2",
|
||||||
|
"reflect-metadata": "^0.1.13",
|
||||||
|
"ts-node": "^8.6.2",
|
||||||
|
"typescript": "^3.8.3"
|
||||||
|
},
|
||||||
|
"eslintConfig": {
|
||||||
|
"extends": [
|
||||||
|
"xo",
|
||||||
|
"xo-typescript/space"
|
||||||
|
],
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"parserOptions": {
|
||||||
|
"project": "./tsconfig.json"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"new-cap": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars": "off",
|
||||||
|
"@typescript-eslint/no-unused-vars-experimental": "error",
|
||||||
|
"@typescript-eslint/no-inferrable-types": "off"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"husky": {
|
||||||
|
"hooks": {
|
||||||
|
"pre-commit": "npm test && npm run build"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@discordjs/opus": "^0.1.0",
|
||||||
|
"discord.js": "^12.0.1",
|
||||||
|
"dotenv": "^8.2.0",
|
||||||
|
"make-dir": "^3.0.2",
|
||||||
|
"node-emoji": "^1.10.0",
|
||||||
|
"sequelize": "^5.21.5",
|
||||||
|
"sequelize-typescript": "^1.1.0",
|
||||||
|
"sqlite3": "^4.1.1"
|
||||||
|
}
|
||||||
|
}
|
24
src/commands/config.ts
Normal file
24
src/commands/config.ts
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
import {CommandHandler} from '../interfaces';
|
||||||
|
|
||||||
|
const config: CommandHandler = {
|
||||||
|
name: 'config',
|
||||||
|
description: 'Change various bot settings.',
|
||||||
|
execute: (msg, args) => {
|
||||||
|
const setting = args[0];
|
||||||
|
|
||||||
|
switch (setting) {
|
||||||
|
case 'prefix':
|
||||||
|
msg.channel.send('Prefix set');
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'channel':
|
||||||
|
msg.channel.send('Channel bound');
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
msg.channel.send('Unknown setting');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
73
src/events/guild-create.ts
Normal file
73
src/events/guild-create.ts
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import {Guild, MessageReaction, TextChannel} from 'discord.js';
|
||||||
|
import emoji from 'node-emoji';
|
||||||
|
import {Settings} from '../models';
|
||||||
|
|
||||||
|
const DEFAULT_PREFIX = '!';
|
||||||
|
|
||||||
|
export default async (guild: Guild): Promise<void> => {
|
||||||
|
await Settings.upsert({guildId: guild.id, prefix: DEFAULT_PREFIX});
|
||||||
|
|
||||||
|
const owner = await guild.client.users.fetch(guild.ownerID);
|
||||||
|
|
||||||
|
let firstStep = '👋 Hi!\n';
|
||||||
|
firstStep += 'I just need to ask a few questions before you start listening to music.\n\n';
|
||||||
|
firstStep += 'First, what channel should I listen to for music commands?\n\n';
|
||||||
|
|
||||||
|
interface EmojiChannel {
|
||||||
|
name: string;
|
||||||
|
id: string;
|
||||||
|
emoji: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const emojiChannels: EmojiChannel[] = [];
|
||||||
|
|
||||||
|
for (const [channelId, channel] of guild.channels.cache) {
|
||||||
|
if (channel.type === 'text') {
|
||||||
|
emojiChannels.push({
|
||||||
|
name: channel.name,
|
||||||
|
id: channelId,
|
||||||
|
emoji: emoji.random().emoji
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const channel of emojiChannels) {
|
||||||
|
firstStep += `${channel.emoji}: #${channel.name}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
firstStep += '\n';
|
||||||
|
|
||||||
|
// Send message
|
||||||
|
const msg = await owner.send(firstStep);
|
||||||
|
|
||||||
|
// Add reactions
|
||||||
|
for await (const channel of emojiChannels) {
|
||||||
|
await msg.react(channel.emoji);
|
||||||
|
}
|
||||||
|
|
||||||
|
const reactions = await msg.awaitReactions((reaction, user) => user.id !== msg.author.id && emojiChannels.map(e => e.emoji).includes(reaction.emoji.name), {max: 1});
|
||||||
|
|
||||||
|
const choice = reactions.first() as MessageReaction;
|
||||||
|
|
||||||
|
const chosenChannel = emojiChannels.find(e => e.emoji === choice.emoji.name) as EmojiChannel;
|
||||||
|
|
||||||
|
// Second setup step (get prefix)
|
||||||
|
let secondStep = `👍 Cool, I'll listen to **#${chosenChannel.name}** \n\n`;
|
||||||
|
secondStep += 'Last question: what character should I use for a prefix? Type a single character and hit enter.';
|
||||||
|
|
||||||
|
await owner.send(secondStep);
|
||||||
|
|
||||||
|
const prefixResponses = await msg.channel.awaitMessages(r => r.content.length === 1, {max: 1});
|
||||||
|
|
||||||
|
const prefixCharacter = prefixResponses.first()!.content;
|
||||||
|
|
||||||
|
// Save settings
|
||||||
|
await Settings.update({prefix: prefixCharacter, channel: chosenChannel.id}, {where: {guildId: guild.id}});
|
||||||
|
|
||||||
|
// Send welcome
|
||||||
|
const boundChannel = guild.client.channels.cache.get(chosenChannel.id) as TextChannel;
|
||||||
|
|
||||||
|
await boundChannel.send(`hey <@${owner.id}> try \`${prefixCharacter}play https://www.youtube.com/watch?v=dQw4w9WgXcQ\``);
|
||||||
|
|
||||||
|
await msg.channel.send(`Sounds good. Check out **#${chosenChannel.name}** to get started.`);
|
||||||
|
};
|
58
src/index.ts
Normal file
58
src/index.ts
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
import makeDir from 'make-dir';
|
||||||
|
import Discord from 'discord.js';
|
||||||
|
import {DISCORD_TOKEN, DISCORD_CLIENT_ID, DATA_DIR} from './utils/config';
|
||||||
|
import {sequelize} from './utils/db';
|
||||||
|
import {CommandHandler} from './interfaces';
|
||||||
|
import handleGuildCreate from './events/guild-create';
|
||||||
|
|
||||||
|
const PREFIX = '!';
|
||||||
|
|
||||||
|
const client = new Discord.Client();
|
||||||
|
const commands = new Discord.Collection();
|
||||||
|
|
||||||
|
// Load in commands
|
||||||
|
const commandFiles = fs.readdirSync(path.join(__dirname, 'commands')).filter(file => file.endsWith('.js'));
|
||||||
|
|
||||||
|
for (const file of commandFiles) {
|
||||||
|
const command = require(`./commands/${file}`).default;
|
||||||
|
|
||||||
|
commands.set(command.name, command);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic message handler
|
||||||
|
client.on('message', (msg: Discord.Message) => {
|
||||||
|
if (!msg.content.startsWith(PREFIX) || msg.author.bot) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const args = msg.content.slice(PREFIX.length).split(/ +/);
|
||||||
|
const command = args.shift()!.toLowerCase();
|
||||||
|
|
||||||
|
if (!commands.has(command)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const handler = commands.get(command) as CommandHandler;
|
||||||
|
|
||||||
|
handler.execute(msg, args);
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
msg.reply('there was an error trying to execute that command!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('ready', async () => {
|
||||||
|
// Create directory if necessary
|
||||||
|
await makeDir(DATA_DIR);
|
||||||
|
|
||||||
|
await sequelize.sync({});
|
||||||
|
|
||||||
|
console.log(`Ready! Invite the bot with https://discordapp.com/oauth2/authorize?client_id=${DISCORD_CLIENT_ID}&scope=bot`);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('guildCreate', handleGuildCreate);
|
||||||
|
|
||||||
|
client.login(DISCORD_TOKEN);
|
7
src/interfaces.ts
Normal file
7
src/interfaces.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import Discord from 'discord.js';
|
||||||
|
|
||||||
|
export interface CommandHandler {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
execute: (msg: Discord.Message, args: string[]) => void;
|
||||||
|
}
|
5
src/models/index.ts
Normal file
5
src/models/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
import Settings from './settings';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Settings
|
||||||
|
};
|
18
src/models/settings.ts
Normal file
18
src/models/settings.ts
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import {Table, Column, PrimaryKey, Model, Default} from 'sequelize-typescript';
|
||||||
|
|
||||||
|
@Table
|
||||||
|
export default class Settings extends Model<Settings> {
|
||||||
|
@PrimaryKey
|
||||||
|
@Column
|
||||||
|
guildId!: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
prefix!: string;
|
||||||
|
|
||||||
|
@Column
|
||||||
|
channel!: string;
|
||||||
|
|
||||||
|
@Default(false)
|
||||||
|
@Column
|
||||||
|
finishedSetup!: boolean;
|
||||||
|
}
|
1
src/packages.d.ts
vendored
Normal file
1
src/packages.d.ts
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
declare module 'node-emoji';
|
7
src/utils/config.ts
Normal file
7
src/utils/config.ts
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import dotenv from 'dotenv';
|
||||||
|
import path from 'path';
|
||||||
|
dotenv.config();
|
||||||
|
|
||||||
|
export const DISCORD_TOKEN: string = process.env.DISCORD_TOKEN ? process.env.DISCORD_TOKEN : '';
|
||||||
|
export const DISCORD_CLIENT_ID: string = process.env.DISCORD_CLIENT_ID ? process.env.DISCORD_CLIENT_ID : '';
|
||||||
|
export const DATA_DIR = path.resolve(process.env.DATA_DIR ? process.env.DATA_DIR : './data');
|
11
src/utils/db.ts
Normal file
11
src/utils/db.ts
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import {Sequelize} from 'sequelize-typescript';
|
||||||
|
import path from 'path';
|
||||||
|
import {DATA_DIR} from '../utils/config';
|
||||||
|
import {Settings} from '../models';
|
||||||
|
|
||||||
|
export const sequelize = new Sequelize({
|
||||||
|
dialect: 'sqlite',
|
||||||
|
database: 'muse',
|
||||||
|
storage: path.join(DATA_DIR, 'db.sqlite'),
|
||||||
|
models: [Settings]
|
||||||
|
});
|
17
tsconfig.json
Normal file
17
tsconfig.json
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ES2017"],
|
||||||
|
"target": "es6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"declaration": true,
|
||||||
|
"outDir": "dist",
|
||||||
|
"declarationDir": "dts",
|
||||||
|
"strict": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"sourceMap": true
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
Loading…
Reference in a new issue