mirror of
https://github.com/Hessenuk/DiscordTickets.git
synced 2024-11-05 04:13:08 +02:00
v2 rewrite with discord.js@v12 support. Now with SQLite or MySQL storage.
Code is at least 80% less bad.
This commit is contained in:
parent
c77f38f209
commit
8431b2d241
37
.eslintrc.js
Normal file
37
.eslintrc.js
Normal file
@ -0,0 +1,37 @@
|
||||
module.exports = {
|
||||
'env': {
|
||||
'commonjs': true,
|
||||
'es6': true,
|
||||
'browser': false,
|
||||
'node': true
|
||||
},
|
||||
'extends': 'eslint:recommended',
|
||||
'globals': {
|
||||
'Atomics': 'readonly',
|
||||
'SharedArrayBuffer': 'readonly'
|
||||
},
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 2018
|
||||
},
|
||||
'rules': {
|
||||
'indent': [
|
||||
'warn',
|
||||
'tab'
|
||||
],
|
||||
'linebreak-style': [
|
||||
'off',
|
||||
'windows'
|
||||
],
|
||||
'quotes': [
|
||||
'warn',
|
||||
'single'
|
||||
],
|
||||
'semi': [
|
||||
'error',
|
||||
'always'
|
||||
],
|
||||
'no-control-regex': [
|
||||
'off'
|
||||
]
|
||||
}
|
||||
};
|
27
.github/CONTRIBUTING.md
vendored
Normal file
27
.github/CONTRIBUTING.md
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
# Contributing
|
||||
|
||||
## Submitting issues
|
||||
|
||||
### Submitting a bug report
|
||||
|
||||
To submit a bug report, please use the "Bug report" template when creating a [new issue](https://github.com/eartharoid/DiscordTickets/issues/new/choose). Describe the bug in as much detail as possible, including how to reproduce the problem, using screenshots or code snippets if possible. Check that someone hasn't already filed an issue before creating another, you can comment on it if you want.
|
||||
|
||||
### Submitting a feature request
|
||||
|
||||
To submit a new feature request, please use the "Feature request" template when creating a [new issue](https://github.com/eartharoid/DiscordTickets/issues/new/choose).
|
||||
|
||||
### Submitting other issues
|
||||
|
||||
For issues not related to feature requests or bugs, you can [create a blank issue](https://github.com/eartharoid/DiscordTickets/issues/new). Please give us as much information as possible. If you just want to talk, you can join the [Discord server](https://github.com/eartharoid/DiscordTickets#support).
|
||||
|
||||
## Submitting a pull request
|
||||
|
||||
To contribute code to this project, create a new [pull request](https://github.com/eartharoid/DiscordTickets/pulls). For anything other than patches (bug fixes, documentation or minor code changes that have no affect on usage), such as a new feature, please create a [new issue](https://github.com/eartharoid/DiscordTickets/issues/new/choose) first, describing what you intend to change and why. Please ensure you update the documentation if needed.
|
||||
|
||||
When contributing, you should follow the same code style already used throughout, to ensure code is consistent.
|
||||
|
||||
1. Use single quote marks (`'`) when possible
|
||||
2. Template literals are preferred
|
||||
3. Commas should always have a space after them
|
||||
4. Use tabs, not spaces, and always indent
|
||||
5. Use arrow functions
|
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
ko_fi: eartharoid
|
19
.github/ISSUE_TEMPLATE/bug_report.md
vendored
19
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,6 +1,6 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
about: Report an issue or bug
|
||||
title: ''
|
||||
labels: ''
|
||||
assignees: ''
|
||||
@ -11,11 +11,7 @@ assignees: ''
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
Steps to reproduce the behaviour:
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
@ -23,16 +19,5 @@ A clear and concise description of what you expected to happen.
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
|
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
2
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -8,7 +8,7 @@ assignees: ''
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
A clear and concise description of what the problem is.
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
22
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
#### Information
|
||||
|
||||
> Check one
|
||||
|
||||
- [ ] This includes major changes (new features, version required)
|
||||
- [ ] This includes minor changes (doesn't change usage)
|
||||
- [ ] This includes patches (bug fixes, documentation changes etc)
|
||||
|
||||
#### Is this related to an issue?
|
||||
|
||||
> Reference any issues here
|
||||
|
||||
#### Changes made
|
||||
|
||||
> Describe your changes
|
||||
|
||||
#### Confirmations
|
||||
|
||||
> Check all that apply
|
||||
|
||||
- [ ] This uses consistent code style
|
||||
- [ ] This is tested and works
|
9
.gitignore
vendored
9
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
package-lock.json
|
||||
|
||||
logs/
|
||||
*.log
|
||||
node_modules/
|
||||
config.json
|
||||
user/.env
|
||||
user/storage.db
|
||||
user/transcripts/*.txt
|
||||
user/transcripts/*.log
|
||||
user/transcripts/*.json
|
@ -1,5 +1,6 @@
|
||||
[![Run on Repl.it](https://repl.it/badge/github/eartharoid/DiscordTickets)](https://repl.it/github/eartharoid/DiscordTickets)
|
||||
# DiscordTickets
|
||||
## ENABLE UPDATER
|
||||
A simple (ish) Discord™️ bot to manage support tickets to allow you and your team to provide better and quicker assistance to your community members.
|
||||
|
||||
**Go to the [wiki](https://github.com/Eartharoid/DiscordTickets/wiki) for more information**
|
||||
@ -13,6 +14,3 @@ A simple (ish) Discord™️ bot to manage support tickets to allow you and your
|
||||
|
||||
The help menu provides an example for each command, along with usage (parameters info) and alias info.
|
||||
An example command / template is provided if you want to create your own command (such as `!website` for example)
|
||||
|
||||
### Credits
|
||||
- [ohlookitsderpy / leeks.js](https://github.com/ohlookitsderpy/leeks.js)
|
||||
|
@ -1,75 +0,0 @@
|
||||
const Discord = require('discord.js');
|
||||
const config = require('../config.json');
|
||||
const log = require(`leekslazylogger`);
|
||||
|
||||
module.exports = {
|
||||
name: 'add',
|
||||
description: 'Add a member to a ticket channel',
|
||||
usage: '<@member>',
|
||||
aliases: ['adduser'],
|
||||
example: 'add @exampleUser',
|
||||
args: true,
|
||||
cooldown: config.cooldown,
|
||||
guildOnly: true,
|
||||
execute(message, args) {
|
||||
const client = message.client;
|
||||
// command starts here
|
||||
message.delete();
|
||||
if (!message.channel.name.startsWith('ticket-')) {
|
||||
if (config.useEmbeds) {
|
||||
const notTicket = new Discord.RichEmbed()
|
||||
.setColor("#E74C3C")
|
||||
.setDescription(`:x: **This command can only be used within a ticket channel**`);
|
||||
return message.channel.send(notTicket);
|
||||
} else {
|
||||
return message.channel.send(`:x: **This command can only be used within a ticket channel**`);
|
||||
}
|
||||
}
|
||||
|
||||
let user = message.guild.member(message.mentions.users.first() || message.guild.members.get(args[0]));
|
||||
if (!user) {
|
||||
if (config.useEmbeds) {
|
||||
const err1 = new Discord.RichEmbed()
|
||||
.setColor("#E74C3C")
|
||||
.setDescription(`:x: **Unknown user.** Please mention a valid user.`);
|
||||
return message.channel.send(err1);
|
||||
} else {
|
||||
return message.channel.send(`:x: **Unknown user.** Please mention a valid user.`);
|
||||
}
|
||||
}
|
||||
try {
|
||||
message.channel.overwritePermissions(user, {
|
||||
SEND_MESSAGES: true,
|
||||
READ_MESSAGES: true,
|
||||
ATTACH_FILES: true
|
||||
});
|
||||
if(config.useEmbeds) {
|
||||
const added = new Discord.RichEmbed()
|
||||
.setColor(config.colour)
|
||||
.setDescription(`${user} has been added.`);
|
||||
message.channel.send(added);
|
||||
} else {
|
||||
message.channel.send(`${user} has been added.`);
|
||||
}
|
||||
// log
|
||||
if (config.useEmbeds) {
|
||||
const embed = new Discord.RichEmbed()
|
||||
.setAuthor(`${client.user.username} / Ticket Log`, client.user.avatarURL)
|
||||
.setTitle("User Added")
|
||||
.setColor(config.colour)
|
||||
.addField("Username", user, true)
|
||||
.addField("Added by", message.author, true)
|
||||
.addField("Channel", message.channel, true)
|
||||
.setFooter(`DiscordTickets`)
|
||||
.setTimestamp();
|
||||
client.channels.get(config.logChannel).send(embed);
|
||||
} else {
|
||||
client.channels.get(config.logChannel).send(`User added to a ticket by **${message.author.tag} (${message.author.id})**`);
|
||||
}
|
||||
log.info(`${message.author.tag} added a user to a ticket (#${message.channel})`)
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
// command ends here
|
||||
},
|
||||
};
|
@ -1,51 +0,0 @@
|
||||
const Discord = require('discord.js');
|
||||
const config = require('../config.json');
|
||||
const log = require(`leekslazylogger`);
|
||||
|
||||
module.exports = {
|
||||
name: 'close',
|
||||
description: 'Close a ticket',
|
||||
usage: '',
|
||||
aliases: ['none'],
|
||||
example: '',
|
||||
args: false,
|
||||
cooldown: config.cooldown,
|
||||
guildOnly: true,
|
||||
execute(message, args) {
|
||||
const client = message.client;
|
||||
// command starts here
|
||||
message.delete();
|
||||
if(!message.channel.name.startsWith('ticket-')) { // // !message.channel.name.length() == 15 &&
|
||||
if(config.useEmbeds) {
|
||||
const notTicket = new Discord.RichEmbed()
|
||||
.setColor("#E74C3C")
|
||||
.setDescription(`:x: **This command can only be used within a ticket channel**`);
|
||||
return message.channel.send(notTicket);
|
||||
} else {
|
||||
return message.channel.send(`:x: **This command can only be used within a ticket channel**`);
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
message.channel.delete();
|
||||
// log
|
||||
if (config.useEmbeds) {
|
||||
const embed = new Discord.RichEmbed()
|
||||
.setAuthor(`${client.user.username} / Ticket Log`, client.user.avatarURL)
|
||||
.setTitle("Ticket Closed")
|
||||
.setColor(config.colour)
|
||||
.addField("Username", message.author, true)
|
||||
.addField("Channel", message.channel.name, true)
|
||||
.setFooter(`DiscordTickets`)
|
||||
.setTimestamp();
|
||||
client.channels.get(config.logChannel).send(embed);
|
||||
} else {
|
||||
client.channels.get(config.logChannel).send(`Ticket closed by **${message.author.tag} (${message.author.id})**`);
|
||||
}
|
||||
log.info(`${message.author.tag} closed a ticket (#${message.channel.name})`);
|
||||
} catch (error) {
|
||||
log.error(log.colour.red(error));
|
||||
}
|
||||
}
|
||||
// command ends here
|
||||
},
|
||||
};
|
@ -1,23 +0,0 @@
|
||||
const Discord = require('discord.js');
|
||||
const config = require('../config.json');
|
||||
const log = require(`leekslazylogger`);
|
||||
module.exports = {
|
||||
name: 'example-command',
|
||||
description: 'An example command',
|
||||
usage: '[args]',
|
||||
aliases: ['none'],
|
||||
example: 'example-command',
|
||||
args: false,
|
||||
cooldown: config.cooldown,
|
||||
guildOnly: true,
|
||||
execute(message, args) {
|
||||
const client = message.client;
|
||||
// command starts here
|
||||
message.delete();
|
||||
|
||||
|
||||
|
||||
|
||||
// command ends here
|
||||
},
|
||||
};
|
108
commands/help.js
108
commands/help.js
@ -1,108 +0,0 @@
|
||||
const Discord = require('discord.js');
|
||||
const config = require('../config.json');
|
||||
const log = require(`leekslazylogger`);
|
||||
|
||||
module.exports = {
|
||||
name: 'help',
|
||||
description: 'Displays help menu',
|
||||
usage: '[command]',
|
||||
aliases: ['command', 'commands'],
|
||||
example: 'help new',
|
||||
args: false,
|
||||
cooldown: config.cooldown,
|
||||
guildOnly: true,
|
||||
execute(message, args) {
|
||||
// command starts here
|
||||
message.delete();
|
||||
const data = [];
|
||||
const { commands } = message.client;
|
||||
|
||||
if (config.useEmbeds) {
|
||||
if (!args.length) {
|
||||
data.push('__**Commands**__');
|
||||
data.push(commands.map(command => `**${config.prefix}${command.name}** : \`${command.description}\``).join('\n'));
|
||||
data.push(`\nType \`${config.prefix}help [command]\` for more information about a specific command.`);
|
||||
|
||||
const embed = new Discord.RichEmbed()
|
||||
.setTitle("Commands")
|
||||
.setColor(config.colour)
|
||||
.setDescription(`\nType \`${config.prefix}help [command]\` for more information about a specific command.`)
|
||||
// .addField("...", `...`, true)
|
||||
// .addField("...", `...`, true)
|
||||
.setFooter(`DiscordTickets by Eartharoid`);
|
||||
|
||||
let cmds = [];
|
||||
cmds.push(commands.map(command => embed.addField(`${config.prefix}${command.name}`, `\`${command.description}\``)));
|
||||
message.channel.send(embed)
|
||||
.then(() => {
|
||||
if (message.channel.type === 'dm') return;
|
||||
// message.channel.send(`A list of commands has been sent to you.`);
|
||||
})
|
||||
.catch(() => {
|
||||
// console.error(`Could not send help DM to ${message.author.tag}.\n`, error);
|
||||
log.warn(`Could not DM help menu to ${message.author.tag}, sending to server channel instead`);
|
||||
message.channel.send(`:x: **Sorry!** There was an error whilst sending the help menu via DMs.`);
|
||||
message.channel.send(data, { split: true });
|
||||
});
|
||||
} else {
|
||||
const name = args[0].toLowerCase();
|
||||
const command = commands.get(name) || commands.find(c => c.aliases && c.aliases.includes(name));
|
||||
|
||||
if (!command) {
|
||||
const notCmd = new Discord.RichEmbed()
|
||||
.setColor("#E74C3C")
|
||||
.setDescription(`:x: **Invalid command name** (\`${config.prefix}help\`)`);
|
||||
return message.channel.send(notCmd);
|
||||
}
|
||||
|
||||
const cmd = new Discord.RichEmbed()
|
||||
.setColor(config.colour)
|
||||
.addField(`Command`,`\`${command.name}\``, true)
|
||||
.setFooter(`DiscordTickets by Eartharoid`);
|
||||
|
||||
if (command.aliases) cmd.addField("Aliases", `\`${command.aliases.join(', ')}\``, true);
|
||||
if (command.description) cmd.addField("Description", `\`${command.description}\``, false);
|
||||
if (command.usage) cmd.addField("Usage", `\`${config.prefix}${command.name} ${command.usage}\``, false)
|
||||
if (command.example) cmd.addField("Example", `\`${config.prefix}${command.example}\``, false);
|
||||
message.channel.send(cmd);
|
||||
}
|
||||
|
||||
} else {
|
||||
// message.channel.send(`**Prefix =** \`${config.prefix}\`\n**Bot Version =** \`${version}\``)
|
||||
if (!args.length) {
|
||||
data.push('__**Commands**__');
|
||||
data.push(commands.map(command => `**${config.prefix}${command.name}** : \`${command.description}\``).join('\n'));
|
||||
data.push(`\nType \`${config.prefix}help [command]\` for more information about a specific command.`);
|
||||
|
||||
return message.author.send(data, { split: true })
|
||||
.then(() => {
|
||||
if (message.channel.type === 'dm') return;
|
||||
// message.channel.send(`A list of commands has been sent to you.`);
|
||||
})
|
||||
.catch(error => {
|
||||
// console.error(`Could not send help DM to ${message.author.tag}.\n`, error);
|
||||
log.warn(`Could not DM help menu to ${message.author.tag}, sending to server channel instead`);
|
||||
message.channel.send(`:x: **Sorry!** There was an error whilst sending the help menu via DMs.`)
|
||||
message.channel.send(data, { split: true })
|
||||
});
|
||||
}
|
||||
|
||||
const name = args[0].toLowerCase();
|
||||
const command = commands.get(name) || commands.find(c => c.aliases && c.aliases.includes(name));
|
||||
|
||||
if (!command) return message.reply(':x: **Invalid command**');
|
||||
|
||||
data.push(`**Command:** \`${command.name}\``);
|
||||
|
||||
if (command.aliases) data.push(`**Aliases:** \`${command.aliases.join(', ')}\``);
|
||||
if (command.description) data.push(`**Description:** \`${command.description}\``);
|
||||
if (command.usage) data.push(`**Usage:** \`${config.prefix}${command.name} ${command.usage}\``);
|
||||
if (command.example) data.push(`**Example:** \`${command.example}\``);
|
||||
|
||||
data.push(`**Cooldown:** \`${command.cooldown || 3} second(s)\``);
|
||||
|
||||
message.channel.send(data, { split: true });
|
||||
}
|
||||
// command ends here
|
||||
},
|
||||
};
|
114
commands/new.js
114
commands/new.js
@ -1,114 +0,0 @@
|
||||
const Discord = require('discord.js');
|
||||
const config = require('../config.json');
|
||||
const log = require(`leekslazylogger`);
|
||||
// const randomString = require('random-string');
|
||||
|
||||
module.exports = {
|
||||
name: 'new',
|
||||
description: 'Create a new ticket',
|
||||
usage: '<brief description>',
|
||||
aliases: ['ticket'],
|
||||
example: 'new I found an error',
|
||||
args: true,
|
||||
cooldown: config.cooldown,
|
||||
guildOnly: true,
|
||||
execute(message, args) {
|
||||
const client = message.client;
|
||||
// command starts here
|
||||
message.delete();
|
||||
let topic = args.join(" ");
|
||||
|
||||
// let num = randomString({
|
||||
// length: 4,
|
||||
// numeric: true,
|
||||
// letters: false,
|
||||
// special: false,
|
||||
// });
|
||||
let id = message.author.id.toString().substr(0, 4) + message.author.discriminator;
|
||||
let chan = `ticket-${id}`;
|
||||
|
||||
if (message.guild.channels.find(channel => channel.name === chan)) {
|
||||
if (config.useEmbeds) {
|
||||
const err1 = new Discord.RichEmbed()
|
||||
.setColor("#E74C3C")
|
||||
.setDescription(`:x: You already have an open ticket.`);
|
||||
return message.channel.send(err1);
|
||||
} else {
|
||||
message.channel.send(`:x: You already have an open ticket.`);
|
||||
}
|
||||
};
|
||||
|
||||
message.guild.createChannel(`ticket-${id}`, {
|
||||
type: 'text'
|
||||
}).then(async c => {
|
||||
c.setParent(config.ticketsCat);
|
||||
// let supportRole = message.guild.roles.find(`id`, config.supportRole)
|
||||
let supportRole = message.guild.roles.get(config.supportRole)
|
||||
if (!supportRole) return message.channel.send(":x: No **Support Team** role found.");
|
||||
|
||||
c.overwritePermissions(message.guild.defaultRole, {
|
||||
VIEW_CHANNEL: false,
|
||||
SEND_MESSAGES: false
|
||||
});
|
||||
c.overwritePermissions(message.member, {
|
||||
VIEW_CHANNEL: true,
|
||||
SEND_MESSAGES: true
|
||||
});
|
||||
c.overwritePermissions(supportRole, {
|
||||
VIEW_CHANNEL: true,
|
||||
SEND_MESSAGES: true
|
||||
});
|
||||
c.setTopic(`${message.author} | ${topic}`);
|
||||
if (config.tagHereOnly) {
|
||||
await c.send(`@here, a user has created a new ticket.\n`);
|
||||
} else {
|
||||
await c.send(`<@&${config.supportRole}>, a user has created a new ticket.\n`);
|
||||
};
|
||||
|
||||
if (config.ticketImage) {
|
||||
await c.send(`__**Here's your ticket channel, ${message.author}**__`, {
|
||||
files: [`./image.png`]
|
||||
});
|
||||
} else {
|
||||
await c.send(`__**Here's your ticket channel, ${message.author}**__`);
|
||||
}
|
||||
|
||||
const created = new Discord.RichEmbed()
|
||||
.setColor(config.colour)
|
||||
.setDescription(`Your ticket (${c}) has been created.\nPlease read the information sent and follow any instructions given.`)
|
||||
.setTimestamp();
|
||||
const welcome = new Discord.RichEmbed()
|
||||
.setColor(config.colour)
|
||||
.setDescription(`**Ticket topic:** \`${topic}\`\n\n${config.ticketText}`);
|
||||
|
||||
if (config.useEmbeds) {
|
||||
message.channel.send(created);
|
||||
let w = await c.send(welcome);
|
||||
await w.pin();
|
||||
// c.fetchMessage(c.lastMessageID).delete()
|
||||
} else {
|
||||
message.channel.send(`Your ticket (${c}) has been created.\nPlease read the information sent and follow any instructions given.`);
|
||||
let w = await c.send(`**Ticket topic:** \`${topic}\`\n\n${config.ticketText}`);
|
||||
await w.pin();
|
||||
// c.fetchMessage(c.lastMessageID).delete()
|
||||
}
|
||||
// log
|
||||
if (config.useEmbeds) {
|
||||
const embed = new Discord.RichEmbed()
|
||||
.setAuthor(`${client.user.username} / Ticket Log`, client.user.avatarURL)
|
||||
.setTitle("New Ticket")
|
||||
.setColor(config.colour)
|
||||
.setDescription(`\`${topic}\``)
|
||||
.addField("Username", message.author, true)
|
||||
.addField("Channel", c, true)
|
||||
.setFooter(`DiscordTickets`)
|
||||
.setTimestamp();
|
||||
client.channels.get(config.logChannel).send(embed);
|
||||
} else {
|
||||
client.channels.get(config.logChannel).send(`New ticket created by **${message.author.tag} (${message.author.id})**`);
|
||||
}
|
||||
log.info(`${message.author.tag} created a new ticket (#ticket-${id})`);
|
||||
});
|
||||
// command ends here
|
||||
},
|
||||
};
|
@ -1,24 +0,0 @@
|
||||
const Discord = require('discord.js');
|
||||
const config = require('../config.json');
|
||||
|
||||
module.exports = {
|
||||
name: 'ping',
|
||||
description: 'Calculate latency',
|
||||
usage: '',
|
||||
aliases: ['none'],
|
||||
example: '',
|
||||
args: false,
|
||||
cooldown: config.cooldown,
|
||||
guildOnly: true,
|
||||
execute(message, args) {
|
||||
// command starts here
|
||||
message.delete();
|
||||
const embed = new Discord.RichEmbed()
|
||||
.setTitle("Pong!")
|
||||
.setColor(config.colour)
|
||||
.setTimestamp()
|
||||
.addField("API Latency", `${Math.round(message.client.ping)}ms`, true);
|
||||
message.channel.send(embed);
|
||||
// command ends here
|
||||
},
|
||||
};
|
@ -1,75 +0,0 @@
|
||||
const Discord = require('discord.js');
|
||||
const config = require('../config.json');
|
||||
const log = require(`leekslazylogger`);
|
||||
|
||||
module.exports = {
|
||||
name: 'remove',
|
||||
description: 'Remove a member from a ticket',
|
||||
usage: '<@member>',
|
||||
aliases: ['kick'],
|
||||
example: 'remove @exampleUser',
|
||||
args: true,
|
||||
cooldown: config.cooldown,
|
||||
guildOnly: true,
|
||||
execute(message, args) {
|
||||
const client = message.client;
|
||||
// command starts here
|
||||
message.delete();
|
||||
if (!message.channel.name.startsWith('ticket-')) {
|
||||
if (config.useEmbeds) {
|
||||
const notTicket = new Discord.RichEmbed()
|
||||
.setColor("#E74C3C")
|
||||
.setDescription(`:x: **This command can only be used within a ticket channel**`)
|
||||
return message.channel.send(notTicket);
|
||||
} else {
|
||||
return message.channel.send(`:x: **This command can only be used within a ticket channel**`)
|
||||
}
|
||||
}
|
||||
|
||||
let user = message.guild.member(message.mentions.users.first() || message.guild.members.get(args[0]));
|
||||
if (!user) {
|
||||
if (config.useEmbeds) {
|
||||
const err1 = new Discord.RichEmbed()
|
||||
.setColor("#E74C3C")
|
||||
.setDescription(`:x: **Unknown user.** Please mention a valid user.`)
|
||||
return message.channel.send(err1);
|
||||
} else {
|
||||
return message.channel.send(`:x: **Unknown user.** Please mention a valid user.`);
|
||||
}
|
||||
}
|
||||
try {
|
||||
message.channel.overwritePermissions(user, {
|
||||
SEND_MESSAGES: false,
|
||||
READ_MESSAGES: false
|
||||
});
|
||||
if (config.useEmbeds) {
|
||||
const removed = new Discord.RichEmbed()
|
||||
.setColor(config.colour)
|
||||
.setDescription(`${user} has been removed.`)
|
||||
message.channel.send(removed);
|
||||
} else {
|
||||
message.channel.send(`${user} has been removed.`);
|
||||
}
|
||||
// log
|
||||
if (config.useEmbeds) {
|
||||
const embed = new Discord.RichEmbed()
|
||||
.setAuthor(`${client.user.username} / Ticket Log`, client.user.avatarURL)
|
||||
.setTitle("User Removed")
|
||||
.setColor(config.colour)
|
||||
.addField("Username", user, true)
|
||||
.addField("Removed by", message.author, true)
|
||||
.addField("Channel", message.channel, true)
|
||||
.setFooter(`DiscordTickets`)
|
||||
.setTimestamp();
|
||||
|
||||
client.channels.get(config.logChannel).send(embed);
|
||||
} else {
|
||||
client.channels.get(config.logChannel).send(`User removed to a ticket by **${message.author.tag} (${message.author.id})**`);
|
||||
}
|
||||
log.info(`${message.author.tag} removed a user to a ticket (#${message.channel})`)
|
||||
} catch(error) {
|
||||
log.error(error);
|
||||
}
|
||||
// command ends here
|
||||
},
|
||||
};
|
@ -1,20 +0,0 @@
|
||||
{
|
||||
"token": "token",
|
||||
"prefix": "!",
|
||||
"name": "DiscordTickets",
|
||||
"guildID": "id",
|
||||
"supportRole": "id",
|
||||
"ticketsCat": "id",
|
||||
"logChannel": "id",
|
||||
"colour": "#009999",
|
||||
"playing": "with tickets (!help)",
|
||||
"activityType": "PLAYING",
|
||||
"status": "ONLINE",
|
||||
"useEmbeds": true,
|
||||
"logDMs": true,
|
||||
"cooldown": 3,
|
||||
"ticketImage": true,
|
||||
"tagHereOnly": false,
|
||||
"ticketText": "Thank you for reaching out to our support team.\nA member of staff will come to assist you shortly. Please describe the issue in detail and explain what you have done to resolve the issue so far.\n\n*If you feel that the support you receive is inadequate, please feel free to submit a formal complaint to a senior member of staff.*",
|
||||
"debugLevel": 0
|
||||
}
|
261
index.js
261
index.js
@ -1,261 +0,0 @@
|
||||
/**
|
||||
###############################################################################################
|
||||
____ _ _____ _ _
|
||||
| _ \ (_) ___ ___ ___ _ __ __| | |_ _| (_) ___ | | __ ___ | |_ ___
|
||||
| | | | | | / __| / __| / _ \ | '__| / _` | | | | | / __| | |/ / / _ \ | __| / __|
|
||||
| |_| | | | \__ \ | (__ | (_) | | | | (_| | | | | | | (__ | < | __/ | |_ \__ \
|
||||
|____/ |_| |___/ \___| \___/ |_| \__,_| |_| |_| \___| |_|\_\ \___| \__| |___/
|
||||
|
||||
===============================================================================================
|
||||
|
||||
---------------------
|
||||
Discord Tickets
|
||||
---------------------
|
||||
|
||||
A bot created by Eartharoid for the Discord (TM) platform. [GNU-GPLv3.0]
|
||||
|
||||
> The bot manages user-created support tickets to allow your support team to provide quicker
|
||||
and better assistance to your community members.
|
||||
|
||||
---------------------
|
||||
Quick Start
|
||||
---------------------
|
||||
|
||||
> For detailed instructions, visit the Github repository and read the documentation.
|
||||
|
||||
> Assuming you have created a Discord application, edit 'config.json' to allow the bot to
|
||||
function correctly.
|
||||
|
||||
> You will need your bot token (keep it a secret) and channel & role IDs from your server
|
||||
|
||||
> It is recommended that you do not change much / anything in any of the .js files unless
|
||||
you know what you are doing, to prevent critical errors from occuring.
|
||||
|
||||
===============================================================================================
|
||||
|
||||
> For support, visit https://github.com/eartharoid/DiscordTickets/#readme
|
||||
> My website: https://eartharoid.me
|
||||
|
||||
@name DiscordTickets
|
||||
@author Eartharoid <contact@eartharoid.me>
|
||||
@license GNU-GPLv3
|
||||
|
||||
###############################################################################################
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const Discord = require('discord.js');
|
||||
const leeks = require('leeks.js');
|
||||
const log = require(`leekslazylogger`);
|
||||
const config = require('./config.json');
|
||||
const { version, homepage } = require('./package.json');
|
||||
const client = new Discord.Client();
|
||||
client.commands = new Discord.Collection();
|
||||
const cooldowns = new Discord.Collection();
|
||||
const now = Date.now();
|
||||
|
||||
const commands = fs.readdirSync('./commands').filter(file => file.endsWith('.js'));
|
||||
console.log(log.colour.magentaBright(`
|
||||
######## #### ###### ###### ####### ######## ########
|
||||
## ## ## ## ## ## ## ## ## ## ## ## ##
|
||||
## ## ## ## ## ## ## ## ## ## ##
|
||||
## ## ## ###### ## ## ## ######## ## ##
|
||||
## ## ## ## ## ## ## ## ## ## ##
|
||||
## ## ## ## ## ## ## ## ## ## ## ## ##
|
||||
######## #### ###### ###### ####### ## ## ########
|
||||
|
||||
######## #### ###### ## ## ######## ######## ######
|
||||
## ## ## ## ## ## ## ## ## ##
|
||||
## ## ## ## ## ## ## ##
|
||||
## ## ## ##### ###### ## ######
|
||||
## ## ## ## ## ## ## ##
|
||||
## ## ## ## ## ## ## ## ## ##
|
||||
## #### ###### ## ## ######## ## ######
|
||||
|
||||
`)); // banner appears in console
|
||||
console.log(log.colour.yellow(leeks.styles.bold(`DiscordTickets v${version} - Made By Eartharoid`)));
|
||||
console.log(log.colour.yellow(leeks.styles.bold(homepage)));
|
||||
console.log('\n\n');
|
||||
console.log(log.colour.bgGrey(log.colour.grey(`\n\n==========================================================================\n\n`)))
|
||||
console.log('\n\n');
|
||||
log.init('DiscordTickets (bot created by Eartharoid)');
|
||||
// all log.* functions are logged to ./log/file.log from here onwards
|
||||
log.info(`Starting up...`);
|
||||
|
||||
client.once('ready', () => { // after bot has logged in
|
||||
|
||||
log.info(`Initialising bot...`);
|
||||
for (const file of commands) {
|
||||
const command = require(`./commands/${file}`);
|
||||
client.commands.set(command.name, command);
|
||||
log.console(`> Loading '${config.prefix}${command.name}' command`);
|
||||
}
|
||||
|
||||
log.success(`Connected to Discord API`)
|
||||
log.success(`Logged in as ${client.user.tag}`)
|
||||
client.user.setPresence({game: {name: config.playing, type: config.activityType},status: config.status})
|
||||
// .then(log.basic)
|
||||
.catch(log.error);
|
||||
|
||||
if (config.useEmbeds) {
|
||||
const embed = new Discord.RichEmbed()
|
||||
.setAuthor(`${client.user.username} / Ticket Log`, client.user.avatarURL)
|
||||
.setColor("#2ECC71")
|
||||
.setDescription(":white_check_mark: **Started succesfully**")
|
||||
.setFooter(`DiscordTickets by Eartharoid`);
|
||||
client.channels.get(config.logChannel).send(embed)
|
||||
} else {
|
||||
client.channels.get(config.logChannel).send(":white_check_mark: **Started succesfully**");
|
||||
}
|
||||
|
||||
if (client.guilds.get(config.guildID).member(client.user).hasPermission("ADMINISTRATOR", false)) {
|
||||
log.info(`Checking permissions...`);
|
||||
setTimeout(() => log.success(`Required permissions have been granted\n\n`), 1250);
|
||||
|
||||
if (config.useEmbeds) {
|
||||
const embed = new Discord.RichEmbed()
|
||||
.setAuthor(`${client.user.username} / Ticket Log`, client.user.avatarURL)
|
||||
.setColor("#2ECC71")
|
||||
.setDescription(":white_check_mark: **Required permissions have been granted**")
|
||||
.setFooter(`DiscordTickets by Eartharoid`);
|
||||
|
||||
client.channels.get(config.logChannel).send(embed);
|
||||
} else {
|
||||
client.channels.get(config.logChannel).send(":white_check_mark: **Started succesfully**")
|
||||
}
|
||||
|
||||
} else {
|
||||
log.error(`Required permissions have not been granted`);
|
||||
log.error(`Please give the bot the 'ADMINISTRATOR' permission\n\n`);
|
||||
if (config.useEmbeds) {
|
||||
const embed = new Discord.RichEmbed()
|
||||
.setAuthor(`${client.user.username} / Ticket Log`, client.user.avatarURL)
|
||||
.setColor("#E74C3C")
|
||||
.setDescription(":x: **Required permissions have not been granted**\nPlease give the bot the `ADMINISTRATOR` permission")
|
||||
.setFooter(`DiscordTickets by Eartharoid`);
|
||||
|
||||
client.channels.get(config.logChannel).send(embed);
|
||||
} else {
|
||||
client.channels.get(config.logChannel).send(":white_check_mark: **Started succesfully**");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
client.on('message', async (message) => {
|
||||
// if (!message.content.startsWith(config.prefix) || message.author.bot) return;
|
||||
if (message.author.bot) return;
|
||||
if (message.channel.type === "dm") {
|
||||
if (message.author.id === client.user.id) return;
|
||||
// message.channel.send(`Sorry, commands can only be used on the server.`)
|
||||
if (config.logDMs) {
|
||||
if (config.useEmbeds) {
|
||||
const embed = new Discord.RichEmbed()
|
||||
.setAuthor(`${client.user.username} / Ticket Log`, client.user.avatarURL)
|
||||
.setTitle("DM Logger")
|
||||
.addField("Username", message.author.tag, true)
|
||||
.addField("Message", message.content, true)
|
||||
.setFooter(`DiscordTickets by Eartharoid`);
|
||||
client.channels.get(config.logChannel).send(embed)
|
||||
} else {
|
||||
client.channels.get(config.logChannel).send(`DM received from **${message.author.tag} (${message.author.id})** : \n\n\`\`\`${message.content}\`\`\``);
|
||||
}
|
||||
} else return;
|
||||
}
|
||||
// const args = message.content.slice(config.prefix.length).split(/ +/);
|
||||
|
||||
const prefixRegex = new RegExp(`^(<@!?${client.user.id}>|\\${config.prefix})\\s*`);
|
||||
if (!prefixRegex.test(message.content)) return;
|
||||
const [, matchedPrefix] = message.content.match(prefixRegex);
|
||||
const args = message.content.slice(matchedPrefix.length).trim().split(/ +/);
|
||||
const commandName = args.shift().toLowerCase();
|
||||
// if (!client.commands.has(commandName)) return;
|
||||
// const command = client.commands.get(commandName);
|
||||
const command = client.commands.get(commandName) || client.commands.find(cmd => cmd.aliases && cmd.aliases.includes(commandName));
|
||||
if (!command) return;
|
||||
if (command.guildOnly && message.channel.type !== 'text') return message.channel.send(`Sorry, this command can only be used on the server.`);
|
||||
|
||||
if (command.args && !args.length) {
|
||||
// let reply = `:x: **Arguments were expected but none were provided.**`;
|
||||
//
|
||||
// if (command.usage) {
|
||||
// reply += `\n**Usage:** \`${config.prefix}${command.name} ${command.usage}\``;
|
||||
// }
|
||||
//
|
||||
// return message.channel.send(reply);
|
||||
if (config.useEmbeds) {
|
||||
const embed = new Discord.RichEmbed()
|
||||
.setColor("#E74C3C")
|
||||
.setDescription(`\n**Usage:** \`${config.prefix}${command.name} ${command.usage}\`\nType \`${config.prefix}help ${command.name}\` for more information`);
|
||||
return message.channel.send(embed);
|
||||
} else {
|
||||
return message.channel.send(`**Usage:** \`${config.prefix}${command.name} ${command.usage}\`\nType \`${config.prefix}help ${command.name}\` for more information`);
|
||||
}
|
||||
};
|
||||
|
||||
if (!cooldowns.has(command.name)) cooldowns.set(command.name, new Discord.Collection());
|
||||
|
||||
const timestamps = cooldowns.get(command.name);
|
||||
const cooldownAmount = (command.cooldown || 3) * 1000;
|
||||
|
||||
if (timestamps.has(message.author.id)) {
|
||||
const expirationTime = timestamps.get(message.author.id) + cooldownAmount;
|
||||
|
||||
if (now < expirationTime) {
|
||||
const timeLeft = (expirationTime - now) / 1000;
|
||||
if (config.useEmbeds) {
|
||||
const embed = new Discord.RichEmbed()
|
||||
.setColor("#E74C3C")
|
||||
.setDescription(`:x: **Please do not spam commands** (wait ${timeLeft.toFixed(1)}s)`)
|
||||
return message.channel.send(embed);
|
||||
} else {
|
||||
return message.reply(`please do not spam commands (wait ${timeLeft.toFixed(1)}s)`);
|
||||
}
|
||||
}
|
||||
}
|
||||
timestamps.set(message.author.id, now);
|
||||
setTimeout(() => timestamps.delete(message.author.id), cooldownAmount);
|
||||
|
||||
|
||||
try {
|
||||
// client.commands.get(command).execute(message, args, config);
|
||||
command.execute(message, args);
|
||||
if (config.useEmbeds) {
|
||||
const embed = new Discord.RichEmbed()
|
||||
.setAuthor(`${client.user.username} / Command Log`, client.user.avatarURL)
|
||||
.setTitle("Command Used")
|
||||
.addField("Username", message.author, true)
|
||||
.addField("Command", command.name, true)
|
||||
.setFooter(`DiscordTickets`)
|
||||
.setTimestamp();
|
||||
client.channels.get(config.logChannel).send(embed);
|
||||
} else {
|
||||
client.channels.get(config.logChannel).send(`**${message.author.tag} (${message.author.id})** used the \`${command.name}\` command`);
|
||||
}
|
||||
log.console(`${message.author.tag} used the '${command.name}' command`)
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
message.channel.send(`:x: **Oof!** An error occured whilst executing that command.\nThe issue has been reported.`);
|
||||
log.error(`An unknown error occured whilst executing the '${command.name}' command`);
|
||||
}
|
||||
});
|
||||
|
||||
client.on('error', (error) => {
|
||||
log.warn(`Potential error detected\n(likely Discord API connection issue)\n`);
|
||||
log.error(`Client error:\n${error}`);
|
||||
});
|
||||
|
||||
client.on('warn', (e) => log.warn(`${e}`));
|
||||
|
||||
if (config.debugLevel == 1) client.on('debug', (e) => log.debug(`${e}`));
|
||||
|
||||
process.on('unhandledRejection', (error) => {
|
||||
log.warn(`An error was not caught`);
|
||||
log.error(`Uncaught error: \n${error.stack}`);
|
||||
});
|
||||
|
||||
process.on('beforeExit', (code) => {
|
||||
log.basic(log.colour.yellowBright(`Disconected from Discord API`));
|
||||
log.basic(`Exiting (${code})`);
|
||||
});
|
||||
|
||||
client.login(config.token);
|
2700
package-lock.json
generated
Normal file
2700
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
64
package.json
64
package.json
@ -1,28 +1,40 @@
|
||||
{
|
||||
"name": "discordtickets",
|
||||
"version": "1.1.5",
|
||||
"description": "A Discord ticket/support - an open-source & self-hosted bot",
|
||||
"main": "index.js",
|
||||
"dependencies": {
|
||||
"discord.js": "11.x",
|
||||
"leekslazylogger": "^1.1.9"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/eartharoid/DiscordTickets.git"
|
||||
},
|
||||
"keywords": [
|
||||
"discord",
|
||||
"bot",
|
||||
"tickets"
|
||||
],
|
||||
"author": "Eartharoid",
|
||||
"license": "GPL-3.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/eartharoid/DiscordTickets/issues"
|
||||
},
|
||||
"homepage": "https://github.com/eartharoid/DiscordTickets"
|
||||
"name": "@eartharoid/discordtickets",
|
||||
"version": "2.0.0",
|
||||
"description": "A Discord ticket/support - an open-source & self-hosted bot",
|
||||
"main": "src/index.js",
|
||||
"dependencies": {
|
||||
"discord.js": "^12.2.0",
|
||||
"dotenv": "^8.2.0",
|
||||
"leekslazylogger": "^2.0.0-alpha.5",
|
||||
"node-fetch": "^2.6.0",
|
||||
"sequelize": "^6.3.4",
|
||||
"sqlite3": "^5.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mysql2": "2.x"
|
||||
},
|
||||
"devDependencies": {
|
||||
"eslint": "^7.6.0",
|
||||
"nodemon": "^2.0.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node src/",
|
||||
"test": "echo \"Nothing to test! Run with 'npm start'\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/eartharoid/DiscordTickets.git"
|
||||
},
|
||||
"keywords": [
|
||||
"discord",
|
||||
"bot",
|
||||
"tickets"
|
||||
],
|
||||
"author": "eartharoid",
|
||||
"license": "GPL-3.0",
|
||||
"bugs": {
|
||||
"url": "https://github.com/eartharoid/DiscordTickets/issues"
|
||||
},
|
||||
"homepage": "https://github.com/eartharoid/DiscordTickets"
|
||||
}
|
||||
|
117
src/commands/add.js
Normal file
117
src/commands/add.js
Normal file
@ -0,0 +1,117 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const Discord = require('discord.js');
|
||||
const config = require('../../user/config.js');
|
||||
const ChildLogger = require('leekslazylogger').ChildLogger;
|
||||
const log = new ChildLogger();
|
||||
|
||||
module.exports = {
|
||||
name: 'add',
|
||||
description: 'Add a member to a ticket channel',
|
||||
usage: '<@member> [... #channel]',
|
||||
aliases: ['+'],
|
||||
example: 'add @member to #ticket-23',
|
||||
args: true,
|
||||
async execute(client, message, args, Ticket) {
|
||||
|
||||
const notTicket = new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(':x: **This isn\'t a ticket channel**')
|
||||
.setDescription('Use this command in the ticket channel you want to add a user to, or mention the channel.')
|
||||
.addField('Usage', `\`${config.prefix}${this.name} ${this.usage}\`\n`)
|
||||
.addField('Help', `Type \`${config.prefix}help ${this.name}\` for more information`)
|
||||
.setFooter(message.guild.name, message.guild.iconURL());
|
||||
|
||||
let ticket;
|
||||
|
||||
let channel = message.mentions.channels.first();
|
||||
|
||||
if(!channel) {
|
||||
|
||||
channel = message.channel;
|
||||
ticket = await Ticket.findOne({ where: { channel: message.channel.id } });
|
||||
if(!ticket)
|
||||
return message.channel.send(notTicket);
|
||||
|
||||
} else {
|
||||
|
||||
ticket = await Ticket.findOne({ where: { channel: channel.id } });
|
||||
if(!ticket) {
|
||||
notTicket
|
||||
.setTitle(':x: **Channel is not a ticket**')
|
||||
.setDescription(`${channel} is not a ticket channel.`);
|
||||
return message.channel.send(notTicket);
|
||||
}
|
||||
}
|
||||
|
||||
if(message.author.id !== ticket.get('creator') && !message.member.roles.cache.has(config.staff_role))
|
||||
return message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(':x: **No permission**')
|
||||
.setDescription(`You don't have permission to alter ${channel} as it does not belong to you and you are not staff.`)
|
||||
.addField('Usage', `\`${config.prefix}${this.name} ${this.usage}\`\n`)
|
||||
.addField('Help', `Type \`${config.prefix}help ${this.name}\` for more information`)
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
|
||||
|
||||
|
||||
let member = message.guild.member(message.mentions.users.first() || message.guild.members.cache.get(args[0]));
|
||||
|
||||
if(!member)
|
||||
return message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(':x: **Unknown member**')
|
||||
.setDescription('Please mention a valid member.')
|
||||
.addField('Usage', `\`${config.prefix}${this.name} ${this.usage}\`\n`)
|
||||
.addField('Help', `Type \`${config.prefix}help ${this.name}\` for more information`)
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
|
||||
try {
|
||||
channel.updateOverwrite(member.user, {
|
||||
VIEW_CHANNEL: true,
|
||||
SEND_MESSAGES: true,
|
||||
ATTACH_FILES: true,
|
||||
READ_MESSAGE_HISTORY: true
|
||||
});
|
||||
|
||||
if(channel.id !== message.channel.id)
|
||||
channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(member.user.username, member.user.displayAvatarURL())
|
||||
.setTitle('**Member added**')
|
||||
.setDescription(`${member} has been added by ${message.author}`)
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
|
||||
|
||||
|
||||
message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(member.user.username, member.user.displayAvatarURL())
|
||||
.setTitle(':white_check_mark: **Member added**')
|
||||
.setDescription(`${member} has been added to <#${ticket.get('channel')}>`)
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
|
||||
log.info(`${message.author.tag} added a user to a ticket (#${message.channel.id})`);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
// command ends here
|
||||
},
|
||||
};
|
106
src/commands/close.js
Normal file
106
src/commands/close.js
Normal file
@ -0,0 +1,106 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const ChildLogger = require('leekslazylogger').ChildLogger;
|
||||
const log = new ChildLogger();
|
||||
const Discord = require('discord.js');
|
||||
const config = require('../../user/config');
|
||||
|
||||
module.exports = {
|
||||
name: 'close',
|
||||
description: 'Close a ticket; either a specified (mentioned) channel, or the channel the command is used in.',
|
||||
usage: '[ticket]',
|
||||
aliases: ['none'],
|
||||
example: 'close #ticket-17',
|
||||
args: false,
|
||||
async execute(client, message, args, Ticket) {
|
||||
|
||||
const notTicket = new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(':x: **This isn\'t a ticket channel**')
|
||||
.setDescription('Use this command in the ticket channel you want to close, or mention the channel.')
|
||||
.addField('Usage', `\`${config.prefix}${this.name} ${this.usage}\`\n`)
|
||||
.addField('Help', `Type \`${config.prefix}help ${this.name}\` for more information`)
|
||||
.setFooter(message.guild.name, message.guild.iconURL());
|
||||
|
||||
let ticket;
|
||||
const channel = message.mentions.channels.first();
|
||||
// let channel = message.guild.channels.resolve(message.mentions.channels.first()); // not necessary
|
||||
|
||||
if(!channel) {
|
||||
|
||||
ticket = await Ticket.findOne({ where: { channel: message.channel.id } });
|
||||
if(!ticket)
|
||||
return message.channel.send(notTicket);
|
||||
|
||||
ticket.update({ open: false}, { where: { channel: message.channel.id } });
|
||||
|
||||
message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(`:white_check_mark: **Ticket ${ticket.id} closed**`)
|
||||
.setDescription('The channel will be automatically deleted once the contents have been archived.')
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
|
||||
setTimeout(() => message.channel.delete(), 5000);
|
||||
|
||||
} else {
|
||||
|
||||
ticket = await Ticket.findOne({ where: { channel: channel.id } });
|
||||
if(!ticket) {
|
||||
notTicket
|
||||
.setTitle(':x: **Channel is not a ticket**')
|
||||
.setDescription(`${channel} is not a ticket channel.`);
|
||||
return message.channel.send(notTicket);
|
||||
}
|
||||
|
||||
if(message.author.id !== ticket.get('creator') && !message.member.roles.cache.has(config.staff_role))
|
||||
return message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(':x: **No permission**')
|
||||
.setDescription(`You don't have permission to close ${channel} as it does not belong to you and you are not staff.`)
|
||||
.addField('Usage', `\`${config.prefix}${this.name} ${this.usage}\`\n`)
|
||||
.addField('Help', `Type \`${config.prefix}help ${this.name}\` for more information`)
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
|
||||
ticket.update({ open: false}, { where: { channel: channel.id } });
|
||||
|
||||
message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(`:white_check_mark: **Ticket ${ticket.id} closed**`)
|
||||
.setDescription('The channel will be automatically deleted once the contents have been archived.')
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
|
||||
setTimeout(() => channel.delete(), 5000);
|
||||
}
|
||||
|
||||
log.info(`${message.author.tag} closed a ticket (#ticket-${ticket.get('id')})`);
|
||||
|
||||
if (config.logs.discord.enabled)
|
||||
client.channels.cache.get(config.logs.discord.channel).send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('Ticket closed')
|
||||
.addField('Creator', `<@${ticket.get('creator')}>` , true)
|
||||
.addField('Closed by', message.author, true)
|
||||
.setFooter(client.user.username, client.user.avatarURL())
|
||||
.setTimestamp()
|
||||
);
|
||||
|
||||
},
|
||||
};
|
98
src/commands/help.js
Normal file
98
src/commands/help.js
Normal file
@ -0,0 +1,98 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const ChildLogger = require('leekslazylogger').ChildLogger;
|
||||
const log = new ChildLogger();
|
||||
const Discord = require('discord.js');
|
||||
const config = require('../../user/config');
|
||||
|
||||
module.exports = {
|
||||
name: 'help',
|
||||
description: 'Display help menu',
|
||||
usage: '[command]',
|
||||
aliases: ['command', 'commands'],
|
||||
example: 'help new',
|
||||
args: false,
|
||||
execute(client, message, args) {
|
||||
|
||||
const commands = Array.from(client.commands.values());
|
||||
|
||||
if (!args.length) {
|
||||
let cmds = [];
|
||||
|
||||
for (let command of commands) {
|
||||
if (command.hide)
|
||||
continue;
|
||||
if (command.permission && !message.member.hasPermission(command.permission))
|
||||
continue;
|
||||
|
||||
let desc = command.description;
|
||||
|
||||
if (desc.length > 50)
|
||||
desc = desc.substring(0, 50) + '...';
|
||||
cmds.push(`**${config.prefix}${command.name}** **·** ${desc}`);
|
||||
}
|
||||
|
||||
message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setTitle('Commands')
|
||||
.setColor(config.colour)
|
||||
.setDescription(
|
||||
`\nThe commands you have access to are listed below. Type \`${config.prefix}help [command]\` for more information about a specific command.
|
||||
\n${cmds.join('\n\n')}
|
||||
\nPlease contact a member of staff if you require assistance.`
|
||||
)
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
.setTimestamp()
|
||||
).catch((error) => {
|
||||
log.warn('Could not send help menu');
|
||||
log.error(error);
|
||||
});
|
||||
|
||||
} else {
|
||||
const name = args[0].toLowerCase();
|
||||
const command = client.commands.get(name) || client.commands.find(c => c.aliases && c.aliases.includes(name));
|
||||
|
||||
if (!command)
|
||||
return message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setDescription(`:x: **Invalid command name** (\`${config.prefix}help\`)`)
|
||||
);
|
||||
|
||||
|
||||
const cmd = new Discord.MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setTitle(command.name);
|
||||
|
||||
|
||||
if (command.long) {
|
||||
cmd.setDescription(command.long);
|
||||
} else {
|
||||
cmd.setDescription(command.description);
|
||||
}
|
||||
if (command.aliases) cmd.addField('Aliases', `\`${command.aliases.join(', ')}\``, true);
|
||||
|
||||
if (command.usage) cmd.addField('Usage', `\`${config.prefix}${command.name} ${command.usage}\``, false);
|
||||
|
||||
if (command.usage) cmd.addField('Example', `\`${config.prefix}${command.example}\``, false);
|
||||
|
||||
|
||||
if (command.permission && !message.member.hasPermission(command.permission)) {
|
||||
cmd.addField('Required Permission', `\`${command.permission}\` :exclamation: You don't have permission to use this command`, true);
|
||||
} else {
|
||||
cmd.addField('Required Permission', `\`${command.permission || 'none'}\``, true);
|
||||
}
|
||||
|
||||
message.channel.send(cmd);
|
||||
|
||||
}
|
||||
|
||||
// command ends here
|
||||
},
|
||||
};
|
195
src/commands/new.js
Normal file
195
src/commands/new.js
Normal file
@ -0,0 +1,195 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const ChildLogger = require('leekslazylogger').ChildLogger;
|
||||
const log = new ChildLogger();
|
||||
const Discord = require('discord.js');
|
||||
const fs = require('fs');
|
||||
const config = require('../../user/config');
|
||||
|
||||
module.exports = {
|
||||
name: 'new',
|
||||
description: 'Create a new support ticket',
|
||||
usage: '[brief description]',
|
||||
aliases: ['ticket', 'open'],
|
||||
example: 'new my server won\'t start',
|
||||
args: false,
|
||||
async execute(client, message, args, Ticket) {
|
||||
|
||||
|
||||
const supportRole = message.guild.roles.cache.get(config.staff_role);
|
||||
if (!supportRole)
|
||||
return message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setTitle(':x: **Error**')
|
||||
.setDescription(`${config.name} has not been set up correctly. Could not find a 'support team' role with the id \`${config.staff_role}\``)
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
|
||||
|
||||
let tickets = await Ticket.findAndCountAll({
|
||||
where: {
|
||||
creator: message.author.id,
|
||||
open: true
|
||||
},
|
||||
limit: config.tickets.max
|
||||
});
|
||||
|
||||
if (tickets.count >= config.tickets.max) {
|
||||
let ticketList = [];
|
||||
for (let t in tickets.rows) {
|
||||
let desc = tickets.rows[t].topic.substring(0, 20);
|
||||
ticketList
|
||||
.push(`<#${tickets.rows[t].channel}>: \`${desc}${desc.length > 20 ? '...' : ''}\``);
|
||||
}
|
||||
|
||||
let m = await message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(`:x: **You already have ${tickets.count} or more open tickets**`)
|
||||
.setDescription(`Use \`${config.prefix}close\` to close unneeded tickets.\n\n${ticketList.join(',\n')}`)
|
||||
.setFooter(message.guild.name + ' | This message will be deleted in 15 seconds', message.guild.iconURL())
|
||||
);
|
||||
|
||||
return setTimeout(async () => {
|
||||
await message.delete();
|
||||
await m.delete();
|
||||
}, 15000);
|
||||
}
|
||||
|
||||
|
||||
let topic = args.join(' ');
|
||||
if (topic.length > 256)
|
||||
return message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(':x: **Description too long**')
|
||||
.setDescription('Please limit your ticket topic to less than 256 characters. A short sentence will do.')
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
else if (topic.length < 1)
|
||||
topic = 'No topic given';
|
||||
|
||||
|
||||
let ticket = await Ticket.create({
|
||||
channel: '',
|
||||
creator: message.author.id,
|
||||
open: true,
|
||||
archived: false,
|
||||
topic: topic
|
||||
});
|
||||
|
||||
let name = 'ticket-' + ticket.get('id');
|
||||
|
||||
message.guild.channels.create(name, {
|
||||
type: 'text',
|
||||
topic: `${message.author} | ${topic}`,
|
||||
parent: config.tickets.category,
|
||||
permissionOverwrites: [{
|
||||
id: message.guild.roles.everyone,
|
||||
deny: ['VIEW_CHANNEL', 'SEND_MESSAGES']
|
||||
},
|
||||
{
|
||||
id: message.member,
|
||||
allow: ['VIEW_CHANNEL', 'SEND_MESSAGES', 'ATTACH_FILES', 'READ_MESSAGE_HISTORY']
|
||||
},
|
||||
{
|
||||
id: supportRole,
|
||||
allow: ['VIEW_CHANNEL', 'SEND_MESSAGES', 'ATTACH_FILES', 'READ_MESSAGE_HISTORY']
|
||||
}
|
||||
],
|
||||
reason: 'User requested a new support ticket channel'
|
||||
}).then(async c => {
|
||||
|
||||
Ticket.update({
|
||||
channel: c.id
|
||||
}, {
|
||||
where: {
|
||||
id: ticket.id
|
||||
}
|
||||
});
|
||||
|
||||
let m = await message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(':white_check_mark: **Ticket created**')
|
||||
.setDescription(`Your ticket has been created: ${c}`)
|
||||
.setFooter(client.user.username + ' | This message will be deleted in 15 seconds', client.user.avatarURL())
|
||||
);
|
||||
|
||||
setTimeout(async () => {
|
||||
await message.delete();
|
||||
await m.delete();
|
||||
}, 15000);
|
||||
|
||||
|
||||
let ping;
|
||||
switch (config.tickets.ping) {
|
||||
case 'staff':
|
||||
ping = `<@&${config.staff_role}>,\n`;
|
||||
break;
|
||||
case false:
|
||||
ping = '';
|
||||
break;
|
||||
default:
|
||||
ping = `@${config.tickets.ping},\n`;
|
||||
}
|
||||
|
||||
await c.send(ping + `${message.author} has created a new ticket`);
|
||||
|
||||
if (config.tickets.send_img) {
|
||||
const images = fs.readdirSync('user/images');
|
||||
await c.send({
|
||||
files: [
|
||||
'user/images/' +
|
||||
images[Math.floor(Math.random() * images.length)]
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
let text = config.tickets.text
|
||||
.replace('{{ name }}', message.author.username)
|
||||
.replace('{{ tag }}', message.author);
|
||||
|
||||
|
||||
let w = await c.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setDescription(text)
|
||||
.addField('Topic', `\`${topic}\``)
|
||||
.setFooter(client.user.username, client.user.avatarURL())
|
||||
);
|
||||
|
||||
if (config.tickets.pin)
|
||||
await w.pin();
|
||||
// await w.pin().then(m => m.delete()); // oopsie, this deletes the pinned message
|
||||
|
||||
if (config.logs.discord.enabled)
|
||||
client.channels.cache.get(config.logs.discord.channel).send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('New ticket')
|
||||
.setDescription(`\`${topic}\``)
|
||||
.addField('Creator', message.author, true)
|
||||
.addField('Channel', c, true)
|
||||
.setFooter(client.user.username, client.user.avatarURL())
|
||||
.setTimestamp()
|
||||
);
|
||||
|
||||
log.info(`${message.author.tag} created a new ticket (#${name})`);
|
||||
|
||||
|
||||
}).catch(log.error);
|
||||
},
|
||||
};
|
117
src/commands/remove.js
Normal file
117
src/commands/remove.js
Normal file
@ -0,0 +1,117 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const Discord = require('discord.js');
|
||||
const config = require('../../user/config.js');
|
||||
const ChildLogger = require('leekslazylogger').ChildLogger;
|
||||
const log = new ChildLogger();
|
||||
|
||||
module.exports = {
|
||||
name: 'remove',
|
||||
description: 'Remove a member from ticket channel',
|
||||
usage: '<@member> [... #channel]',
|
||||
aliases: ['-'],
|
||||
example: 'remove @member from #ticket-23',
|
||||
args: true,
|
||||
async execute(client, message, args, Ticket) {
|
||||
|
||||
const notTicket = new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(':x: **This isn\'t a ticket channel**')
|
||||
.setDescription('Use this command in the ticket channel you want to remove a user from, or mention the channel.')
|
||||
.addField('Usage', `\`${config.prefix}${this.name} ${this.usage}\`\n`)
|
||||
.addField('Help', `Type \`${config.prefix}help ${this.name}\` for more information`)
|
||||
.setFooter(message.guild.name, message.guild.iconURL());
|
||||
|
||||
let ticket;
|
||||
|
||||
let channel = message.mentions.channels.first();
|
||||
|
||||
if(!channel) {
|
||||
|
||||
channel = message.channel;
|
||||
ticket = await Ticket.findOne({ where: { channel: message.channel.id } });
|
||||
if(!ticket)
|
||||
return message.channel.send(notTicket);
|
||||
|
||||
} else {
|
||||
|
||||
ticket = await Ticket.findOne({ where: { channel: channel.id } });
|
||||
if(!ticket) {
|
||||
notTicket
|
||||
.setTitle(':x: **Channel is not a ticket**')
|
||||
.setDescription(`${channel} is not a ticket channel.`);
|
||||
return message.channel.send(notTicket);
|
||||
}
|
||||
}
|
||||
|
||||
if(message.author.id !== ticket.get('creator') && !message.member.roles.cache.has(config.staff_role))
|
||||
return message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(':x: **No permission**')
|
||||
.setDescription(`You don't have permission to alter ${channel} as it does not belong to you and you are not staff.`)
|
||||
.addField('Usage', `\`${config.prefix}${this.name} ${this.usage}\`\n`)
|
||||
.addField('Help', `Type \`${config.prefix}help ${this.name}\` for more information`)
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
|
||||
|
||||
|
||||
let member = message.guild.member(message.mentions.users.first() || message.guild.members.cache.get(args[0]));
|
||||
|
||||
if(!member)
|
||||
return message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(':x: **Unknown member**')
|
||||
.setDescription('Please mention a valid member.')
|
||||
.addField('Usage', `\`${config.prefix}${this.name} ${this.usage}\`\n`)
|
||||
.addField('Help', `Type \`${config.prefix}help ${this.name}\` for more information`)
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
|
||||
try {
|
||||
channel.updateOverwrite(member.user, {
|
||||
VIEW_CHANNEL: false,
|
||||
SEND_MESSAGES: false,
|
||||
ATTACH_FILES: false,
|
||||
READ_MESSAGE_HISTORY: false
|
||||
});
|
||||
|
||||
if(channel.id !== message.channel.id)
|
||||
channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(member.user.username, member.user.displayAvatarURL())
|
||||
.setTitle('**Member remove**')
|
||||
.setDescription(`${member} has been removed by ${message.author}`)
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
|
||||
|
||||
|
||||
message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(member.user.username, member.user.displayAvatarURL())
|
||||
.setTitle(':white_check_mark: **Member removed**')
|
||||
.setDescription(`${member} has been removed from <#${ticket.get('channel')}>`)
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
|
||||
log.info(`${message.author.tag} removed a user from a ticket (#${message.channel.id})`);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
// command ends here
|
||||
},
|
||||
};
|
114
src/commands/tickets.js
Normal file
114
src/commands/tickets.js
Normal file
@ -0,0 +1,114 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const Discord = require('discord.js');
|
||||
const fs = require('fs');
|
||||
const config = require('../../user/config');
|
||||
|
||||
module.exports = {
|
||||
name: 'tickets',
|
||||
description: 'List your recent tickets to access transcripts / archives.',
|
||||
usage: '[@member]',
|
||||
aliases: ['list'],
|
||||
example: '',
|
||||
args: false,
|
||||
async execute(client, message, args, Ticket) {
|
||||
|
||||
|
||||
const supportRole = message.guild.roles.cache.get(config.staff_role);
|
||||
if (!supportRole)
|
||||
return message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setTitle(':x: **Error**')
|
||||
.setDescription(`${config.name} has not been set up correctly. Could not find a 'support team' role with the id \`${config.staff_role}\``)
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
|
||||
let context;
|
||||
let user = message.mentions.users.first() || message.guild.members.cache.get(args[0]);
|
||||
|
||||
if(!user) {
|
||||
if(!message.member.roles.cache.has(config.staff_role))
|
||||
return message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(':x: **No permission**')
|
||||
.setDescription('You don\'t have permission to list others\' tickets as you are not staff.')
|
||||
.addField('Usage', `\`${config.prefix}${this.name} ${this.usage}\`\n`)
|
||||
.addField('Help', `Type \`${config.prefix}help ${this.name}\` for more information`)
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
|
||||
user = message.author;
|
||||
context = 'staff';
|
||||
}
|
||||
|
||||
context = 'self';
|
||||
|
||||
|
||||
let openTickets = await Ticket.findAndCountAll({
|
||||
where: {
|
||||
creator: user.id,
|
||||
open: true
|
||||
}
|
||||
});
|
||||
|
||||
let closedTickets = await Ticket.findAndCountAll({
|
||||
where: {
|
||||
creator: user.id,
|
||||
open: false
|
||||
}
|
||||
});
|
||||
|
||||
closedTickets.rows = closedTickets.rows.slice(-10); // get most recent 10
|
||||
|
||||
let embed = new Discord.MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('Your tickets')
|
||||
.setFooter(message.guild.name + ' | This message will be deleted in 60 seconds', message.guild.iconURL());
|
||||
|
||||
if(config.transcripts.web.enabled)
|
||||
embed.setDescription(`You can access all of your ticket archives on the [web portal](${config.transcripts.web.server}/${user.id}).`);
|
||||
|
||||
let open = [],
|
||||
closed = [];
|
||||
|
||||
|
||||
for (let t in openTickets.rows) {
|
||||
let desc = openTickets.rows[t].topic.substring(0, 30);
|
||||
open.push(`> <#${openTickets.rows[t].channel}>: \`${desc}${desc.length > 20 ? '...' : ''}\``);
|
||||
|
||||
}
|
||||
|
||||
for (let t in closedTickets.rows) {
|
||||
let desc = closedTickets.rows[t].topic.substring(0, 30);
|
||||
let transcript = '';
|
||||
if(fs.existsSync(`user/transcripts/text/${closedTickets.rows[t].channel}.txt`))
|
||||
transcript = `\n> Type \`${config.prefix}transcript ${closedTickets.rows[t].id}\` to download text transcript.`;
|
||||
|
||||
closed.push(`> #${closedTickets.rows[t].id}: \`${desc}${desc.length > 20 ? '...' : ''}\`${transcript}`);
|
||||
|
||||
}
|
||||
let pre = context === 'self' ? 'You have' : user + ' has';
|
||||
embed.addField('Open tickets', openTickets.count === 0 ? `${pre} no open tickets.` : open.join('\n\n'), false);
|
||||
embed.addField('Closed tickets', closedTickets.count === 0 ? `${pre} no old tickets` : closed.join('\n\n'), false);
|
||||
|
||||
let m = await message.channel.send(embed);
|
||||
|
||||
return setTimeout(async () => {
|
||||
await message.delete();
|
||||
await m.delete();
|
||||
}, 60000);
|
||||
|
||||
|
||||
|
||||
},
|
||||
};
|
23
src/commands/transcript.js
Normal file
23
src/commands/transcript.js
Normal file
@ -0,0 +1,23 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const Discord = require('discord.js');
|
||||
const fs = require('fs');
|
||||
const config = require('../../user/config');
|
||||
|
||||
module.exports = {
|
||||
name: 'transcript',
|
||||
description: 'Download a transcript',
|
||||
usage: '<ticket-id>',
|
||||
aliases: ['archive', 'download'],
|
||||
example: 'transcript 57',
|
||||
args: false,
|
||||
async execute(client, message, args, Ticket) {
|
||||
/** @TODO TRY TO SEND ATTACHMENT TO DM */
|
||||
}
|
||||
};
|
17
src/events/debug.js
Normal file
17
src/events/debug.js
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const ChildLogger = require('leekslazylogger').ChildLogger;
|
||||
const log = new ChildLogger();
|
||||
|
||||
module.exports = {
|
||||
event: 'debug',
|
||||
execute(client, e) {
|
||||
log.debug(e);
|
||||
}
|
||||
};
|
17
src/events/error.js
Normal file
17
src/events/error.js
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const ChildLogger = require('leekslazylogger').ChildLogger;
|
||||
const log = new ChildLogger();
|
||||
|
||||
module.exports = {
|
||||
event: 'error',
|
||||
execute(client, e) {
|
||||
log.error(e);
|
||||
}
|
||||
};
|
87
src/events/message.js
Normal file
87
src/events/message.js
Normal file
@ -0,0 +1,87 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const Discord = require('discord.js');
|
||||
const ChildLogger = require('leekslazylogger').ChildLogger;
|
||||
const log = new ChildLogger();
|
||||
const config = require('../../user/config');
|
||||
|
||||
module.exports = {
|
||||
event: 'message',
|
||||
async execute(client, message, Ticket) {
|
||||
if (message.author.bot || message.author.id === client.user.id) return;
|
||||
|
||||
if (message.channel.type === 'dm') {
|
||||
log.console(`Received a DM from ${message.author.tag}: ${message.cleanContent}`);
|
||||
return message.channel.send(`Hello there, ${message.author.username}!
|
||||
I am the support bot for **${client.guilds.cache.get(config.guild)}**.
|
||||
Type \`${config.prefix}new\` on the server to create a new ticket.`);
|
||||
}
|
||||
|
||||
const prefixRegex = new RegExp(`^(<@!?${client.user.id}>|\\${config.prefix})\\s*`);
|
||||
if (!prefixRegex.test(message.content)) return;
|
||||
const [, matchedPrefix] = message.content.match(prefixRegex);
|
||||
const args = message.content.slice(matchedPrefix.length).trim().split(/ +/);
|
||||
const commandName = args.shift().toLowerCase();
|
||||
const command = client.commands.get(commandName) || client.commands.find(cmd => cmd.aliases && cmd.aliases.includes(commandName));
|
||||
if (!command || commandName === 'none') return;
|
||||
|
||||
if (command.permission && !message.member.hasPermission(command.permission)) {
|
||||
log.console(`${message.author.tag} tried to use the '${command.name}' command without permission`);
|
||||
return message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setTitle(':x: No permission')
|
||||
.setDescription(`**You do not have permission to use the \`${command.name}\` command** (requires \`${command.permission}\`).`)
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
if (command.args && !args.length)
|
||||
return message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.addField('Usage', `\`${config.prefix}${command.name} ${command.usage}\`\n`)
|
||||
.addField('Help', `Type \`${config.prefix}help ${command.name}\` for more information`)
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
|
||||
if (!client.cooldowns.has(command.name)) client.cooldowns.set(command.name, new Discord.Collection());
|
||||
|
||||
const now = Date.now();
|
||||
const timestamps = client.cooldowns.get(command.name);
|
||||
const cooldownAmount = (command.cooldown || config.cooldown) * 1000;
|
||||
|
||||
if (timestamps.has(message.author.id)) {
|
||||
const expirationTime = timestamps.get(message.author.id) + cooldownAmount;
|
||||
|
||||
if (now < expirationTime) {
|
||||
const timeLeft = (expirationTime - now) / 1000;
|
||||
log.console(`${message.author.tag} attempted to use the '${command.name}' command before the cooldown was over`);
|
||||
return message.channel.send(
|
||||
new Discord.MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setDescription(`:x: Please wait ${timeLeft.toFixed(1)} second(s) before reusing the \`${command.name}\` command.`)
|
||||
.setFooter(message.guild.name, message.guild.iconURL())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
timestamps.set(message.author.id, now);
|
||||
setTimeout(() => timestamps.delete(message.author.id), cooldownAmount);
|
||||
|
||||
try {
|
||||
command.execute(client, message, args, Ticket);
|
||||
log.console(`${message.author.tag} used the '${command.name}' command`);
|
||||
} catch (error) {
|
||||
log.warn(`An error occurred whilst executing the '${command.name}' command`);
|
||||
log.error(error);
|
||||
message.channel.send(`:x: An error occurred whilst executing the \`${command.name}\` command.\nThe issue has been reported.`);
|
||||
}
|
||||
}
|
||||
};
|
18
src/events/rateLimit.js
Normal file
18
src/events/rateLimit.js
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const ChildLogger = require('leekslazylogger').ChildLogger;
|
||||
const log = new ChildLogger();
|
||||
|
||||
module.exports = {
|
||||
event: 'rateLimit',
|
||||
execute(client, limit) {
|
||||
log.warn('Rate-limited!');
|
||||
log.debug(limit);
|
||||
}
|
||||
};
|
42
src/events/ready.js
Normal file
42
src/events/ready.js
Normal file
@ -0,0 +1,42 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const ChildLogger = require('leekslazylogger').ChildLogger;
|
||||
const log = new ChildLogger();
|
||||
const config = require('../../user/config');
|
||||
|
||||
module.exports = {
|
||||
event: 'ready',
|
||||
execute(client) {
|
||||
|
||||
log.success(`Authenticated as ${client.user.tag}`);
|
||||
|
||||
const updatePresence = () => {
|
||||
let num = Math.floor(Math.random() * config.activities.length);
|
||||
client.user.setPresence({
|
||||
activity: {
|
||||
name: config.activities[num] + ` | ${config.prefix}help`,
|
||||
type: config.activity_types[num]
|
||||
}
|
||||
}).catch(log.error);
|
||||
log.debug(`Updated presence: ${config.activity_types[num]} ${config.activities[num]}`);
|
||||
};
|
||||
|
||||
updatePresence();
|
||||
setInterval(() => {
|
||||
updatePresence();
|
||||
}, 15000);
|
||||
|
||||
|
||||
if (client.guilds.cache.get(config.guild).member(client.user).hasPermission('ADMINISTRATOR', false))
|
||||
log.success('\'ADMINISTRATOR\' permission has been granted');
|
||||
else
|
||||
log.warn('Bot does not have \'ADMINISTRATOR\' permission');
|
||||
|
||||
}
|
||||
};
|
17
src/events/warn.js
Normal file
17
src/events/warn.js
Normal file
@ -0,0 +1,17 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const ChildLogger = require('leekslazylogger').ChildLogger;
|
||||
const log = new ChildLogger();
|
||||
|
||||
module.exports = {
|
||||
event: 'warn',
|
||||
execute(client, e) {
|
||||
log.warn(e);
|
||||
}
|
||||
};
|
99
src/index.js
Normal file
99
src/index.js
Normal file
@ -0,0 +1,99 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
require('dotenv').config({path: 'user/.env'});
|
||||
const Discord = require('discord.js');
|
||||
const fs = require('fs');
|
||||
const leeks = require('leeks.js');
|
||||
const client = new Discord.Client({
|
||||
autoReconnect: true
|
||||
});
|
||||
client.events = new Discord.Collection();
|
||||
client.commands = new Discord.Collection();
|
||||
client.cooldowns = new Discord.Collection();
|
||||
|
||||
require('./utils/banner')(leeks); // big coloured text thing
|
||||
|
||||
const config = require('../user/config');
|
||||
const Logger = require('leekslazylogger');
|
||||
const log = new Logger({
|
||||
name: config.name,
|
||||
logToFile: config.logs.files.enabled,
|
||||
maxAge: config.logs.files.keep_for,
|
||||
debug: config.debug
|
||||
});
|
||||
log.multi(log); // required to allow other files to access the logger
|
||||
|
||||
require('./utils/updater')(); // check for updates
|
||||
|
||||
|
||||
/**
|
||||
* storage
|
||||
*/
|
||||
const { Sequelize, Model, DataTypes } = require('sequelize');
|
||||
|
||||
let sequelize;
|
||||
|
||||
if(config.storage.type === 'mysql') {
|
||||
log.info('Connecting to MySQL database...');
|
||||
sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASS, {
|
||||
dialect: 'mysql',
|
||||
host: process.env.DB_HOST,
|
||||
logging: log.debug
|
||||
});
|
||||
} else {
|
||||
log.info('Using SQLite storage');
|
||||
sequelize = new Sequelize({
|
||||
dialect: 'sqlite',
|
||||
storage: 'user/storage.db',
|
||||
logging: log.debug
|
||||
});
|
||||
}
|
||||
|
||||
class Ticket extends Model {}
|
||||
Ticket.init({
|
||||
channel: DataTypes.STRING,
|
||||
creator: DataTypes.STRING,
|
||||
open: DataTypes.BOOLEAN,
|
||||
topic: DataTypes.TEXT
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'ticket'
|
||||
});
|
||||
|
||||
Ticket.sync();
|
||||
|
||||
/**
|
||||
* event loader
|
||||
*/
|
||||
const events = fs.readdirSync('src/events').filter(file => file.endsWith('.js'));
|
||||
for (const file of events) {
|
||||
const event = require(`./events/${file}`);
|
||||
client.events.set(event.event, event);
|
||||
client.on(event.event, e => client.events.get(event.event).execute(client, e, Ticket));
|
||||
log.console(log.format(`> Loaded &7${event.event}&f event`));
|
||||
}
|
||||
|
||||
/**
|
||||
* command loader
|
||||
*/
|
||||
const commands = fs.readdirSync('src/commands').filter(file => file.endsWith('.js'));
|
||||
for (const file of commands) {
|
||||
const command = require(`./commands/${file}`);
|
||||
client.commands.set(command.name, command);
|
||||
log.console(log.format(`> Loaded &7${config.prefix}${command.name}&f command`));
|
||||
}
|
||||
|
||||
log.info(`Loaded ${events.length} events and ${commands.length} commands`);
|
||||
|
||||
process.on('unhandledRejection', error => {
|
||||
log.warn('An error was not caught');
|
||||
log.error(`Uncaught error: \n${error.stack}`);
|
||||
});
|
||||
|
||||
client.login(process.env.TOKEN);
|
22
src/utils/archive.js
Normal file
22
src/utils/archive.js
Normal file
@ -0,0 +1,22 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const ChildLogger = require('leekslazylogger').ChildLogger;
|
||||
const log = new ChildLogger();
|
||||
|
||||
module.exports.create = (client, channel) => {
|
||||
|
||||
};
|
||||
|
||||
module.exports.addUser = (client, channel, user) => {
|
||||
|
||||
};
|
||||
|
||||
module.exports.addMessage = (client, channel, message) => {
|
||||
|
||||
};
|
31
src/utils/banner.js
Normal file
31
src/utils/banner.js
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const { version, homepage } = require('../../package.json');
|
||||
module.exports = (leeks) => {
|
||||
console.log(leeks.colours.cyan(`
|
||||
######## #### ###### ###### ####### ######## ########
|
||||
## ## ## ## ## ## ## ## ## ## ## ## ##
|
||||
## ## ## ## ## ## ## ## ## ## ##
|
||||
## ## ## ###### ## ## ## ######## ## ##
|
||||
## ## ## ## ## ## ## ## ## ## ##
|
||||
## ## ## ## ## ## ## ## ## ## ## ## ##
|
||||
######## #### ###### ###### ####### ## ## ########
|
||||
|
||||
######## #### ###### ## ## ######## ######## ######
|
||||
## ## ## ## ## ## ## ## ## ##
|
||||
## ## ## ## ## ## ## ##
|
||||
## ## ## ##### ###### ## ######
|
||||
## ## ## ## ## ## ## ##
|
||||
## ## ## ## ## ## ## ## ## ##
|
||||
## #### ###### ## ## ######## ## ######
|
||||
`));
|
||||
console.log(leeks.colours.cyanBright(`DiscordTickets bot v${version} by eartharoid`));
|
||||
console.log(leeks.colours.cyanBright(homepage));
|
||||
console.log('\n\n');
|
||||
};
|
31
src/utils/updater.js
Normal file
31
src/utils/updater.js
Normal file
@ -0,0 +1,31 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const ChildLogger = require('leekslazylogger').ChildLogger;
|
||||
const log = new ChildLogger();
|
||||
const fetch = require('node-fetch');
|
||||
const config = require('../../user/config');
|
||||
let {version} = require('../../package.json');
|
||||
version = 'v' + version;
|
||||
|
||||
module.exports = () => {
|
||||
if(!config.updater)
|
||||
return;
|
||||
|
||||
fetch('https://api.github.com/repos/eartharoid/DiscordTickets/releases')
|
||||
.then(res => res.json())
|
||||
.then(json => {
|
||||
const update = json[0];
|
||||
|
||||
if (version !== update.tag_name) {
|
||||
log.notice('There is an update available for Discord Tickets');
|
||||
log.info(`Download "&f${update.name}&3" from &6https://github.com/eartharoid/DiscordTickets/releases/`);
|
||||
log.notice(`You currently have ${version}; The latest is ${update.tag_name}`);
|
||||
}
|
||||
});
|
||||
};
|
79
user/config.js
Normal file
79
user/config.js
Normal file
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* ###############################################################################################
|
||||
* ____ _ _____ _ _
|
||||
* | _ \ (_) ___ ___ ___ _ __ __| | |_ _| (_) ___ | | __ ___ | |_ ___
|
||||
* | | | | | | / __| / __| / _ \ | '__| / _` | | | | | / __| | |/ / / _ \ | __| / __|
|
||||
* | |_| | | | \__ \ | (__ | (_) | | | | (_| | | | | | | (__ | < | __/ | |_ \__ \
|
||||
* |____/ |_| |___/ \___| \___/ |_| \__,_| |_| |_| \___| |_|\_\ \___| \__| |___/
|
||||
*
|
||||
* ---------------------
|
||||
* Quick Start
|
||||
* ---------------------
|
||||
*
|
||||
* > For detailed instructions, visit the GitHub repository and read the documentation:
|
||||
* https://github.com/eartharoid/DiscordTickets/#readme
|
||||
*
|
||||
* > IMPORTANT: Also rename 'user/example.env' to 'user/.env' and edit the TOKEN
|
||||
*
|
||||
* ---------------------
|
||||
* Support
|
||||
* ---------------------
|
||||
*
|
||||
* > Information: https://github.com/eartharoid/DiscordTickets/#readme
|
||||
* > Discord Support Server: https://go.eartharoid.me/discord
|
||||
* > Wiki: https://github.com/eartharoid/DiscordTickets/wiki
|
||||
*
|
||||
* ###############################################################################################
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
prefix: '-',
|
||||
name: 'DiscordTickets',
|
||||
activities: ['-new', 'with tickets', 'for new tickets'], /** @INFO " | {PRE}help" */
|
||||
activity_types: ['PLAYING', 'PLAYING', 'WATCHING'], /** @INFO paired */
|
||||
colour: '#009999',
|
||||
err_colour: '#E74C3C',
|
||||
|
||||
guild: '451745464480432129', // ID of your guild
|
||||
staff_role: '451745586564169728', // ID of your Support Team role
|
||||
|
||||
tickets: {
|
||||
category: '620272351988285480', // ID of your tickets category
|
||||
send_img: true,
|
||||
ping: 'here', /** @INFO here, everyone, staff, false */
|
||||
text: `Hello there, {{ tag }}!
|
||||
A member of staff will assist you shortly.
|
||||
In the mean time, please describe your issue in as much detail as possible! :)`, // {{ name }} and {{ tag }}
|
||||
pin: false,
|
||||
max: 3 /** @INFO OPEN */
|
||||
},
|
||||
|
||||
transcripts: {
|
||||
text: {
|
||||
enabled: true,
|
||||
keep_for: 90,
|
||||
},
|
||||
web: {
|
||||
enabled: false,
|
||||
server: 'https://tickets.example.com', // WITHOUT! trailing /
|
||||
}
|
||||
},
|
||||
|
||||
storage: {
|
||||
type: 'sqlite'
|
||||
},
|
||||
|
||||
logs: {
|
||||
files: {
|
||||
enabled: true,
|
||||
keep_for: 7
|
||||
},
|
||||
discord: {
|
||||
enabled: true,
|
||||
channel: '573957980152791080' // ID of your log channel
|
||||
}
|
||||
},
|
||||
|
||||
debug: false,
|
||||
updater: false /** @INFO ENABLE BY DEFAULT */
|
||||
};
|
8
user/example.env
Normal file
8
user/example.env
Normal file
@ -0,0 +1,8 @@
|
||||
TOKEN=
|
||||
|
||||
ARCHIVES_KEY=
|
||||
|
||||
DB_HOST=
|
||||
DB_NAME=
|
||||
DB_USER=
|
||||
DB_PASS=
|
BIN
user/images/how-can-i-help-1.gif
Normal file
BIN
user/images/how-can-i-help-1.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 MiB |
BIN
user/images/how-can-i-help-2.gif
Normal file
BIN
user/images/how-can-i-help-2.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 MiB |
BIN
user/images/how-can-we-help.gif
Normal file
BIN
user/images/how-can-we-help.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.6 MiB |
0
user/transcripts/raw/.gitkeep
Normal file
0
user/transcripts/raw/.gitkeep
Normal file
0
user/transcripts/text/.gitkeep
Normal file
0
user/transcripts/text/.gitkeep
Normal file
Loading…
Reference in New Issue
Block a user