mirror of
https://github.com/BluemediaGER/muse.git
synced 2024-11-12 21:05: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
|
||||
*.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