Compare commits
No commits in common. "typescript_db_rework" and "main" have entirely different histories.
typescript
...
main
49
.eslintrc.json
Normal file
49
.eslintrc.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"extends": "eslint:recommended",
|
||||
"env": {
|
||||
"node": true,
|
||||
"es6": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2021
|
||||
},
|
||||
"rules": {
|
||||
"arrow-spacing": ["warn", { "before": true, "after": true }],
|
||||
"brace-style": ["error", "stroustrup", { "allowSingleLine": true }],
|
||||
"comma-dangle": ["error", "always-multiline"],
|
||||
"comma-spacing": "error",
|
||||
"comma-style": "error",
|
||||
"curly": ["error", "multi-line", "consistent"],
|
||||
"dot-location": ["error", "property"],
|
||||
"handle-callback-err": "off",
|
||||
"indent": ["error", "tab"],
|
||||
"keyword-spacing": "error",
|
||||
"max-nested-callbacks": ["error", { "max": 4 }],
|
||||
"max-statements-per-line": ["error", { "max": 2 }],
|
||||
"no-console": "off",
|
||||
"no-empty-function": "error",
|
||||
"no-floating-decimal": "error",
|
||||
"no-inline-comments": "error",
|
||||
"no-lonely-if": "error",
|
||||
"no-multi-spaces": "error",
|
||||
"no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }],
|
||||
"no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }],
|
||||
"no-trailing-spaces": ["error"],
|
||||
"no-var": "error",
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"prefer-const": "error",
|
||||
"quotes": ["error", "single"],
|
||||
"semi": ["error", "always"],
|
||||
"space-before-blocks": "error",
|
||||
"space-before-function-paren": ["error", {
|
||||
"anonymous": "never",
|
||||
"named": "never",
|
||||
"asyncArrow": "always"
|
||||
}],
|
||||
"space-in-parens": "error",
|
||||
"space-infix-ops": "error",
|
||||
"space-unary-ops": "error",
|
||||
"spaced-comment": "error",
|
||||
"yoda": "error"
|
||||
}
|
||||
}
|
||||
8
.gitignore
vendored
8
.gitignore
vendored
@ -1,6 +1,4 @@
|
||||
node_modules
|
||||
dist
|
||||
breadbot.db
|
||||
.env
|
||||
tools/profanity_filter/bin/Words.json
|
||||
tools/profanity_filter/src/Words.json
|
||||
config.json
|
||||
node_modules
|
||||
keyfile.json
|
||||
File diff suppressed because it is too large
Load Diff
22
README.md
Normal file
22
README.md
Normal file
@ -0,0 +1,22 @@
|
||||
<h1>BreadBot</h1>
|
||||
|
||||
BreadBot helps, BreadBot does the things. Whatever things I'm willing to add to BreadBot.
|
||||
|
||||
BreadBot is the breadiest bread.
|
||||
|
||||
In all seriousness, BreadBot is my first real attempt at a Discord bot. I'm writing it such that it can really only be used in one server at the moment, although that will hopefully change in the future. The intention is really for it only to be used for the Team 2648 Discord server.
|
||||
|
||||
I have a subset of features that I want it to have before I call it "production ready" and a set of features that I want if I'm feeling adventurous and want to put in a lot of work.
|
||||
|
||||
"Production Ready" Features
|
||||
- [ ] Google Calendar Integration for Event Management
|
||||
- [ ] Create/Manage/Remove Events and Calendars
|
||||
- [ ] Add Autocomplete for common elements like Calendar Names, Event Names, Timezones, etc.
|
||||
- [ ] Calendar Announcements for Upcoming Events
|
||||
- [ ] Poll Creation and Results Announcements
|
||||
- [ ] Conversation Archiving (May be Removed)
|
||||
|
||||
"Adventurous (Lots of Work)" Features
|
||||
- [ ] BreadBot Voice Chat Hall Monitor
|
||||
- [ ] Breadle (BreadBot Idle Bread Baking Game)
|
||||
- [ ] BreadBot Calendar Send To Email
|
||||
67
breadbot.js
Normal file
67
breadbot.js
Normal file
@ -0,0 +1,67 @@
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const { Client, Events, GatewayIntentBits, Collection } = require('discord.js');
|
||||
const { token } = require('./config.json');
|
||||
|
||||
const getAllFiles = function(directoryPath, arrayOfFiles) {
|
||||
const files = fs.readdirSync(directoryPath);
|
||||
|
||||
arrayOfFiles = arrayOfFiles || [];
|
||||
|
||||
files.forEach(file => {
|
||||
if (fs.statSync(directoryPath + path.sep + file).isDirectory()) {
|
||||
arrayOfFiles = getAllFiles(directoryPath + path.sep + file, arrayOfFiles);
|
||||
}
|
||||
else {
|
||||
arrayOfFiles.push(path.join(__dirname, directoryPath, path.sep, file));
|
||||
}
|
||||
});
|
||||
|
||||
return arrayOfFiles;
|
||||
};
|
||||
|
||||
const allFiles = [];
|
||||
getAllFiles('.' + path.sep + 'commands', allFiles);
|
||||
|
||||
const client = new Client({ intents: [GatewayIntentBits.Guilds] });
|
||||
|
||||
client.commands = new Collection();
|
||||
|
||||
const commandFiles = allFiles.filter(file => file.endsWith('.js'));
|
||||
|
||||
for (const file of commandFiles) {
|
||||
const command = require(file);
|
||||
|
||||
if ('data' in command && 'execute' in command) {
|
||||
client.commands.set(command.data.name, command);
|
||||
console.log(`[INFO] Loaded command at ${file}`);
|
||||
}
|
||||
else {
|
||||
console.log(`[WARNING] The command at ${file} is missing a required "data" or "execute" property.`);
|
||||
}
|
||||
}
|
||||
|
||||
client.on(Events.InteractionCreate, async interaction => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
|
||||
const command = interaction.client.commands.get(interaction.commandName);
|
||||
|
||||
if (!command) {
|
||||
console.error(`No command matching ${interaction.commandName} was found.`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await command.execute(interaction);
|
||||
}
|
||||
catch (error) {
|
||||
console.error(error);
|
||||
await interaction.reply({ content: 'There was an error while executing this command!', ephemeral: true });
|
||||
}
|
||||
});
|
||||
|
||||
client.once(Events.ClientReady, c => {
|
||||
console.log(`Ready! Logged in as ${c.user.tag}`);
|
||||
});
|
||||
|
||||
client.login(token);
|
||||
33
calendarpulltest.js
Normal file
33
calendarpulltest.js
Normal file
@ -0,0 +1,33 @@
|
||||
const { google } = require('googleapis');
|
||||
const { googlePrivateKey, googleClientEmail, googleProjectNumber } = require('./config.json');
|
||||
|
||||
const SCOPES = ['https://www.googleapis.com/auth/calendar'];
|
||||
|
||||
async function main() {
|
||||
const jwtClient = new google.auth.JWT(
|
||||
googleClientEmail,
|
||||
null,
|
||||
googlePrivateKey,
|
||||
SCOPES,
|
||||
);
|
||||
|
||||
const calendar = new google.calendar({
|
||||
version: 'v3',
|
||||
project: googleProjectNumber,
|
||||
auth: jwtClient,
|
||||
});
|
||||
|
||||
calendar.calendarList.list({}, (err, res) => {
|
||||
if (err) {
|
||||
console.log('[ERROR]');
|
||||
console.log(err.errors);
|
||||
return;
|
||||
}
|
||||
console.log(res.data.items.map(x => x.summary + '---' + x.timeZone));
|
||||
});
|
||||
}
|
||||
|
||||
main().catch(e => {
|
||||
console.error(e);
|
||||
throw e;
|
||||
});
|
||||
37
commands/googlecalendar/addcalendar.js
Normal file
37
commands/googlecalendar/addcalendar.js
Normal file
@ -0,0 +1,37 @@
|
||||
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
|
||||
const { addCalendar } = require('../../utilities/googlecalendar.js');
|
||||
// const { getTimeZones } = require('@vvo/tzdb');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('addcalendar')
|
||||
.setDescription('Creates a new Google Calendar')
|
||||
.addStringOption(option =>
|
||||
option
|
||||
.setName('name')
|
||||
.setDescription('The new name for the calendar')
|
||||
.setRequired(true))
|
||||
.addStringOption(option =>
|
||||
option
|
||||
.setName('timezone')
|
||||
.setDescription('The Time Zone of this new calendar, must be in IANA format')
|
||||
.setRequired(true)),
|
||||
// .addChoices(getTimeZones().map(tz => {
|
||||
// return { name: tz.name, value: tz.name };
|
||||
// }))),
|
||||
async execute(interaction) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const name = interaction.options.getString('name');
|
||||
const timezone = interaction.options.getString('timezone');
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
addCalendar(name, timezone, async (success, message, extra) => {
|
||||
const embedResponse = new EmbedBuilder()
|
||||
.setColor(success ? 0x00FF00 : 0xFF0000)
|
||||
.setTitle(message);
|
||||
|
||||
await interaction.editReply({ embeds: [ embedResponse ] });
|
||||
});
|
||||
},
|
||||
};
|
||||
0
commands/googlecalendar/addevent.js
Normal file
0
commands/googlecalendar/addevent.js
Normal file
27
commands/googlecalendar/deletecalendar.js
Normal file
27
commands/googlecalendar/deletecalendar.js
Normal file
@ -0,0 +1,27 @@
|
||||
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
|
||||
const { deleteCalendar } = require('../../utilities/googlecalendar.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('deletecalendar')
|
||||
.setDescription('Permanently deletes a calendar and it\'s associated events')
|
||||
.addStringOption(option =>
|
||||
option
|
||||
.setName('name')
|
||||
.setDescription('The name of the calendar you want to delete')
|
||||
.setRequired(true)),
|
||||
async execute(interaction) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
const name = interaction.options.getString('name');
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
deleteCalendar(name, async (success, message, extra) => {
|
||||
const embedResponse = new EmbedBuilder()
|
||||
.setColor(success ? 0x0FF00 : 0xFF0000)
|
||||
.setTitle(message);
|
||||
|
||||
await interaction.editReply({ embeds: [ embedResponse ] });
|
||||
});
|
||||
},
|
||||
};
|
||||
0
commands/googlecalendar/deleteevent.js
Normal file
0
commands/googlecalendar/deleteevent.js
Normal file
20
commands/googlecalendar/listcalendar.js
Normal file
20
commands/googlecalendar/listcalendar.js
Normal file
@ -0,0 +1,20 @@
|
||||
const { SlashCommandBuilder, EmbedBuilder } = require('discord.js');
|
||||
const { getListOfCalendars } = require('../../utilities/googlecalendar');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('listcalendars')
|
||||
.setDescription('Lists the currently available calendars'),
|
||||
async execute(interaction) {
|
||||
await interaction.deferReply({ ephemeral: true });
|
||||
|
||||
getListOfCalendars({}, async (success, message, extra) => {
|
||||
const embedResponse = new EmbedBuilder()
|
||||
.setColor(success ? 0x00FF00 : 0xFF0000)
|
||||
.setTitle(message)
|
||||
.setDescription(extra.map(x => x.summary + ' --- ' + x.timeZone).join('\n\n'));
|
||||
|
||||
await interaction.editReply({ embeds: [ embedResponse ] });
|
||||
});
|
||||
},
|
||||
};
|
||||
0
commands/googlecalendar/listevents.js
Normal file
0
commands/googlecalendar/listevents.js
Normal file
0
commands/googlecalendar/modifyevent.js
Normal file
0
commands/googlecalendar/modifyevent.js
Normal file
10
commands/ping.js
Normal file
10
commands/ping.js
Normal file
@ -0,0 +1,10 @@
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('ping')
|
||||
.setDescription('Replies with Pong!'),
|
||||
async execute(interaction) {
|
||||
await interaction.reply('Pong!');
|
||||
},
|
||||
};
|
||||
11
commands/server.js
Normal file
11
commands/server.js
Normal file
@ -0,0 +1,11 @@
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('server')
|
||||
.setDescription('Provides information about the server.'),
|
||||
async execute(interaction) {
|
||||
// interaction.guild is the object representing the Guild in which the command was run
|
||||
await interaction.reply(`This server is ${interaction.guild.name} and has ${interaction.guild.memberCount} members.`);
|
||||
},
|
||||
};
|
||||
12
commands/user.js
Normal file
12
commands/user.js
Normal file
@ -0,0 +1,12 @@
|
||||
const { SlashCommandBuilder } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
data: new SlashCommandBuilder()
|
||||
.setName('user')
|
||||
.setDescription('Provides information about the user.'),
|
||||
async execute(interaction) {
|
||||
// interaction.user is the object representing the User who ran the command
|
||||
// interaction.member is the GuildMember object, which represents the user in the specific guild
|
||||
await interaction.reply(`This command was run by ${interaction.user.username}, who joined on ${interaction.member.joinedAt}.`);
|
||||
},
|
||||
};
|
||||
35
datetesting.js
Normal file
35
datetesting.js
Normal file
@ -0,0 +1,35 @@
|
||||
const readline = require('readline');
|
||||
|
||||
const r1 = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
terminal: false,
|
||||
});
|
||||
|
||||
let date1 = null;
|
||||
let date2 = null;
|
||||
let result = null;
|
||||
|
||||
r1.on('line', line => {
|
||||
if (line.startsWith('date1')) {
|
||||
date1 = new Date(line.split(':')[1]);
|
||||
}
|
||||
else if (line.startsWith('date2')) {
|
||||
date2 = new Date(line.split(':')[1]);
|
||||
}
|
||||
else if (line.startsWith('result')) {
|
||||
if (date1 !== null && date2 !== null) {
|
||||
result = new Date(date1.getFullYear(), date1.getMonth(), date1.getDate(),
|
||||
date2.getHours(), date2.getMinutes(), date2.getSeconds());
|
||||
|
||||
console.log(result);
|
||||
}
|
||||
}
|
||||
else {
|
||||
console.log('Bad command');
|
||||
}
|
||||
});
|
||||
|
||||
r1.once('close', () => {
|
||||
console.log('Closing');
|
||||
});
|
||||
63
deploy-commands.js
Normal file
63
deploy-commands.js
Normal file
@ -0,0 +1,63 @@
|
||||
const { REST, Routes } = require('discord.js');
|
||||
const { clientId, guildId, token } = require('./config.json');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const getAllFiles = function(directoryPath, arrayOfFiles) {
|
||||
const files = fs.readdirSync(directoryPath);
|
||||
|
||||
arrayOfFiles = arrayOfFiles || [];
|
||||
|
||||
files.forEach(file => {
|
||||
if (fs.statSync(directoryPath + path.sep + file).isDirectory()) {
|
||||
arrayOfFiles = getAllFiles(directoryPath + path.sep + file, arrayOfFiles);
|
||||
}
|
||||
else {
|
||||
arrayOfFiles.push(path.join(__dirname, directoryPath, path.sep, file));
|
||||
}
|
||||
});
|
||||
|
||||
return arrayOfFiles;
|
||||
};
|
||||
|
||||
const commands = [];
|
||||
const allFiles = [];
|
||||
getAllFiles('.' + path.sep + 'commands', allFiles);
|
||||
allFiles.forEach(file => {
|
||||
console.log(file);
|
||||
});
|
||||
// Grab all the command files from the commands directory you created earlier
|
||||
const commandFiles = allFiles.filter(file => file.endsWith('.js'));
|
||||
|
||||
// Grab the SlashCommandBuilder#toJSON() output of each command's data for deployment
|
||||
for (const file of commandFiles) {
|
||||
const command = require(`${file}`);
|
||||
if ('data' in command && 'execute' in command) {
|
||||
commands.push(command.data.toJSON());
|
||||
}
|
||||
else {
|
||||
console.log(`Skipping ${file} as it is missing one or more required exported fields`);
|
||||
}
|
||||
}
|
||||
|
||||
// Construct and prepare an instance of the REST module
|
||||
const rest = new REST({ version: '10' }).setToken(token);
|
||||
|
||||
// and deploy your commands!
|
||||
(async () => {
|
||||
try {
|
||||
console.log(`Started refreshing ${commands.length} application (/) commands.`);
|
||||
|
||||
// The put method is used to fully refresh all commands in the guild with the current set
|
||||
const data = await rest.put(
|
||||
Routes.applicationGuildCommands(clientId, guildId),
|
||||
{ body: commands },
|
||||
);
|
||||
|
||||
console.log(`Successfully reloaded ${data.length} application (/) commands.`);
|
||||
}
|
||||
catch (error) {
|
||||
// And of course, make sure you catch and log any errors!
|
||||
console.error(error);
|
||||
}
|
||||
})();
|
||||
7046
package-lock.json
generated
7046
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
30
package.json
30
package.json
@ -1,31 +1,19 @@
|
||||
{
|
||||
"name": "breadbot-test",
|
||||
"name": "breadbot",
|
||||
"version": "1.0.0",
|
||||
"main": "breadbot.ts",
|
||||
"description": "A very bready Discord bot",
|
||||
"main": "breadbot.js",
|
||||
"scripts": {
|
||||
"watch": "tsc -w",
|
||||
"start": "tsc && node dist/breadbot.js",
|
||||
"typeorm": "./node_modules/.bin/typeorm"
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"author": "Bradley Bickford",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"@discordjs/opus": "^0.9.0",
|
||||
"@discordjs/voice": "^0.18.0",
|
||||
"discord.js": "^14.20.0",
|
||||
"dotenv": "^16.5.0",
|
||||
"libsodium-wrappers": "^0.7.13",
|
||||
"node-crc": "1.3.2",
|
||||
"prism-media": "^2.0.0-alpha.0",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"sqlite3": "^5.1.7",
|
||||
"typeorm": "^0.3.27"
|
||||
"@vvo/tzdb": "^6.77.0",
|
||||
"discord.js": "^14.6.0",
|
||||
"googleapis": "^109.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.10.0",
|
||||
"tsup": "^8.5.0",
|
||||
"tsx": "^4.20.3",
|
||||
"typescript": "^5.8.3"
|
||||
"eslint": "^8.27.0"
|
||||
}
|
||||
}
|
||||
|
||||
119
src/breadbot.ts
119
src/breadbot.ts
@ -1,119 +0,0 @@
|
||||
import "reflect-metadata"
|
||||
import { ChatInputCommandInteraction, Client, Events, GatewayIntentBits, Guild, GuildBasedChannel, Interaction, Role } from "discord.js"
|
||||
import { config } from "./config"
|
||||
import { DataSource } from "typeorm"
|
||||
import { DBServer } from "./utilties/storage/entities/DBServer"
|
||||
import path from "path"
|
||||
import { DBChannel } from "./utilties/storage/entities/DBChannel"
|
||||
import { DBRole } from "./utilties/storage/entities/DBRole"
|
||||
import { insertGuild } from "./utilties/discord/guilds"
|
||||
import { insertChannel } from "./utilties/discord/channels"
|
||||
import { insertRole } from "./utilties/discord/roles"
|
||||
import { setupRoleCapture } from "./utilties/events/roles"
|
||||
import { DBUser } from "./utilties/storage/entities/DBUser"
|
||||
import { DBMessage } from "./utilties/storage/entities/DBMessage"
|
||||
import { DBMessageContentChanges } from "./utilties/storage/entities/DBMessageContentChanges"
|
||||
import { DBMessageAttachments } from "./utilties/storage/entities/DBMessageAttachment"
|
||||
import { setupMessageCapture } from "./utilties/events/messages"
|
||||
import { utilities } from "./utilties"
|
||||
import { commands } from "./commands"
|
||||
import { DBCall } from "./utilties/storage/entities/DBCall"
|
||||
import { DBCallTranscriptions } from "./utilties/storage/entities/DBCallTranscriptions"
|
||||
import { DBCallUsers } from "./utilties/storage/entities/DBCallUsers"
|
||||
import { DBMessageRegex } from "./utilties/storage/entities/DBMessageRegex"
|
||||
|
||||
console.log(__dirname + path.sep + "utilities" + path.sep + "storage" + path.sep + "entities" + path.sep + "*.ts")
|
||||
|
||||
export const dataSource = new DataSource({
|
||||
type: "sqlite",
|
||||
database: "breadbot.db",
|
||||
entities: [
|
||||
DBServer,
|
||||
DBChannel,
|
||||
DBRole,
|
||||
DBUser,
|
||||
DBMessage,
|
||||
DBMessageContentChanges,
|
||||
DBMessageAttachments,
|
||||
DBCall,
|
||||
DBCallTranscriptions,
|
||||
DBCallUsers,
|
||||
DBMessageRegex
|
||||
],
|
||||
synchronize: true,
|
||||
logging: true
|
||||
})
|
||||
|
||||
export const client : Client = new Client({
|
||||
intents: [
|
||||
GatewayIntentBits.Guilds,
|
||||
GatewayIntentBits.GuildMessages,
|
||||
GatewayIntentBits.GuildMembers,
|
||||
GatewayIntentBits.MessageContent,
|
||||
GatewayIntentBits.GuildVoiceStates
|
||||
]
|
||||
})
|
||||
|
||||
client.once(Events.ClientReady, async () => {
|
||||
await dataSource.initialize()
|
||||
|
||||
const serverRepo = dataSource.getRepository(DBServer)
|
||||
const channelRepo = dataSource.getRepository(DBChannel)
|
||||
const roleRepo = dataSource.getRepository(DBRole)
|
||||
const userRepo = dataSource.getRepository(DBUser)
|
||||
const messageRepo = dataSource.getRepository(DBMessage)
|
||||
const mccRepo = dataSource.getRepository(DBMessageContentChanges)
|
||||
const maRepo = dataSource.getRepository(DBMessageAttachments)
|
||||
|
||||
client.guilds.cache.forEach(async (guild: Guild) => {
|
||||
const server: DBServer | null = await insertGuild(serverRepo, guild)
|
||||
|
||||
if (server != null) {
|
||||
guild.channels.cache.forEach(async (channel: GuildBasedChannel) => {
|
||||
await insertChannel(channelRepo, channel)
|
||||
})
|
||||
|
||||
guild.roles.cache.forEach(async (role: Role) => {
|
||||
await insertRole(roleRepo, role)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
client.on(Events.GuildCreate, async (guild : Guild) => {
|
||||
await utilities.commands.deployCommands(guild.id)
|
||||
await utilities.guilds.insertGuild(serverRepo, guild)
|
||||
|
||||
guild.channels.cache.forEach(async (channel: GuildBasedChannel) => {
|
||||
await utilities.channels.insertChannel(channelRepo, channel)
|
||||
})
|
||||
|
||||
guild.roles.cache.forEach(async (role: Role) => {
|
||||
await insertRole(roleRepo, role)
|
||||
})
|
||||
})
|
||||
|
||||
client.on(Events.ChannelCreate, async (channel) => {
|
||||
await utilities.channels.insertChannel(channelRepo, channel)
|
||||
})
|
||||
|
||||
client.on(Events.ThreadCreate, async (channel) => {
|
||||
await utilities.channels.insertChannel(channelRepo, channel)
|
||||
})
|
||||
|
||||
client.on(Events.InteractionCreate, async (interaction: Interaction) => {
|
||||
if (!interaction.isCommand()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (commands[interaction.commandName as keyof typeof commands]) {
|
||||
commands[interaction.commandName as keyof typeof commands].execute(interaction as ChatInputCommandInteraction)
|
||||
}
|
||||
})
|
||||
|
||||
setupRoleCapture(client, serverRepo, roleRepo)
|
||||
setupMessageCapture(client, channelRepo, userRepo, messageRepo, mccRepo, maRepo)
|
||||
|
||||
console.log("Breadbot is Ready")
|
||||
})
|
||||
|
||||
client.login(config.DISCORD_TOKEN)
|
||||
@ -1,5 +0,0 @@
|
||||
import * as ping from "./ping";
|
||||
|
||||
export const commands = {
|
||||
ping
|
||||
}
|
||||
@ -1,11 +0,0 @@
|
||||
import { CommandInteraction, SlashCommandBuilder } from "discord.js"
|
||||
|
||||
export const enabled : boolean = true
|
||||
|
||||
export const data : SlashCommandBuilder = new SlashCommandBuilder()
|
||||
.setName("ping")
|
||||
.setDescription("Replies with Pong!")
|
||||
|
||||
export async function execute(interaction: CommandInteraction) {
|
||||
return interaction.reply("Pong!")
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
import dotenv from "dotenv"
|
||||
|
||||
dotenv.config()
|
||||
|
||||
const { DISCORD_TOKEN, DISCORD_CLIENT_ID, DB_MODE } = process.env
|
||||
|
||||
if (!DISCORD_TOKEN || !DISCORD_CLIENT_ID || !DB_MODE) {
|
||||
throw new Error("Missing environment variables")
|
||||
}
|
||||
|
||||
export const config = {
|
||||
DISCORD_TOKEN,
|
||||
DISCORD_CLIENT_ID,
|
||||
DB_MODE
|
||||
}
|
||||
@ -1,37 +0,0 @@
|
||||
import { DMChannel, GuildBasedChannel, PartialDMChannel } from "discord.js";
|
||||
import { Repository } from "typeorm";
|
||||
import { DBChannel } from "../storage/entities/DBChannel";
|
||||
|
||||
export async function doesChannelExistByID(db: Repository<DBChannel>, channelID: string) : Promise<boolean> {
|
||||
return (await db.findOne({"where": {channel_snowflake: channelID}})) != null
|
||||
}
|
||||
|
||||
export async function doesChannelExist(db: Repository<DBChannel>, channel : GuildBasedChannel | DMChannel | PartialDMChannel) : Promise<boolean> {
|
||||
return await doesChannelExistByID(db, channel.id)
|
||||
}
|
||||
|
||||
export async function insertChannel(db: Repository<DBChannel>, channel: GuildBasedChannel | DMChannel | PartialDMChannel) : Promise<DBChannel | null> {
|
||||
const alreadyExists: boolean = await doesChannelExist(db, channel)
|
||||
|
||||
if(alreadyExists) {
|
||||
return await db.findOne({"where": {channel_snowflake: channel.id}})
|
||||
}
|
||||
|
||||
try {
|
||||
const newChannel : DBChannel = await db.create({
|
||||
channel_snowflake: channel.id,
|
||||
channel_name: channel.isDMBased() ? channel.recipient?.username : channel.name,
|
||||
is_dm: channel.isDMBased(),
|
||||
is_thread: channel.isThread(),
|
||||
is_voice: channel.isVoiceBased(),
|
||||
server: channel.isDMBased() ? null : {server_snowflake: channel.guild.id}
|
||||
})
|
||||
|
||||
return await db.save(newChannel)
|
||||
} catch (err) {
|
||||
//TODO Winston should handle this
|
||||
console.log("CHANNEL INSERT ERROR")
|
||||
console.log(err)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -1,32 +0,0 @@
|
||||
import { REST, Routes } from "discord.js"
|
||||
import { config } from "../../config"
|
||||
import { commands } from "../../commands"
|
||||
|
||||
const commandsData = Object.values(commands)
|
||||
.filter((command) => command.enabled)
|
||||
.map((command) => command.data)
|
||||
|
||||
const rest : REST = new REST({ version: "10" }).setToken(config.DISCORD_TOKEN)
|
||||
|
||||
export async function deployCommands(guildId: string) {
|
||||
try {
|
||||
// TODO Winston should handle this
|
||||
console.log(`Refreshing slash commands for ${guildId}`)
|
||||
|
||||
await rest.put(
|
||||
Routes.applicationGuildCommands(
|
||||
config.DISCORD_CLIENT_ID,
|
||||
guildId
|
||||
),
|
||||
{
|
||||
body: commandsData
|
||||
}
|
||||
)
|
||||
|
||||
// TODO Winston should handle this
|
||||
console.log(`Successfully reloaded slash commands for ${guildId}`)
|
||||
} catch (error) {
|
||||
// TODO Winston should handle this
|
||||
console.error(error)
|
||||
}
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
import { Guild } from "discord.js";
|
||||
import { Repository } from "typeorm";
|
||||
import { DBServer } from "../storage/entities/DBServer";
|
||||
|
||||
export async function doesGuildExist(db: Repository<DBServer>, guild : Guild) : Promise<boolean> {
|
||||
return (await db.findOne({"where": {server_snowflake: guild.id}})) != null
|
||||
}
|
||||
|
||||
export async function insertGuild(db: Repository<DBServer>, guild: Guild) : Promise<DBServer | null> {
|
||||
const alreadyExists: boolean = await doesGuildExist(db, guild)
|
||||
|
||||
if (alreadyExists) {
|
||||
return await db.findOne({"where": {server_snowflake: guild.id}})
|
||||
}
|
||||
try {
|
||||
const server: DBServer = await db.create({
|
||||
server_snowflake: guild.id,
|
||||
server_name: guild.name,
|
||||
server_description: guild.description ?? ""
|
||||
})
|
||||
|
||||
return await db.save(server)
|
||||
} catch (err) {
|
||||
console.log("Insert Failed")
|
||||
//TODO Winston should handle this
|
||||
console.log(err)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -1,100 +0,0 @@
|
||||
import { Attachment, Message, OmitPartialGroupDMChannel, PartialMessage } from "discord.js";
|
||||
import { Repository } from "typeorm";
|
||||
import { DBMessage } from "../storage/entities/DBMessage";
|
||||
import { DBMessageAttachments } from "../storage/entities/DBMessageAttachment";
|
||||
import { DBMessageContentChanges } from "../storage/entities/DBMessageContentChanges";
|
||||
|
||||
// TODO Do partial messages affect other functionality elsewhere?
|
||||
|
||||
export async function doesMessageExist(db: Repository<DBMessage>, message: OmitPartialGroupDMChannel<Message<boolean>> | PartialMessage) : Promise<boolean> {
|
||||
return (await db.findOne({"where": {message_snowflake: message.id}})) != null
|
||||
}
|
||||
|
||||
export async function doesAttachmentExist(db: Repository<DBMessageAttachments>, attachment: Attachment) : Promise<boolean> {
|
||||
return (await db.findOne({"where": {attachment_snowflake: attachment.id}})) != null
|
||||
}
|
||||
|
||||
export async function insertMessage(messageDB: Repository<DBMessage>, maDB: Repository<DBMessageAttachments>, message: OmitPartialGroupDMChannel<Message<boolean>>) : Promise<DBMessage | null> {
|
||||
const alreadyExists: boolean = await doesMessageExist(messageDB, message)
|
||||
|
||||
if(alreadyExists) {
|
||||
return await messageDB.findOne({"where": {message_snowflake: message.id}})
|
||||
}
|
||||
|
||||
try {
|
||||
const newMessage: DBMessage = await messageDB.create({
|
||||
message_snowflake: message.id,
|
||||
channel: {channel_snowflake: message.channel.id},
|
||||
user: {user_snowflake: message.author.id},
|
||||
message_content: message.content,
|
||||
message_timestamp: message.createdAt,
|
||||
attachments: message.attachments.size == 0 ? null : message.attachments.map((attachment: Attachment) => {
|
||||
return maDB.create({
|
||||
attachment_snowflake: attachment.id,
|
||||
message: {message_snowflake: message.id},
|
||||
attachment_name: attachment.name,
|
||||
attachment_description: attachment.description,
|
||||
attachment_timestamp: message.createdAt,
|
||||
attachment_mime_type: attachment.contentType,
|
||||
attachment_url: attachment.url
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return await messageDB.save(newMessage)
|
||||
} catch (err) {
|
||||
//TODO Winston should handle this
|
||||
console.log("MESSAGE INSERTION ERROR")
|
||||
console.log(err)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateMessageContentHistory(messageDB: Repository<DBMessage>, mccDB: Repository<DBMessageContentChanges>,
|
||||
ma: Repository<DBMessageAttachments>, message: OmitPartialGroupDMChannel<Message<boolean>>) : Promise<DBMessage | null> {
|
||||
let dbMessage: DBMessage | null = await messageDB.findOne({"where": {message_snowflake: message.id}})
|
||||
|
||||
if(dbMessage == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const contentChange: DBMessageContentChanges = mccDB.create({
|
||||
message: {message_snowflake: message.id},
|
||||
message_change_old_content: dbMessage.message_content,
|
||||
message_change_old_timestamp: dbMessage.message_timestamp
|
||||
})
|
||||
|
||||
dbMessage.message_content = message.content
|
||||
dbMessage.message_timestamp = message.editedAt ?? message.createdAt
|
||||
|
||||
// TODO This should really be a transaction
|
||||
// TODO Changes to attachments aren't captured
|
||||
return await mccDB.save(contentChange).then(async (dbmcc: DBMessageContentChanges) => {
|
||||
return await messageDB.save(dbMessage)
|
||||
})
|
||||
} catch (err) {
|
||||
//TODO Winston should handle this
|
||||
console.log("MESSAGE MODIFY FAILED")
|
||||
console.log(err)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function markMessageDeleted(db: Repository<DBMessage>, message: OmitPartialGroupDMChannel<Message<boolean>> | PartialMessage) : Promise<DBMessage | null> {
|
||||
let dbMessage: DBMessage | null = await db.findOne({"where": {message_snowflake: message.id}})
|
||||
|
||||
if(dbMessage == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
dbMessage.message_deleted = true
|
||||
|
||||
return await db.save(dbMessage)
|
||||
} catch (err) {
|
||||
// TODO Winston should handle this
|
||||
console.log(err)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
import { Guild } from "discord.js";
|
||||
import { DBMessageRegex } from "../storage/entities/DBMessageRegex";
|
||||
import { Repository } from "typeorm";
|
||||
import { DBServer } from "../storage/entities/DBServer";
|
||||
|
||||
export async function getRegexesForGuild(db: Repository<DBServer>, guild: Guild): Promise<DBMessageRegex[] | null | undefined> {
|
||||
return (await db.findOne({
|
||||
select: {
|
||||
regexes: true
|
||||
},
|
||||
relations: {
|
||||
regexes: true
|
||||
},
|
||||
where: {
|
||||
server_snowflake: guild.id
|
||||
}
|
||||
}))?.regexes
|
||||
}
|
||||
@ -1,79 +0,0 @@
|
||||
import { Role } from "discord.js";
|
||||
import { Repository } from "typeorm";
|
||||
import { DBRole } from "../storage/entities/DBRole";
|
||||
|
||||
export async function doesRoleExist(db: Repository<DBRole>, role : Role) : Promise<boolean> {
|
||||
return (await db.findOne({"where": {role_snowflake: role.id}})) != null
|
||||
}
|
||||
|
||||
export async function insertRole(db: Repository<DBRole>, role: Role) : Promise<DBRole | null> {
|
||||
const alreadyExists: boolean = await doesRoleExist(db, role)
|
||||
|
||||
if(alreadyExists) {
|
||||
return await db.findOne({"where": {role_snowflake: role.id}})
|
||||
}
|
||||
|
||||
try {
|
||||
const newRole : DBRole = await db.create({
|
||||
role_snowflake: role.id,
|
||||
server: {server_snowflake: role.guild.id},
|
||||
role_name: role.name,
|
||||
is_deleted: false
|
||||
})
|
||||
|
||||
return await db.save(newRole)
|
||||
} catch (err) {
|
||||
console.log("ROLE INSERT ERROR")
|
||||
console.log(err)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateRole(db: Repository<DBRole>, role: Role): Promise<DBRole | null> {
|
||||
const roleExists: boolean = await doesRoleExist(db, role)
|
||||
|
||||
if(!roleExists) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const toUpdate: DBRole | null = await db.findOne({"where": {role_snowflake: role.id}})
|
||||
|
||||
if(toUpdate != null) {
|
||||
toUpdate.role_name = role.name
|
||||
|
||||
return await db.save(toUpdate)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.log("ROLE UPDATE FAILED")
|
||||
console.log(err)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export async function markRoleDeleted(db: Repository<DBRole>, role: Role) : Promise<DBRole | null> {
|
||||
const roleExists: boolean = await doesRoleExist(db, role)
|
||||
|
||||
if(!roleExists) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const toUpdate: DBRole | null = await db.findOne({"where": {role_snowflake: role.id}})
|
||||
|
||||
if(toUpdate != null) {
|
||||
toUpdate.is_deleted = true
|
||||
|
||||
return await db.save(toUpdate)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
} catch (err) {
|
||||
console.log("ROLE DELETE FAILED")
|
||||
console.log(err)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -1,30 +0,0 @@
|
||||
import { User } from "discord.js";
|
||||
import { Repository } from "typeorm";
|
||||
import { DBUser } from "../storage/entities/DBUser";
|
||||
|
||||
export async function doesUserExist(db: Repository<DBUser>, user: User): Promise<boolean> {
|
||||
return (await db.findOne({"where": {user_snowflake: user.id}})) != null
|
||||
}
|
||||
|
||||
export async function insertUser(db: Repository<DBUser>, user: User): Promise<DBUser | null> {
|
||||
const alreadyExists: boolean = await doesUserExist(db, user)
|
||||
|
||||
if(alreadyExists) {
|
||||
return null
|
||||
}
|
||||
|
||||
try {
|
||||
const newUser: DBUser = db.create({
|
||||
user_snowflake: user.id,
|
||||
user_name: user.username,
|
||||
user_displayname: user.displayName
|
||||
})
|
||||
|
||||
return await db.save(newUser)
|
||||
} catch (err) {
|
||||
//TODO Winston should handle this
|
||||
console.log("USER INSERT ERROR")
|
||||
console.log(err)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
import { VoiceBasedChannel } from "discord.js";
|
||||
import { IsNull, Repository } from "typeorm";
|
||||
import { DBCall } from "../storage/entities/DBCall";
|
||||
|
||||
export async function breadbotInCall(db: Repository<DBCall>, channel: VoiceBasedChannel) : Promise<boolean> {
|
||||
return (await db.findOne({
|
||||
where: {
|
||||
channel: {channel_snowflake: channel.id},
|
||||
call_end_time: IsNull()
|
||||
}
|
||||
})) != null
|
||||
}
|
||||
|
||||
export async function getExistingCallID(db: Repository<DBCall>, channel: VoiceBasedChannel) : Promise<Number | undefined> {
|
||||
return (await db.findOne({
|
||||
where: {
|
||||
channel: {channel_snowflake: channel.id},
|
||||
call_end_time: IsNull()
|
||||
}
|
||||
}))?.call_id
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
import * as roles from "./roles"
|
||||
import * as messages from "./messages"
|
||||
|
||||
export const events = {
|
||||
roles,
|
||||
messages
|
||||
}
|
||||
@ -1,57 +0,0 @@
|
||||
import { Client, Events, Message, OmitPartialGroupDMChannel, PartialMessage } from "discord.js";
|
||||
import { insertChannel } from "../discord/channels";
|
||||
import { insertUser } from "../discord/users";
|
||||
import { insertMessage, markMessageDeleted, updateMessageContentHistory } from "../discord/messages";
|
||||
import { Repository } from "typeorm";
|
||||
import { DBMessage } from "../storage/entities/DBMessage";
|
||||
import { DBMessageContentChanges } from "../storage/entities/DBMessageContentChanges";
|
||||
import { DBMessageAttachments } from "../storage/entities/DBMessageAttachment";
|
||||
import { DBChannel } from "../storage/entities/DBChannel";
|
||||
import { DBUser } from "../storage/entities/DBUser";
|
||||
|
||||
export function setupMessageCapture(client: Client,
|
||||
channelDB: Repository<DBChannel>,
|
||||
userDB: Repository<DBUser>,
|
||||
messageDB: Repository<DBMessage>,
|
||||
mccDB: Repository<DBMessageContentChanges>,
|
||||
maDB: Repository<DBMessageAttachments>
|
||||
) {
|
||||
client.on(Events.MessageCreate, async (message) => {
|
||||
await processMessageCreate(channelDB, userDB, messageDB, maDB, message)
|
||||
})
|
||||
|
||||
client.on(Events.MessageUpdate, async (oldMessage, newMessage) => {
|
||||
await processMessageModify(messageDB, mccDB, maDB, newMessage)
|
||||
})
|
||||
|
||||
client.on(Events.MessageDelete, async (deletedMessage) => {
|
||||
await processMessageDeleted(messageDB, deletedMessage)
|
||||
})
|
||||
}
|
||||
|
||||
async function processMessageCreate(
|
||||
channelDB: Repository<DBChannel>,
|
||||
userDB: Repository<DBUser>,
|
||||
messageDB: Repository<DBMessage>,
|
||||
maDB: Repository<DBMessageAttachments>,
|
||||
message: OmitPartialGroupDMChannel<Message<boolean>>) {
|
||||
const channelOk: DBChannel | null = await insertChannel(channelDB, message.channel)
|
||||
const userOk: DBUser | null = await insertUser(userDB, message.author)
|
||||
|
||||
if (channelOk != null && userOk != null) {
|
||||
await insertMessage(messageDB, maDB, message)
|
||||
}
|
||||
}
|
||||
|
||||
async function processMessageModify(
|
||||
messageDB: Repository<DBMessage>,
|
||||
mccDB: Repository<DBMessageContentChanges>,
|
||||
maDB: Repository<DBMessageAttachments>,
|
||||
newMessage: OmitPartialGroupDMChannel<Message<boolean>>) {
|
||||
await updateMessageContentHistory(messageDB, mccDB, maDB, newMessage)
|
||||
}
|
||||
|
||||
|
||||
async function processMessageDeleted(db: Repository<DBMessage>, deletedMessage: OmitPartialGroupDMChannel<Message<boolean>> | PartialMessage) {
|
||||
await markMessageDeleted(db, deletedMessage)
|
||||
}
|
||||
@ -1,24 +0,0 @@
|
||||
import { Client, Events } from "discord.js";
|
||||
import { insertRole, markRoleDeleted, updateRole } from "../discord/roles";
|
||||
import { insertGuild } from "../discord/guilds";
|
||||
import { Repository } from "typeorm";
|
||||
import { DBServer } from "../storage/entities/DBServer";
|
||||
import { DBRole } from "../storage/entities/DBRole";
|
||||
|
||||
export function setupRoleCapture(client: Client, guildDB: Repository<DBServer>, roleDB: Repository<DBRole>) {
|
||||
client.on(Events.GuildRoleCreate, async (role) => {
|
||||
const serverOk: DBServer | null = await insertGuild(guildDB, role.guild)
|
||||
|
||||
if (serverOk != null) {
|
||||
await insertRole(roleDB, role)
|
||||
}
|
||||
})
|
||||
|
||||
client.on(Events.GuildRoleUpdate, async (role) => {
|
||||
await updateRole(roleDB, role)
|
||||
})
|
||||
|
||||
client.on(Events.GuildRoleDelete, async (role) => {
|
||||
await markRoleDeleted(roleDB, role)
|
||||
})
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
import { Client, Events, VoiceState } from "discord.js"
|
||||
import { Repository } from "typeorm"
|
||||
import { DBCall } from "../storage/entities/DBCall"
|
||||
|
||||
export function setupVoice(client: Client, db: Repository<DBCall>) {
|
||||
client.on(Events.VoiceStateUpdate, (oldState: VoiceState, newState: VoiceState) => {
|
||||
if(oldState.channel == null && newState.channel != null) {
|
||||
// TODO Null Type Safety Risk?
|
||||
if (newState.member?.id == client.user?.id) {
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function processCallJoin(db: Repository<DBCall>, voiceState: VoiceState) {
|
||||
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
import * as commands from "./discord/commands"
|
||||
import * as guilds from "./discord/guilds"
|
||||
import * as channels from "./discord/channels"
|
||||
import * as users from "./discord/users"
|
||||
import * as roles from "./discord/roles"
|
||||
import { events } from "./events"
|
||||
|
||||
export const utilities = {
|
||||
commands,
|
||||
guilds,
|
||||
channels,
|
||||
users,
|
||||
events,
|
||||
roles
|
||||
}
|
||||
@ -1,34 +0,0 @@
|
||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { DBChannel } from "./DBChannel";
|
||||
import { DBCallTranscriptions } from "./DBCallTranscriptions";
|
||||
import { DBCallUsers } from "./DBCallUsers";
|
||||
|
||||
@Entity()
|
||||
export class DBCall {
|
||||
@PrimaryGeneratedColumn()
|
||||
call_id: number
|
||||
|
||||
@ManyToOne(() => DBChannel, (channel: DBChannel) => channel.calls)
|
||||
channel: DBChannel
|
||||
|
||||
@Column({type: "datetime"})
|
||||
call_start_time: Date
|
||||
|
||||
@Column({type: "datetime", nullable: true, default: null})
|
||||
call_end_time: Date | null
|
||||
|
||||
@Column({default: false})
|
||||
call_consolidated: boolean
|
||||
|
||||
@Column({default: false})
|
||||
call_transcribed: boolean
|
||||
|
||||
@Column({default: false})
|
||||
call_data_cleaned_up: boolean
|
||||
|
||||
@OneToMany(() => DBCallTranscriptions, (transcription: DBCallTranscriptions) => transcription.call, {nullable: true})
|
||||
transcriptions: DBCallTranscriptions[] | null
|
||||
|
||||
@OneToMany(() => DBCallUsers, (callUser: DBCallUsers) => callUser.call, {nullable: true})
|
||||
participants: DBCallUsers | null
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { DBCall } from "./DBCall";
|
||||
import { DBUser } from "./DBUser";
|
||||
|
||||
@Entity()
|
||||
export class DBCallTranscriptions {
|
||||
@PrimaryGeneratedColumn()
|
||||
transcription_id: number
|
||||
|
||||
@ManyToOne(() => DBCall, (call: DBCall) => call.transcriptions)
|
||||
call: DBCall
|
||||
|
||||
@ManyToOne(() => DBUser, (user: DBUser) => user.transcriptions)
|
||||
user: DBUser
|
||||
|
||||
@Column({type: "datetime"})
|
||||
speaking_start_time: Date
|
||||
|
||||
@Column()
|
||||
text: string
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { DBCall } from "./DBCall";
|
||||
import { DBUser } from "./DBUser";
|
||||
|
||||
@Entity()
|
||||
export class DBCallUsers {
|
||||
@PrimaryGeneratedColumn()
|
||||
call_users_id: number
|
||||
|
||||
@ManyToOne(() => DBCall, (call: DBCall) => call.participants)
|
||||
call: DBCall
|
||||
|
||||
@ManyToOne(() => DBUser, (user: DBUser) => user.call_history)
|
||||
user: DBUser
|
||||
|
||||
@Column({type: "datetime"})
|
||||
call_join_time: Date
|
||||
|
||||
@Column({type: "datetime", nullable: true, default: null})
|
||||
call_leave_time: Date | null
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryColumn } from "typeorm";
|
||||
import { DBServer } from "./DBServer";
|
||||
import { DBMessage } from "./DBMessage";
|
||||
import { DBCall } from "./DBCall";
|
||||
|
||||
@Entity()
|
||||
export class DBChannel {
|
||||
@PrimaryColumn({type: "bigint"})
|
||||
channel_snowflake: string
|
||||
|
||||
@ManyToOne(() => DBServer, (server: DBServer) => server.channels, {nullable: true})
|
||||
server: DBServer | null
|
||||
|
||||
@Column()
|
||||
channel_name: string
|
||||
|
||||
@Column()
|
||||
is_thread: boolean
|
||||
|
||||
@Column()
|
||||
is_dm: boolean
|
||||
|
||||
@Column()
|
||||
is_voice: boolean
|
||||
|
||||
@OneToMany(() => DBMessage, (message: DBMessage) => message.channel)
|
||||
messages: DBMessage[] | null
|
||||
|
||||
@OneToMany(() => DBCall, (call: DBCall) => call.channel)
|
||||
calls: DBCall[] | null
|
||||
}
|
||||
@ -1,36 +0,0 @@
|
||||
import { Column, Entity, ManyToOne, OneToMany, OneToOne, PrimaryColumn } from "typeorm";
|
||||
import { DBChannel } from "./DBChannel";
|
||||
import { DBUser } from "./DBUser";
|
||||
import { DBMessageContentChanges } from "./DBMessageContentChanges";
|
||||
import { DBMessageAttachments } from "./DBMessageAttachment";
|
||||
import { DBMessageRegexMatches } from "./DBMessageRegexMatches";
|
||||
|
||||
@Entity()
|
||||
export class DBMessage {
|
||||
@PrimaryColumn({type: "bigint"})
|
||||
message_snowflake: string
|
||||
|
||||
@ManyToOne(() => DBChannel, (channel: DBChannel) => channel.messages)
|
||||
channel: DBChannel
|
||||
|
||||
@ManyToOne(() => DBUser, (user: DBUser) => user.messages)
|
||||
user: DBUser
|
||||
|
||||
@Column({type: "longtext"})
|
||||
message_content: string
|
||||
|
||||
@Column({type: "datetime"})
|
||||
message_timestamp: Date
|
||||
|
||||
@Column({default: false})
|
||||
message_deleted: boolean
|
||||
|
||||
@OneToMany(() => DBMessageContentChanges, (mcc: DBMessageContentChanges) => mcc.message, {nullable: true})
|
||||
changes: DBMessageContentChanges[] | null
|
||||
|
||||
@OneToMany(() => DBMessageAttachments, (ma: DBMessageAttachments) => ma.attachment_snowflake, {nullable: true})
|
||||
attachments: DBMessageAttachments[] | null
|
||||
|
||||
@OneToOne(() => DBMessageRegexMatches, (mrm: DBMessageRegexMatches) => mrm.message, {nullable: true})
|
||||
violation_regex: DBMessageRegexMatches | null
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm";
|
||||
import { DBMessage } from "./DBMessage";
|
||||
|
||||
@Entity()
|
||||
export class DBMessageAttachments {
|
||||
@PrimaryColumn({"type": "bigint"})
|
||||
attachment_snowflake: string
|
||||
|
||||
@ManyToOne(() => DBMessage, (message: DBMessage) => message.attachments)
|
||||
message: DBMessage
|
||||
|
||||
@Column()
|
||||
attachment_name: string
|
||||
|
||||
@Column({nullable: true})
|
||||
attachment_description: string | null
|
||||
|
||||
@Column({type: "datetime"})
|
||||
attachment_timestamp: Date
|
||||
|
||||
@Column({nullable: true})
|
||||
attachment_mime_type: string | null
|
||||
|
||||
@Column()
|
||||
attachment_url: string
|
||||
|
||||
@Column({default: false})
|
||||
attachment_download: boolean
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { DBMessage } from "./DBMessage";
|
||||
|
||||
@Entity()
|
||||
export class DBMessageContentChanges {
|
||||
@PrimaryGeneratedColumn()
|
||||
message_change_id: number
|
||||
|
||||
@ManyToOne(() => DBMessage, (message: DBMessage) => message.changes)
|
||||
message: DBMessage
|
||||
|
||||
@Column({type: "datetime"})
|
||||
message_change_old_timestamp: Date
|
||||
|
||||
@Column({type: "longtext"})
|
||||
message_change_old_content: string
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
import { Column, Entity, ManyToOne, OneToMany, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { DBServer } from "./DBServer";
|
||||
import { DBMessageRegexMatches } from "./DBMessageRegexMatches";
|
||||
|
||||
@Entity()
|
||||
export class DBMessageRegex {
|
||||
@PrimaryGeneratedColumn()
|
||||
message_regex_id: number
|
||||
|
||||
@ManyToOne(() => DBServer, (server: DBServer) => server.regexes)
|
||||
server: DBServer
|
||||
|
||||
@Column()
|
||||
regex: string
|
||||
|
||||
@OneToMany(() => DBMessageRegexMatches, (mrm: DBMessageRegexMatches) => mrm.regex, {nullable: true})
|
||||
matches: DBMessageRegexMatches[] | null
|
||||
}
|
||||
@ -1,15 +0,0 @@
|
||||
import { Entity, ManyToOne, OneToOne, PrimaryGeneratedColumn } from "typeorm";
|
||||
import { DBMessage } from "./DBMessage";
|
||||
import { DBMessageRegex } from "./DBMessageRegex";
|
||||
|
||||
@Entity()
|
||||
export class DBMessageRegexMatches {
|
||||
@PrimaryGeneratedColumn()
|
||||
message_regex_match_id: number
|
||||
|
||||
@OneToOne(() => DBMessage, (message: DBMessage) => message.violation_regex)
|
||||
message: DBMessage
|
||||
|
||||
@ManyToOne(() => DBMessageRegex, (regex: DBMessageRegex) => regex.matches)
|
||||
regex: DBMessageRegex
|
||||
}
|
||||
@ -1,17 +0,0 @@
|
||||
import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm";
|
||||
import { DBServer } from "./DBServer";
|
||||
|
||||
@Entity()
|
||||
export class DBRole {
|
||||
@PrimaryColumn({type: "bigint"})
|
||||
role_snowflake: string
|
||||
|
||||
@ManyToOne(() => DBServer, (server: DBServer) => server.roles)
|
||||
server: DBServer
|
||||
|
||||
@Column()
|
||||
role_name: string
|
||||
|
||||
@Column()
|
||||
is_deleted: boolean
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
import { Column, Entity, OneToMany, PrimaryColumn } from "typeorm";
|
||||
import { DBChannel } from "./DBChannel";
|
||||
import { DBRole } from "./DBRole";
|
||||
import { DBMessageRegex } from "./DBMessageRegex";
|
||||
|
||||
@Entity()
|
||||
export class DBServer {
|
||||
@PrimaryColumn({type: "bigint"})
|
||||
server_snowflake: string
|
||||
|
||||
@Column()
|
||||
server_name: string
|
||||
|
||||
@Column()
|
||||
server_description: string
|
||||
|
||||
@OneToMany(() => DBChannel, (channel: DBChannel) => channel.server)
|
||||
channels: DBChannel[]
|
||||
|
||||
@OneToMany(() => DBRole, (role: DBRole) => role.server)
|
||||
roles: DBRole[]
|
||||
|
||||
@OneToMany(() => DBMessageRegex, (regex: DBMessageRegex) => regex.server, {nullable: true})
|
||||
regexes: DBMessageRegex[] | null
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
import { Column, Entity, OneToMany, PrimaryColumn } from "typeorm";
|
||||
import { DBMessage } from "./DBMessage";
|
||||
import { DBCallTranscriptions } from "./DBCallTranscriptions";
|
||||
import { DBCallUsers } from "./DBCallUsers";
|
||||
|
||||
@Entity()
|
||||
export class DBUser {
|
||||
@PrimaryColumn({type: "bigint"})
|
||||
user_snowflake: string
|
||||
|
||||
@Column()
|
||||
user_name: string
|
||||
|
||||
@Column()
|
||||
user_displayname: string
|
||||
|
||||
@OneToMany(() => DBMessage, (message: DBMessage) => message.user)
|
||||
messages: DBMessage[]
|
||||
|
||||
@OneToMany(() => DBCallTranscriptions, (transcription: DBCallTranscriptions) => transcription.user, {nullable: true})
|
||||
transcriptions: DBCallTranscriptions[] | null
|
||||
|
||||
@OneToMany(() => DBCallUsers, (call_user: DBCallUsers) => call_user.user)
|
||||
call_history: DBCallUsers[] | null
|
||||
}
|
||||
@ -1,22 +0,0 @@
|
||||
export function timeShorthandToSeconds(shorthand: string) : number {
|
||||
let totalSeconds: number = 0
|
||||
|
||||
shorthand.split(" ").forEach((value) => {
|
||||
console.log(value)
|
||||
const unit: string = value.substring(value.length - 1, value.length)
|
||||
|
||||
if (unit == "d" || unit == "D") {
|
||||
totalSeconds += Number.parseInt(value.substring(0, value.length - 1)) * 86400
|
||||
} else if (unit == "h" || unit == "H") {
|
||||
totalSeconds += Number.parseInt(value.substring(0, value.length - 1)) * 3600
|
||||
} else if (unit == "m" || unit == "M") {
|
||||
totalSeconds += Number.parseInt(value.substring(0, value.length - 1)) * 60
|
||||
} else if (unit == "s" || unit == "S") {
|
||||
totalSeconds += Number.parseInt(value.substring(0, value.length - 1))
|
||||
} else {
|
||||
console.log(`An invalid shorthand directive was sent ${value}`)
|
||||
}
|
||||
})
|
||||
|
||||
return totalSeconds
|
||||
}
|
||||
25
tools/profanity_filter/.vscode/launch.json
vendored
25
tools/profanity_filter/.vscode/launch.json
vendored
@ -1,25 +0,0 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "java",
|
||||
"name": "Current File",
|
||||
"request": "launch",
|
||||
"mainClass": "${file}"
|
||||
},
|
||||
{
|
||||
"type": "java",
|
||||
"name": "RegexGenerator",
|
||||
"request": "launch",
|
||||
"mainClass": "RegexGenerator",
|
||||
"projectName": "ProfanityFilter_ceb3bd68",
|
||||
"args": [
|
||||
"ass",
|
||||
"shit"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
7
tools/profanity_filter/.vscode/settings.json
vendored
7
tools/profanity_filter/.vscode/settings.json
vendored
@ -1,7 +0,0 @@
|
||||
{
|
||||
"java.project.sourcePaths": ["src"],
|
||||
"java.project.outputPath": "bin",
|
||||
"java.project.referencedLibraries": [
|
||||
"lib/**/*.jar"
|
||||
]
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
## Getting Started
|
||||
|
||||
Welcome to the VS Code Java world. Here is a guideline to help you get started to write Java code in Visual Studio Code.
|
||||
|
||||
## Folder Structure
|
||||
|
||||
The workspace contains two folders by default, where:
|
||||
|
||||
- `src`: the folder to maintain sources
|
||||
- `lib`: the folder to maintain dependencies
|
||||
|
||||
Meanwhile, the compiled output files will be generated in the `bin` folder by default.
|
||||
|
||||
> If you want to customize the folder structure, open `.vscode/settings.json` and update the related settings there.
|
||||
|
||||
## Dependency Management
|
||||
|
||||
The `JAVA PROJECTS` view allows you to manage your dependencies. More details can be found [here](https://github.com/microsoft/vscode-java-dependency#manage-dependencies).
|
||||
Binary file not shown.
Binary file not shown.
@ -1,34 +0,0 @@
|
||||
package generators;
|
||||
|
||||
public class ExceptionGenerator {
|
||||
public static void main(String[] args) {
|
||||
//position 0 should be the base word, the rest should be exceptions
|
||||
String base = args[0].toLowerCase();
|
||||
|
||||
String prefixes = "(?<!";
|
||||
String regex = RegexGenerator.regexGenerator(base);
|
||||
String suffixes = "(?!";
|
||||
|
||||
for (int i = 1; i < args.length; i++) {
|
||||
args[i] = "$" + args[i] + "$";
|
||||
|
||||
String prefix = args[i].toLowerCase().split(base)[0];
|
||||
|
||||
String suffix = args[i].toLowerCase().split(base)[1];
|
||||
|
||||
if (!prefix.equals("$")) {
|
||||
prefixes += RegexGenerator.regexGenerator(prefix) + "|";
|
||||
}
|
||||
|
||||
if (!suffix.equals("$")) {
|
||||
suffixes += RegexGenerator.regexGenerator(suffix) + "|";
|
||||
}
|
||||
}
|
||||
|
||||
prefixes = prefixes.substring(0, prefixes.length() - 1);
|
||||
suffixes = suffixes.substring(0, suffixes.length() - 1);
|
||||
|
||||
|
||||
System.out.println(prefixes + ")" + regex + suffixes + ")");
|
||||
}
|
||||
}
|
||||
@ -1,77 +0,0 @@
|
||||
package generators;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class RegexGenerator {
|
||||
private static Map<Character, String> dictionary;
|
||||
|
||||
static {
|
||||
Map<Character, String> map = new HashMap<>();
|
||||
map.put('a', "[a@*]");
|
||||
map.put('b', "([b38]|\\\\|3)");
|
||||
map.put('c', "[c(k]");
|
||||
map.put('d', "[d]");
|
||||
map.put('e', "[e3*]");
|
||||
map.put('f', "([f]|ph)");
|
||||
map.put('g', "[g]");
|
||||
map.put('h', "[h]");
|
||||
map.put('i', "[il1!*]");
|
||||
map.put('j', "[j]");
|
||||
map.put('k', "[kc]");
|
||||
map.put('l', "[li]");
|
||||
map.put('m', "([m]|rn)");
|
||||
map.put('n', "[n]");
|
||||
map.put('o', "[o0pq*]");
|
||||
map.put('p', "[p]");
|
||||
map.put('q', "[q]");
|
||||
map.put('r', "[r]");
|
||||
map.put('s', "[sz5$]");
|
||||
map.put('t', "[t7+]");
|
||||
map.put('u', "[uv*]");
|
||||
map.put('v', "[v]");
|
||||
map.put('w', "([w]|vv)");
|
||||
map.put('x', "[x]");
|
||||
map.put('y', "[y]");
|
||||
map.put('z', "[z]");
|
||||
map.put(' ', "");
|
||||
map.put('1', "([1]|one)");
|
||||
map.put('2', "([2]|two)");
|
||||
map.put('3', "([3]|three)");
|
||||
map.put('4', "([4]|four)");
|
||||
map.put('5', "([5]|five)");
|
||||
map.put('6', "([6]|six)");
|
||||
map.put('7', "([7]|seven)");
|
||||
map.put('8', "([8]|eight)");
|
||||
map.put('9', "([9]|nine)");
|
||||
map.put('0', "([0]|zero)");
|
||||
dictionary = Collections.unmodifiableMap(map);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
for (String s : args) {
|
||||
String regex = "(\\\\b|^)";
|
||||
|
||||
for (char c : s.toLowerCase().toCharArray()) {
|
||||
regex += dictionary.get(c) + "+[\\\\s\\\\n\\\\W]*";
|
||||
}
|
||||
|
||||
regex += "(\\\\b|$)";
|
||||
|
||||
System.out.println(regex);
|
||||
}
|
||||
}
|
||||
|
||||
public static String regexGenerator(String str) {
|
||||
String regex = "(\\\\b|^)";
|
||||
|
||||
for (char c : str.toLowerCase().toCharArray()) {
|
||||
regex += dictionary.get(c) + "+[\\\\s\\\\n\\\\W]*";
|
||||
}
|
||||
|
||||
regex += "(\\\\b|$)";
|
||||
|
||||
return regex;
|
||||
}
|
||||
}
|
||||
@ -1,20 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"lib": ["es5", "es6"],
|
||||
"target": "es6",
|
||||
"module": "commonjs",
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"removeComments": true,
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"skipLibCheck": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"strictPropertyInitialization": false,
|
||||
"sourceMap": true
|
||||
}
|
||||
}
|
||||
122
utilities/googlecalendar.js
Normal file
122
utilities/googlecalendar.js
Normal file
@ -0,0 +1,122 @@
|
||||
const { google } = require('googleapis');
|
||||
const { googlePrivateKey, googleClientEmail, googleProjectNumber } = require('../config.json');
|
||||
const { stdout } = require('node:process');
|
||||
const SCOPES = ['https://www.googleapis.com/auth/calendar'];
|
||||
|
||||
async function getCalendarReference() {
|
||||
const jwtClient = new google.auth.JWT(
|
||||
googleClientEmail,
|
||||
'../keyfile.json',
|
||||
googlePrivateKey,
|
||||
SCOPES,
|
||||
);
|
||||
|
||||
return new google.calendar({
|
||||
version: 'v3',
|
||||
project: googleProjectNumber,
|
||||
auth: jwtClient,
|
||||
});
|
||||
}
|
||||
|
||||
async function doesCalendarExist(calendarName) {
|
||||
const calendarReference = await getCalendarReference();
|
||||
const listResults = await calendarReference.calendarList.list({});
|
||||
|
||||
for (const item of listResults.data.items) {
|
||||
if (item.summary === calendarName) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getListOfCalendars(options, callback) {
|
||||
const calendarReference = await getCalendarReference();
|
||||
calendarReference.calendarList.list(options, async (err, res) => {
|
||||
if (err) {
|
||||
callback(false, 'Failed to retrieve the list of calendars\nAsk Bradley to check Breadbot console');
|
||||
stdout.write('[ERROR]:');
|
||||
console.log(err.errors);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(true, 'Calendar List', res.data.items);
|
||||
});
|
||||
}
|
||||
|
||||
async function addCalendar(calendarName, timezone, callback) {
|
||||
const calendarReference = await getCalendarReference();
|
||||
calendarReference.calendars.insert({
|
||||
resource: {
|
||||
summary: calendarName,
|
||||
timeZone: timezone,
|
||||
},
|
||||
},
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
async (err, res) => {
|
||||
if (err) {
|
||||
callback(false, 'Failed to create new calendar ' + calendarName + '\nAsk Bradley to check Breadbat console', err);
|
||||
stdout.write('[ERROR]: ');
|
||||
console.log(err.errors);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(true, 'Successfully created new calendar ' + calendarName, null);
|
||||
});
|
||||
}
|
||||
|
||||
async function deleteCalendar(calendarName, callback) {
|
||||
const exists = await doesCalendarExist(calendarName);
|
||||
|
||||
if (exists) {
|
||||
const calendarReference = await getCalendarReference();
|
||||
calendarReference.calendars.delete({
|
||||
calendarId: exists.id,
|
||||
},
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
async (err, res) => {
|
||||
if (err) {
|
||||
callback(false, 'Failed to delete ' + calendarName + '\nAsk Bradley to check Breadbot console', err);
|
||||
stdout.write('[ERROR]: ');
|
||||
console.log(err);
|
||||
return;
|
||||
}
|
||||
|
||||
callback(true, 'Successfully deleted ' + calendarName, null);
|
||||
});
|
||||
}
|
||||
else {
|
||||
callback(false, 'The calendar name specified doesn\'t exist', null);
|
||||
}
|
||||
}
|
||||
|
||||
async function addEvent(calendarName, eventName, location, description, startDate, startTime, endDate, endTime) {
|
||||
const exists = await doesCalendarExist(calendarName);
|
||||
|
||||
if (exists) {
|
||||
const calendarReference = await getCalendarReference();
|
||||
calendarReference.events.insert({
|
||||
calendarId: exists.id,
|
||||
resource: {
|
||||
summary: eventName,
|
||||
location: location,
|
||||
description: description,
|
||||
start: {
|
||||
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
else {
|
||||
callback(false, 'The calendar name specified doesn\'t exist', null);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getCalendarReference,
|
||||
getListOfCalendars,
|
||||
doesCalendarExist,
|
||||
deleteCalendar,
|
||||
addCalendar,
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user