mirror of
https://github.com/Hessenuk/DiscordTickets.git
synced 2024-12-22 15:53:08 +02:00
Set up for v3
This commit is contained in:
parent
7763889a2d
commit
9863c45fdf
@ -11,7 +11,7 @@ module.exports = {
|
||||
'SharedArrayBuffer': 'readonly'
|
||||
},
|
||||
'parserOptions': {
|
||||
'ecmaVersion': 2018
|
||||
'ecmaVersion': 2021
|
||||
},
|
||||
'rules': {
|
||||
'indent': [
|
||||
|
16
.github/CONTRIBUTING.md
vendored
16
.github/CONTRIBUTING.md
vendored
@ -4,26 +4,20 @@
|
||||
|
||||
### 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.
|
||||
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 else hasn't already created a similar first.
|
||||
|
||||
### 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).
|
||||
~~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).~~
|
||||
|
||||
You can request new features on [Feedbacky](https://app.feedbacky.net/b/dsctickets/).
|
||||
|
||||
### 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).
|
||||
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 (if you need help or have questions), you can join the [Discord server](https://go.eartharoid.me/discord) or use the [Discussions tab](https://github.com/eartharoid/DiscordTickets/discussions).
|
||||
|
||||
## 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
|
||||
|
||||
**Note**: Create `user/dev.env` and `user/dev.config.js` for testing.
|
||||
|
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
18
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@ -1,23 +1,23 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Report an issue or bug
|
||||
title: ''
|
||||
labels: ''
|
||||
about: Report a bug
|
||||
title: '[BUG] '
|
||||
labels: 'bug'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
<!-- A clear and concise description of what the bug is -->
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behaviour:
|
||||
**To reproduce**
|
||||
<!-- Steps to reproduce the behaviour -->
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
<!-- A clear and concise description of what you expected to happen -->
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
<!-- If applicable, add screenshots to help explain your problem -->
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
||||
<!-- Add any other context about the problem here -->
|
||||
|
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
18
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@ -1,20 +1,24 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: ''
|
||||
title: '[FEATURE] '
|
||||
labels: 'feature_request'
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is.
|
||||
<!--
|
||||
Note that you can now submit feature requests on Feedbacky: https://app.feedbacky.net/b/dsctickets
|
||||
-->
|
||||
|
||||
**Is your feature request related to a problem?**
|
||||
<!-- A clear and concise description of what the problem is. Reference any relevant issues. -->
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
<!-- A clear and concise description of what you want to happen. -->
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
<!-- A clear and concise description of any alternative solutions or features you've considered. -->
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
||||
<!-- Add any other context or screenshots about the feature request here. -->
|
||||
|
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
16
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,22 +1,28 @@
|
||||
<!--
|
||||
Please read the CONTRIBUTING file (.github/CONTRIBUTING.md) before creating a pull request.
|
||||
Unless you are doing something small like fixing a typo, please create an issue first!
|
||||
-->
|
||||
|
||||
#### Information
|
||||
|
||||
> Check one
|
||||
<!-- Please select **one** by replacing the space with an `x`: `[X]` -->
|
||||
|
||||
- [ ] This includes major changes (breaking changes)
|
||||
- [ ] This includes minor changes (minimal usage changes, minor new features)
|
||||
- [ ] This includes patches (bug fixes, documentation changes etc)
|
||||
- [ ] This includes patches (bug or typo fixes)
|
||||
- [ ] This is includes **only** documentation changes
|
||||
|
||||
#### Is this related to an issue?
|
||||
|
||||
> Reference any issues here
|
||||
<!-- Reference any issues here -->
|
||||
|
||||
#### Changes made
|
||||
|
||||
> Describe your changes
|
||||
<!-- Describe your changes -->
|
||||
|
||||
#### Confirmations
|
||||
|
||||
> Check all that apply
|
||||
<!-- Select **all that apply** by replacing the space with an `x`: `[X]` -->
|
||||
|
||||
- [ ] I have updated any necessary documentation
|
||||
- [ ] This uses consistent code style
|
||||
|
15
.github/SECURITY.md
vendored
15
.github/SECURITY.md
vendored
@ -1,14 +1,15 @@
|
||||
# Security Policy
|
||||
# Security policy
|
||||
|
||||
## Supported Versions
|
||||
## Supported versions
|
||||
|
||||
Release versions that will receive security updates.
|
||||
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.x | ✅ |
|
||||
| < 2.0 | ❌ |
|
||||
| Version | Supported |
|
||||
| ------- | -------------- |
|
||||
| 3.x | ✅ |
|
||||
| 2.x | ⚠️ Deprecated |
|
||||
| < 2.0 | ❌ |
|
||||
|
||||
## Reporting a Vulnerability
|
||||
## Reporting a vulnerability
|
||||
|
||||
If you find a vulnerability, please [email me](mailto:contact@eartharoid.me).
|
||||
|
15
.github/workflows/deploy-docs.yml
vendored
Normal file
15
.github/workflows/deploy-docs.yml
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
name: Build and deploy docs
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.x
|
||||
- run: pip install -r requirements.txt
|
||||
- run: mkdocs gh-deploy --force
|
14
.gitignore
vendored
14
.gitignore
vendored
@ -1,10 +1,10 @@
|
||||
logs/
|
||||
# directories
|
||||
.vscode/
|
||||
node_modules/
|
||||
user/dev.env
|
||||
user/dev.config.js
|
||||
user/storage.db
|
||||
user/transcripts/text/*.txt
|
||||
user/transcripts/raw/*.log
|
||||
user/transcripts/raw/entities/*.json
|
||||
logs/
|
||||
site/
|
||||
|
||||
# files
|
||||
.env
|
||||
user/config.js
|
||||
*.code-workspace
|
||||
|
@ -1,3 +1,4 @@
|
||||
# Please download from [releases](https://github.com/eartharoid/DiscordTickets/releases) or [v2 branch](https://github.com/eartharoid/DiscordTickets/tree/v2) - master branch is currently a work in progress
|
||||
# DiscordTickets
|
||||
|
||||
[![Run on Repl.it](https://repl.it/badge/github/eartharoid/DiscordTickets)](https://repl.it/github/eartharoid/DiscordTickets) [![GitHub issues](https://img.shields.io/github/issues/eartharoid/DiscordTickets?style=flat-square)](https://github.com/eartharoid/DiscordTickets/issues) [![GitHub stars](https://img.shields.io/github/stars/eartharoid/DiscordTickets?style=flat-square)](https://github.com/eartharoid/DiscordTickets/stargazers) [![GitHub forks](https://img.shields.io/github/forks/eartharoid/DiscordTickets?style=flat-square)](https://github.com/eartharoid/DiscordTickets/network) [![GitHub license](https://img.shields.io/github/license/eartharoid/DiscordTickets?style=flat-square)](https://github.com/eartharoid/DiscordTickets/blob/master/LICENSE) ![Codacy grade](https://img.shields.io/codacy/grade/14e6851c85444424b75b8bc3f93e93db?logo=codacy&style=flat-square) [![Discord support server](https://discordapp.com/api/guilds/451745464480432129/embed.png?style=shield)](https://discord.gg/pXc9vyC)
|
||||
|
3
docs/.pages
Normal file
3
docs/.pages
Normal file
@ -0,0 +1,3 @@
|
||||
arrange:
|
||||
- index.md
|
||||
- ...
|
1
docs/README
Normal file
1
docs/README
Normal file
@ -0,0 +1 @@
|
||||
This documentation is intended to be access through the website (https://eartharoid.github.io/discordtickets)
|
BIN
docs/img/favicon.ico
Normal file
BIN
docs/img/favicon.ico
Normal file
Binary file not shown.
After Width: | Height: | Size: 104 KiB |
BIN
docs/img/logo.png
Normal file
BIN
docs/img/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
1
docs/index.md
Normal file
1
docs/index.md
Normal file
@ -0,0 +1 @@
|
||||
# Home
|
24
docs/stylesheets/extra.css
Normal file
24
docs/stylesheets/extra.css
Normal file
@ -0,0 +1,24 @@
|
||||
a code {
|
||||
color: var(--md-primary-fg-color) !important;
|
||||
background-color: rgba(213, 0, 44, 0.1) !important;
|
||||
transition: color 125ms;
|
||||
transition: background-color 125ms
|
||||
}
|
||||
|
||||
a code:hover {
|
||||
/* ONLY WORKS ON SLATE THEME
|
||||
color: #81E8ED !important;
|
||||
background-color: rgba(129, 232, 237, 0.1) !important; */
|
||||
color: var(--md-primary-fg-color--dark) !important;
|
||||
}
|
||||
|
||||
.md-announce {
|
||||
background-color: var(--md-primary-fg-color--dark) !important;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.md-announce a, .md-announce a:hover {
|
||||
color: white !important;
|
||||
/* text-decoration: underline; */
|
||||
font-weight: bold;
|
||||
}
|
6
example.env
Normal file
6
example.env
Normal file
@ -0,0 +1,6 @@
|
||||
DISCORD_TOKEN=
|
||||
|
||||
DB_HOST=
|
||||
DB_NAME=
|
||||
DB_USER=
|
||||
DB_PASS=
|
82
mkdocs.yml
Normal file
82
mkdocs.yml
Normal file
@ -0,0 +1,82 @@
|
||||
# Project information
|
||||
site_name: DiscordTickets
|
||||
site_description: An open-source & self-hosted Discord bot for ticket management.
|
||||
site_author: eartharoid
|
||||
site_url: https://eartharoid.github.io/discordtickets
|
||||
|
||||
# Repository
|
||||
repo_name: eartharoid/DiscordTickets
|
||||
repo_url: https://github.com/eartharoid/DiscordTickets
|
||||
edit_uri: blob/master/docs/
|
||||
|
||||
# Copyright
|
||||
copyright: '© 2021 Isaac Saunders'
|
||||
|
||||
# Configuration
|
||||
extra_css:
|
||||
- stylesheets/extra.css
|
||||
|
||||
theme:
|
||||
name: material
|
||||
language: en
|
||||
|
||||
custom_dir: overrides/
|
||||
|
||||
palette:
|
||||
scheme: default
|
||||
primary: primary
|
||||
accent: primary
|
||||
|
||||
font:
|
||||
text: Roboto
|
||||
code: Roboto Mono
|
||||
|
||||
features:
|
||||
- instant
|
||||
- tabs
|
||||
- navigation.expand
|
||||
|
||||
logo: /img/logo.png
|
||||
favicon: /img/favicon.ico
|
||||
|
||||
# Extras
|
||||
extra:
|
||||
social:
|
||||
- icon: fontawesome/brands/github-alt
|
||||
link: https://github.com/eartharoid
|
||||
- icon: fontawesome/brands/twitter
|
||||
link: https://twitter.com/eartharoid
|
||||
|
||||
plugins:
|
||||
- search # necessary for search to work
|
||||
- git-revision-date-localized # last modified date at bottom of page
|
||||
- awesome-pages # custom nav order
|
||||
|
||||
# Extensions
|
||||
markdown_extensions:
|
||||
- admonition
|
||||
- codehilite:
|
||||
guess_lang: false
|
||||
- toc:
|
||||
permalink: true
|
||||
- footnotes
|
||||
- meta
|
||||
# pymd
|
||||
- pymdownx.arithmatex
|
||||
- pymdownx.betterem:
|
||||
smart_enable: all
|
||||
- pymdownx.caret
|
||||
- pymdownx.critic
|
||||
- pymdownx.details
|
||||
- pymdownx.emoji:
|
||||
emoji_index: !!python/name:materialx.emoji.twemoji
|
||||
emoji_generator: !!python/name:materialx.emoji.to_svg
|
||||
- pymdownx.inlinehilite
|
||||
- pymdownx.magiclink
|
||||
- pymdownx.mark
|
||||
- pymdownx.smartsymbols
|
||||
- pymdownx.superfences
|
||||
- pymdownx.tasklist:
|
||||
custom_checkbox: true
|
||||
- pymdownx.tabbed
|
||||
- pymdownx.tilde
|
2
overrides/announce.html
Normal file
2
overrides/announce.html
Normal file
@ -0,0 +1,2 @@
|
||||
<span class="twemoji">{% include ".icons/material/alert-decagram.svg" %}</span>
|
||||
<a href="/changelog">See what's new in v3.0.</a>
|
2
overrides/main.html
Normal file
2
overrides/main.html
Normal file
@ -0,0 +1,2 @@
|
||||
{% extends "base.html" %}
|
||||
{% block announce %}{% include "announce.html" ignore missing %}{% endblock %}
|
2915
package-lock.json
generated
2915
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@eartharoid/discordtickets",
|
||||
"version": "2.1.3",
|
||||
"version": "3.0.0",
|
||||
"private": true,
|
||||
"description": "An open-source & self-hosted Discord bot for ticket management.",
|
||||
"main": "src/index.js",
|
||||
@ -34,7 +34,7 @@
|
||||
"test": "echo \"Nothing to test! Run with 'npm start'\" && exit 1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
"node": ">=14"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -42,8 +42,8 @@
|
||||
},
|
||||
"keywords": [
|
||||
"discord",
|
||||
"bot",
|
||||
"tickets"
|
||||
"tickets",
|
||||
"bot"
|
||||
],
|
||||
"author": "eartharoid",
|
||||
"license": "GPL-3.0",
|
||||
|
2462
pnpm-lock.yaml
Normal file
2462
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load Diff
3
requirements.txt
Normal file
3
requirements.txt
Normal file
@ -0,0 +1,3 @@
|
||||
mkdocs-material
|
||||
mkdocs-awesome-pages-plugin
|
||||
mkdocs-git-revision-date-localized-plugin
|
@ -1,111 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const { MessageEmbed } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: 'add',
|
||||
description: 'Add a member to a ticket channel',
|
||||
usage: '<@member> [... #channel]',
|
||||
aliases: ['none'],
|
||||
example: 'add @member to #ticket-23',
|
||||
args: true,
|
||||
async execute(client, message, args, log, {config, Ticket}) {
|
||||
const guild = client.guilds.cache.get(config.guild);
|
||||
|
||||
const notTicket = new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **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(guild.name, 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('❌ **Channel is not a ticket**')
|
||||
.setDescription(`${channel} is not a ticket channel.`);
|
||||
return message.channel.send(notTicket);
|
||||
}
|
||||
}
|
||||
|
||||
if (message.author.id !== ticket.creator && !message.member.roles.cache.has(config.staff_role)) {
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **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(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
let member = guild.member(message.mentions.users.first() || guild.members.cache.get(args[0]));
|
||||
|
||||
if (!member) {
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **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(guild.name, 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 MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(member.user.username, member.user.displayAvatarURL())
|
||||
.setTitle('**Member added**')
|
||||
.setDescription(`${member} has been added by ${message.author}`)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(member.user.username, member.user.displayAvatarURL())
|
||||
.setTitle('✅ **Member added**')
|
||||
.setDescription(`${member} has been added to <#${ticket.channel}>`)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
|
||||
log.info(`${message.author.tag} added a user to a ticket (#${message.channel.id})`);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
// command ends here
|
||||
},
|
||||
};
|
@ -1,238 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const { MessageEmbed } = require('discord.js');
|
||||
const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
const archive = require('../modules/archive');
|
||||
|
||||
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, log, { config, Ticket }) {
|
||||
const guild = client.guilds.cache.get(config.guild);
|
||||
|
||||
const notTicket = new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **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(guild.name, guild.iconURL());
|
||||
|
||||
let ticket;
|
||||
let channel = message.mentions.channels.first();
|
||||
// || client.channels.resolve(await Ticket.findOne({ where: { id: args[0] } }).channel) // channels.fetch()
|
||||
|
||||
if (!channel) {
|
||||
channel = message.channel;
|
||||
|
||||
ticket = await Ticket.findOne({
|
||||
where: {
|
||||
channel: channel.id
|
||||
}
|
||||
});
|
||||
if (!ticket) return message.channel.send(notTicket);
|
||||
} else {
|
||||
ticket = await Ticket.findOne({
|
||||
where: {
|
||||
channel: channel.id
|
||||
}
|
||||
});
|
||||
if (!ticket) {
|
||||
notTicket
|
||||
.setTitle('❌ **Channel is not a ticket**')
|
||||
.setDescription(`${channel} is not a ticket channel.`);
|
||||
return message.channel.send(notTicket);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let paths = {
|
||||
text: join(__dirname, `../../user/transcripts/text/${ticket.get('channel')}.txt`),
|
||||
log: join(__dirname, `../../user/transcripts/raw/${ticket.get('channel')}.log`),
|
||||
json: join(__dirname, `../../user/transcripts/raw/entities/${ticket.get('channel')}.json`)
|
||||
};
|
||||
|
||||
if (message.author.id !== ticket.creator && !message.member.roles.cache.has(config.staff_role))
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **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(guild.name, guild.iconURL())
|
||||
);
|
||||
|
||||
|
||||
if (config.commands.close.confirmation) {
|
||||
let success;
|
||||
let pre = fs.existsSync(paths.text) || fs.existsSync(paths.log)
|
||||
? `You will be able to view an archived version later with \`${config.prefix}transcript ${ticket.id}\``
|
||||
: '';
|
||||
|
||||
let confirm = await message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❔ Are you sure?')
|
||||
.setDescription(`${pre}\n**React with ✅ to confirm.**`)
|
||||
.setFooter(guild.name + ' | Expires in 15 seconds', guild.iconURL())
|
||||
);
|
||||
|
||||
await confirm.react('✅');
|
||||
|
||||
const collector = confirm.createReactionCollector(
|
||||
(r, u) => r.emoji.name === '✅' && u.id === message.author.id, {
|
||||
time: 15000
|
||||
});
|
||||
|
||||
collector.on('collect', async () => {
|
||||
if (channel.id !== message.channel.id) {
|
||||
channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('**Ticket closed**')
|
||||
.setDescription(`Ticket closed by ${message.author}`)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
confirm.reactions.removeAll();
|
||||
confirm.edit(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(`✅ **Ticket ${ticket.id} closed**`)
|
||||
.setDescription('The channel will be automatically deleted in a few seconds, once the contents have been archived.')
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
|
||||
|
||||
if (channel.id !== message.channel.id)
|
||||
message.delete({
|
||||
timeout: 5000
|
||||
}).then(() => confirm.delete());
|
||||
|
||||
success = true;
|
||||
close();
|
||||
});
|
||||
|
||||
|
||||
collector.on('end', () => {
|
||||
if (!success) {
|
||||
confirm.reactions.removeAll();
|
||||
confirm.edit(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **Expired**')
|
||||
.setDescription('You took too long to react; confirmation failed.')
|
||||
.setFooter(guild.name, guild.iconURL()));
|
||||
|
||||
message.delete({
|
||||
timeout: 10000
|
||||
}).then(() => confirm.delete());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
close();
|
||||
}
|
||||
|
||||
|
||||
async function close () {
|
||||
let users = [];
|
||||
|
||||
if (config.transcripts.text.enabled || config.transcripts.web.enabled) {
|
||||
let u = await client.users.fetch(ticket.creator);
|
||||
if (u) {
|
||||
let dm;
|
||||
try {
|
||||
dm = u.dmChannel || await u.createDM();
|
||||
} catch (e) {
|
||||
log.warn(`Could not create DM channel with ${u.tag}`);
|
||||
}
|
||||
|
||||
let res = {};
|
||||
const embed = new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(`Ticket ${ticket.id}`)
|
||||
.setFooter(guild.name, guild.iconURL());
|
||||
|
||||
if (fs.existsSync(paths.text)) {
|
||||
embed.addField('Text transcript', 'See attachment');
|
||||
res.files = [{
|
||||
attachment: paths.text,
|
||||
name: `ticket-${ticket.id}-${ticket.get('channel')}.txt`
|
||||
}];
|
||||
}
|
||||
|
||||
if (fs.existsSync(paths.log) && fs.existsSync(paths.json)) {
|
||||
let data = JSON.parse(fs.readFileSync(paths.json));
|
||||
for (u in data.entities.users) users.push(u);
|
||||
embed.addField('Web archive', await archive.export(Ticket, channel)); // this will also delete these files
|
||||
}
|
||||
|
||||
if (embed.fields.length < 1) {
|
||||
embed.setDescription(`No text transcripts or archive data exists for ticket ${ticket.id}`);
|
||||
}
|
||||
|
||||
res.embed = embed;
|
||||
|
||||
try {
|
||||
if (config.commands.close.send_transcripts) dm.send(res);
|
||||
if (config.transcripts.channel.length > 1) client.channels.cache.get(config.transcripts.channel).send(res);
|
||||
} catch (e) {
|
||||
message.channel.send('❌ Couldn\'t send DM or transcript log message');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// update database
|
||||
ticket.update({
|
||||
open: false
|
||||
}, {
|
||||
where: {
|
||||
channel: channel.id
|
||||
}
|
||||
});
|
||||
|
||||
// delete channel
|
||||
channel.delete({
|
||||
timeout: 5000
|
||||
});
|
||||
|
||||
log.info(`${message.author.tag} closed a ticket (#ticket-${ticket.id})`);
|
||||
|
||||
if (config.logs.discord.enabled) {
|
||||
let embed = new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(`Ticket ${ticket.id} closed`)
|
||||
.addField('Creator', `<@${ticket.creator}>`, true)
|
||||
.addField('Closed by', message.author, true)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
.setTimestamp();
|
||||
|
||||
if (users.length > 1)
|
||||
embed.addField('Members', users.map(u => `<@${u}>`).join('\n'));
|
||||
|
||||
client.channels.cache.get(config.logs.discord.channel).send(embed);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
@ -1,258 +0,0 @@
|
||||
/**
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const { MessageEmbed } = require('discord.js');
|
||||
const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
const config = require(join(__dirname, '../../user/', require('../').config));
|
||||
const archive = require('../modules/archive');
|
||||
const { plural } = require('../modules/utils');
|
||||
const { Op } = require('sequelize');
|
||||
const toTime = require('to-time-monthsfork');
|
||||
|
||||
// A slight modification to the 'close' command to allow multiple tickets to be closed at once
|
||||
|
||||
module.exports = {
|
||||
name: 'closeall',
|
||||
description: 'Closes all currently open tickets older than a specified time length',
|
||||
usage: '[time]',
|
||||
aliases: ['ca'],
|
||||
example: 'closeall 1mo 1w',
|
||||
args: false,
|
||||
disabled: !config.commands.closeall.enabled,
|
||||
async execute(client, message, args, log, {
|
||||
config,
|
||||
Ticket
|
||||
}) {
|
||||
const guild = client.guilds.cache.get(config.guild);
|
||||
|
||||
if (!message.member.roles.cache.has(config.staff_role))
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **No permission**')
|
||||
.setDescription('You do not have permission to use this command as you are not a staff member.')
|
||||
.addField('Usage', `\`${config.prefix}${this.name}${' ' + this.usage}\`\n`)
|
||||
.addField('Help', `Type \`${config.prefix}help ${this.name}\` for more information`)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
|
||||
let tickets;
|
||||
|
||||
if (args.length > 0) {
|
||||
let time, maxDate;
|
||||
let timestamp = args.join(' ');
|
||||
|
||||
try {
|
||||
time = toTime(timestamp).milliseconds();
|
||||
maxDate = new Date(Date.now() - time);
|
||||
} catch (error) {
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **Invalid Timestamp**')
|
||||
.setDescription(`The timestamp that you specified, \`${timestamp}\`, was invalid.`)
|
||||
.addField('Usage', `\`${config.prefix}${this.name}${' ' + this.usage}\`\n`)
|
||||
.addField('Help', `Type \`${config.prefix}help ${this.name}\` for more information`)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
tickets = await Ticket.findAndCountAll({
|
||||
where: {
|
||||
open: true,
|
||||
updatedAt: {
|
||||
[Op.lte]: maxDate,
|
||||
}
|
||||
},
|
||||
});
|
||||
} else {
|
||||
tickets = await Ticket.findAndCountAll({
|
||||
where: {
|
||||
open: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (tickets.count === 0)
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.display)
|
||||
.setTitle('❌ **No open tickets**')
|
||||
.setDescription('There are no open tickets to close.')
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
|
||||
log.info(`Found ${tickets.count} open tickets`);
|
||||
|
||||
if (config.commands.close.confirmation) {
|
||||
let success;
|
||||
let pre = config.transcripts.text.enabled || config.transcripts.web.enabled
|
||||
? `You will be able to view an archived version of each ticket later with \`${config.prefix}transcript <id>\``
|
||||
: '';
|
||||
|
||||
let confirm = await message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(`❔ Are you sure you want to close **${tickets.count}** tickets?`)
|
||||
.setDescription(`${pre}\n**React with ✅ to confirm.**`)
|
||||
.setFooter(guild.name + ' | Expires in 15 seconds', guild.iconURL())
|
||||
);
|
||||
|
||||
await confirm.react('✅');
|
||||
|
||||
const collector = confirm.createReactionCollector(
|
||||
(reaction, user) => reaction.emoji.name === '✅' && user.id === message.author.id, {
|
||||
time: 15000,
|
||||
});
|
||||
|
||||
collector.on('collect', async () => {
|
||||
message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(`**\`${tickets.count}\` tickets closed**`)
|
||||
.setDescription(`**\`${tickets.count}\`** tickets closed by ${message.author}`)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
|
||||
confirm.reactions.removeAll();
|
||||
confirm.edit(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(`✅ ** \`${tickets.count}\` tickets closed**`)
|
||||
.setDescription('The channels will be automatically deleted in a few seconds, once the contents have been archived.')
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
|
||||
message.delete({
|
||||
timeout: 5000,
|
||||
}).then(() => confirm.delete());
|
||||
|
||||
success = true;
|
||||
closeAll();
|
||||
});
|
||||
|
||||
collector.on('end', () => {
|
||||
if (!success) {
|
||||
confirm.reactions.removeAll();
|
||||
confirm.edit(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **Expired**')
|
||||
.setDescription('You took too long to react; confirmation failed.')
|
||||
.setFooter(guild.name, guild.iconURL()));
|
||||
|
||||
message.delete({
|
||||
timeout: 10000
|
||||
}).then(() => confirm.delete());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
closeAll();
|
||||
}
|
||||
|
||||
|
||||
async function closeAll() {
|
||||
tickets.rows.forEach(async ticket => {
|
||||
let users = [];
|
||||
|
||||
if (config.transcripts.text.enabled || config.transcripts.web.enabled) {
|
||||
let {
|
||||
channel,
|
||||
id,
|
||||
creator
|
||||
} = ticket;
|
||||
|
||||
let user = await client.users.fetch(creator);
|
||||
let paths = {
|
||||
text: join(__dirname, `../../user/transcripts/text/${channel}.txt`),
|
||||
log: join(__dirname, `../../user/transcripts/raw/${channel}.log`),
|
||||
json: join(__dirname, `../../user/transcripts/raw/entities/${channel}.json`)
|
||||
};
|
||||
|
||||
if (user) {
|
||||
let dm;
|
||||
try {
|
||||
dm = user.dmChannel || await user.createDM();
|
||||
} catch (e) {
|
||||
log.warn(`Could not create DM channel with ${user.tag}`);
|
||||
}
|
||||
|
||||
let res = {};
|
||||
const embed = new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username)
|
||||
.setTitle(`Ticket ${id}`)
|
||||
.setFooter(guild.name, guild.iconURL());
|
||||
|
||||
if (fs.existsSync(paths.text)) {
|
||||
embed.addField('Text Transcript', 'See attachment');
|
||||
res.files = [{
|
||||
attachment: paths.text,
|
||||
name: `ticket-${id}-${channel}.txt`
|
||||
}];
|
||||
}
|
||||
|
||||
if (fs.existsSync(paths.log) && fs.existsSync(paths.json)) {
|
||||
let data = JSON.parse(fs.readFileSync(paths.json));
|
||||
data.entities.users.forEach(u => users.push(u));
|
||||
embed.addField('Web archive', await archive.export(Ticket, channel));
|
||||
}
|
||||
|
||||
res.embed = embed;
|
||||
|
||||
try {
|
||||
if (config.commands.close.send_transcripts) dm.send(res);
|
||||
if (config.transcripts.channel.length > 1) client.channels.cache.get(config.transcripts.channel).send(res);
|
||||
} catch (e) {
|
||||
message.channel.send('❌ Couldn\'t send DM or transcript log message');
|
||||
}
|
||||
}
|
||||
|
||||
await Ticket.update({
|
||||
open: false,
|
||||
}, {
|
||||
where: {
|
||||
id,
|
||||
}
|
||||
});
|
||||
|
||||
log.info(log.format(`${message.author.tag} closed ticket &7${id}&f`));
|
||||
|
||||
client.channels.fetch(channel)
|
||||
.then(c => c.delete()
|
||||
.then(o => log.info(`Deleted channel with name: '#${o.name}' <${o.id}>`))
|
||||
.catch(e => log.error(e)))
|
||||
.catch(e => log.error(e));
|
||||
|
||||
if (config.logs.discord.enabled) {
|
||||
let embed = new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(`${tickets.count} ${plural('ticket', tickets.count)} closed (${config.prefix}closeall)`)
|
||||
.addField('Closed by', message.author, true)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
.setTimestamp();
|
||||
|
||||
if (users.length > 1)
|
||||
embed.addField('Members', users.map(u => `<@${u}>`).join('\n'));
|
||||
|
||||
client.channels.cache.get(config.logs.discord.channel).send(embed);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
};
|
@ -1,185 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const {
|
||||
MessageEmbed
|
||||
} = require('discord.js');
|
||||
const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
|
||||
module.exports = {
|
||||
name: 'delete',
|
||||
description: 'Delete a ticket. Similar to closing a ticket, but does not save transcript or archives.',
|
||||
usage: '[ticket]',
|
||||
aliases: ['del'],
|
||||
example: 'delete #ticket-17',
|
||||
args: false,
|
||||
async execute(client, message, _args, log, {
|
||||
config,
|
||||
Ticket
|
||||
}) {
|
||||
const guild = client.guilds.cache.get(config.guild);
|
||||
|
||||
const notTicket = new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **This isn\'t a ticket channel**')
|
||||
.setDescription('Use this command in the ticket channel you want to delete, 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(guild.name, guild.iconURL());
|
||||
|
||||
let ticket;
|
||||
let channel = message.mentions.channels.first();
|
||||
// || client.channels.resolve(await Ticket.findOne({ where: { id: args[0] } }).channel) // channels.fetch()
|
||||
|
||||
if (!channel) {
|
||||
channel = message.channel;
|
||||
|
||||
ticket = await Ticket.findOne({
|
||||
where: {
|
||||
channel: channel.id
|
||||
}
|
||||
});
|
||||
if (!ticket) return channel.send(notTicket);
|
||||
|
||||
} else {
|
||||
ticket = await Ticket.findOne({
|
||||
where: {
|
||||
channel: channel.id
|
||||
}
|
||||
});
|
||||
if (!ticket) {
|
||||
notTicket
|
||||
.setTitle('❌ **Channel is not a ticket**')
|
||||
.setDescription(`${channel} is not a ticket channel.`);
|
||||
return message.channel.send(notTicket);
|
||||
}
|
||||
|
||||
}
|
||||
if (message.author.id !== ticket.creator && !message.member.roles.cache.has(config.staff_role))
|
||||
return channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **No permission**')
|
||||
.setDescription(`You don't have permission to delete ${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(guild.name, guild.iconURL())
|
||||
);
|
||||
|
||||
|
||||
if (config.commands.delete.confirmation) {
|
||||
let success;
|
||||
let confirm = await message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❔ Are you sure?')
|
||||
.setDescription(
|
||||
`:warning: This action is **irreversible**, the ticket will be completely removed from the database.
|
||||
You will **not** be able to view a transcript/archive of the channel later.
|
||||
Use the \`close\` command instead if you don't want this behaviour.\n**React with ✅ to confirm.**`)
|
||||
.setFooter(guild.name + ' | Expires in 15 seconds', guild.iconURL())
|
||||
);
|
||||
|
||||
await confirm.react('✅');
|
||||
|
||||
const collector = confirm.createReactionCollector(
|
||||
(r, u) => r.emoji.name === '✅' && u.id === message.author.id, {
|
||||
time: 15000
|
||||
});
|
||||
|
||||
collector.on('collect', async () => {
|
||||
if (channel.id !== message.channel.id)
|
||||
channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('**Ticket deleted**')
|
||||
.setDescription(`Ticket deleted by ${message.author}`)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
|
||||
confirm.reactions.removeAll();
|
||||
confirm.edit(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(`✅ **Ticket ${ticket.id} deleted**`)
|
||||
.setDescription('The channel will be automatically deleted in a few seconds.')
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
|
||||
if (channel.id !== message.channel.id)
|
||||
message.delete({
|
||||
timeout: 5000
|
||||
}).then(() => confirm.delete());
|
||||
|
||||
success = true;
|
||||
del();
|
||||
});
|
||||
|
||||
collector.on('end', () => {
|
||||
if (!success) {
|
||||
confirm.reactions.removeAll();
|
||||
confirm.edit(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **Expired**')
|
||||
.setDescription('You took too long to react; confirmation failed.')
|
||||
.setFooter(guild.name, guild.iconURL()));
|
||||
|
||||
message.delete({
|
||||
timeout: 10000
|
||||
}).then(() => confirm.delete());
|
||||
}
|
||||
});
|
||||
} else {
|
||||
del();
|
||||
}
|
||||
|
||||
|
||||
async function del () {
|
||||
let txt = join(__dirname, `../../user/transcripts/text/${ticket.get('channel')}.txt`),
|
||||
raw = join(__dirname, `../../user/transcripts/raw/${ticket.get('channel')}.log`),
|
||||
json = join(__dirname, `../../user/transcripts/raw/entities/${ticket.get('channel')}.json`);
|
||||
|
||||
if (fs.existsSync(txt)) fs.unlinkSync(txt);
|
||||
if (fs.existsSync(raw)) fs.unlinkSync(raw);
|
||||
if (fs.existsSync(json)) fs.unlinkSync(json);
|
||||
|
||||
// update database
|
||||
ticket.destroy(); // remove ticket from database
|
||||
|
||||
// channel
|
||||
channel.delete({
|
||||
timeout: 5000
|
||||
});
|
||||
|
||||
|
||||
log.info(`${message.author.tag} deleted a ticket (#ticket-${ticket.id})`);
|
||||
|
||||
if (config.logs.discord.enabled) {
|
||||
client.channels.cache.get(config.logs.discord.channel).send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('Ticket deleted')
|
||||
.addField('Creator', `<@${ticket.creator}>`, true)
|
||||
.addField('Deleted by', message.author, true)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
.setTimestamp()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
};
|
@ -1,87 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const { MessageEmbed } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: 'help',
|
||||
description: 'Display help menu',
|
||||
usage: '[command]',
|
||||
aliases: ['command', 'commands'],
|
||||
example: 'help new',
|
||||
args: false,
|
||||
execute(client, message, args, log, {config}) {
|
||||
const guild = client.guilds.cache.get(config.guild);
|
||||
|
||||
const commands = Array.from(client.commands.values());
|
||||
|
||||
if (!args.length) {
|
||||
let cmds = [];
|
||||
|
||||
for (let command of commands) {
|
||||
if (command.hide || command.disabled) 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 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(guild.name, guild.iconURL())
|
||||
).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 MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setDescription(`❌ **Invalid command name** (\`${config.prefix}help\`)`)
|
||||
);
|
||||
|
||||
|
||||
const cmd = new 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
|
||||
},
|
||||
};
|
@ -1,205 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const { MessageEmbed } = require('discord.js');
|
||||
const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
const config = require(join(__dirname, '../../user/', require('../').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,
|
||||
disabled: !config.commands.new.enabled,
|
||||
async execute(client, message, args, log, {config, Ticket}) {
|
||||
|
||||
if (!config.commands.new.enabled) return; // stop if the command is disabled
|
||||
|
||||
|
||||
const guild = client.guilds.cache.get(config.guild);
|
||||
|
||||
const supportRole = guild.roles.cache.get(config.staff_role);
|
||||
|
||||
if (!supportRole)
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setTitle('❌ **Error**')
|
||||
.setDescription(`${config.name} has not been set up correctly. Could not find a 'support team' role with the id \`${config.staff_role}\``)
|
||||
.setFooter(guild.name, 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, 30);
|
||||
ticketList
|
||||
.push(`<#${tickets.rows[t].channel}>: \`${desc}${desc.length > 30 ? '...' : ''}\``);
|
||||
}
|
||||
|
||||
let m = await message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(`❌ **You already have ${tickets.count} or more open tickets**`)
|
||||
.setDescription(`Use \`${config.prefix}close\` to close unneeded tickets.\n\n${ticketList.join(',\n')}`)
|
||||
.setFooter(guild.name + ' | This message will be deleted in 15 seconds', 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 MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **Description too long**')
|
||||
.setDescription('Please limit your ticket topic to less than 256 characters. A short sentence will do.')
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
else if (topic.length < 1) {
|
||||
topic = config.tickets.default_topic.command;
|
||||
}
|
||||
|
||||
let ticket = await Ticket.create({
|
||||
channel: '',
|
||||
creator: message.author.id,
|
||||
open: true,
|
||||
archived: false,
|
||||
topic: topic
|
||||
});
|
||||
|
||||
let name = 'ticket-' + ticket.get('id');
|
||||
|
||||
guild.channels.create(name, {
|
||||
type: 'text',
|
||||
topic: `${message.author} | ${topic}`,
|
||||
parent: config.tickets.category,
|
||||
permissionOverwrites: [{
|
||||
id: guild.roles.everyone,
|
||||
deny: ['VIEW_CHANNEL', 'SEND_MESSAGES']
|
||||
},
|
||||
{
|
||||
id: client.user,
|
||||
allow: ['VIEW_CHANNEL', 'SEND_MESSAGES', 'ATTACH_FILES', 'READ_MESSAGE_HISTORY']
|
||||
},
|
||||
{
|
||||
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 MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('✅ **Ticket created**')
|
||||
.setDescription(`Your ticket has been created: ${c}`)
|
||||
.setFooter(client.user.username + ' | This message will be deleted in 15 seconds', client.user.displayAvatarURL())
|
||||
);
|
||||
|
||||
setTimeout(async () => {
|
||||
await message.delete();
|
||||
await m.delete();
|
||||
}, 15000);
|
||||
|
||||
// require('../modules/archive').create(client, c); // create files
|
||||
|
||||
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(join(__dirname, '../../user/images'));
|
||||
await c.send({
|
||||
files: [
|
||||
join(__dirname, '../../user/images', images[Math.floor(Math.random() * images.length)])
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
let text = config.tickets.text
|
||||
.replace(/{{ ?name ?}}/gmi, message.author.username)
|
||||
.replace(/{{ ?(tag|mention) ?}}/gmi, message.author);
|
||||
|
||||
|
||||
let w = await c.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setDescription(text)
|
||||
.addField('Topic', `\`${topic}\``)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
|
||||
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 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(guild.name, guild.iconURL())
|
||||
.setTimestamp()
|
||||
);
|
||||
|
||||
log.info(`${message.author.tag} created a new ticket (#${name})`);
|
||||
|
||||
|
||||
}).catch(log.error);
|
||||
|
||||
},
|
||||
};
|
@ -1,65 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const { MessageEmbed } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: 'panel',
|
||||
description: 'Create or a panel widget in the channel the command is used in. Note that there can only be 1 panel.',
|
||||
usage: '',
|
||||
aliases: ['widget'],
|
||||
args: false,
|
||||
permission: 'MANAGE_GUILD',
|
||||
async execute(client, message, _args, log, {config, Setting}) {
|
||||
const guild = client.guilds.cache.get(config.guild);
|
||||
|
||||
let msgID = await Setting.findOne({ where: { key: 'panel_msg_id' } });
|
||||
let chanID = await Setting.findOne({ where: { key: 'panel_chan_id' } });
|
||||
let panel;
|
||||
|
||||
if (!chanID) {
|
||||
chanID = await Setting.create({
|
||||
key: 'panel_chan_id',
|
||||
value: message.channel.id,
|
||||
});
|
||||
}
|
||||
|
||||
if (!msgID) {
|
||||
msgID = await Setting.create({
|
||||
key: 'panel_msg_id',
|
||||
value: '',
|
||||
});
|
||||
} else {
|
||||
try {
|
||||
panel = await client.channels.cache.get(chanID.get('value')).messages.fetch(msgID.get('value')); // get old panel message
|
||||
if (panel) {
|
||||
panel.delete({ reason: 'Creating new panel/widget' }).then(() => log.info('Deleted old panel')).catch(e => log.warn(e)); // delete old panel
|
||||
}
|
||||
} catch (e) {
|
||||
log.warn('Couldn\'t delete old panel');
|
||||
}
|
||||
}
|
||||
|
||||
message.delete();
|
||||
|
||||
panel = await message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setTitle(config.panel.title)
|
||||
.setDescription(config.panel.description)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
); // send new panel
|
||||
|
||||
let emoji = panel.guild.emojis.cache.get(config.panel.reaction) || config.panel.reaction;
|
||||
panel.react(emoji); // add reaction
|
||||
Setting.update({ value: message.channel.id }, { where: { key: 'panel_chan_id' }}); // update database
|
||||
Setting.update({ value: panel.id }, { where: { key: 'panel_msg_id' }}); // update database
|
||||
|
||||
log.info(`${message.author.tag} created a panel widget`);
|
||||
}
|
||||
};
|
@ -1,112 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const { MessageEmbed } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: 'remove',
|
||||
description: 'Remove a member from ticket channel',
|
||||
usage: '<@member> [... #channel]',
|
||||
aliases: ['none'],
|
||||
example: 'remove @member from #ticket-23',
|
||||
args: true,
|
||||
async execute(client, message, args, log, {config, Ticket}) {
|
||||
const guild = client.guilds.cache.get(config.guild);
|
||||
|
||||
const notTicket = new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **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(guild.name, 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('❌ **Channel is not a ticket**')
|
||||
.setDescription(`${channel} is not a ticket channel.`);
|
||||
return message.channel.send(notTicket);
|
||||
}
|
||||
}
|
||||
|
||||
if (message.author.id !== ticket.creator && !message.member.roles.cache.has(config.staff_role)) {
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **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(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
let member = guild.member(message.mentions.users.first() || guild.members.cache.get(args[0]));
|
||||
|
||||
if (!member || member.id === message.author.id || member.id === guild.me.id)
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **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(guild.name, 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 MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(member.user.username, member.user.displayAvatarURL())
|
||||
.setTitle('**Member removed**')
|
||||
.setDescription(`${member} has been removed by ${message.author}`)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(member.user.username, member.user.displayAvatarURL())
|
||||
.setTitle('✅ **Member removed**')
|
||||
.setDescription(`${member} has been removed from <#${ticket.channel}>`)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
|
||||
log.info(`${message.author.tag} removed a user from a ticket (#${message.channel.id})`);
|
||||
} catch (error) {
|
||||
log.error(error);
|
||||
}
|
||||
},
|
||||
};
|
@ -1,63 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author iFusion for eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const { MessageEmbed } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: 'rename',
|
||||
description: 'Rename a ticket channel',
|
||||
usage: '<new name>',
|
||||
aliases: ['none'],
|
||||
example: 'rename important-ticket',
|
||||
args: true,
|
||||
async execute(client, message, args, {config, Ticket}) {
|
||||
const guild = client.guilds.cache.get(config.guild);
|
||||
|
||||
let ticket = await Ticket.findOne({
|
||||
where: {
|
||||
channel: message.channel.id
|
||||
}
|
||||
});
|
||||
|
||||
if (!ticket) {
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **This isn\'t a ticket channel**')
|
||||
.setDescription('Use this command in the ticket channel you want to rename.')
|
||||
.addField('Usage', `\`${config.prefix}${this.name} ${this.usage}\`\n`)
|
||||
.addField('Help', `Type \`${config.prefix}help ${this.name}\` for more information`)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
if (!message.member.roles.cache.has(config.staff_role))
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **No permission**')
|
||||
.setDescription('You don\'t have permission to rename this channel 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(guild.name, guild.iconURL())
|
||||
);
|
||||
|
||||
message.channel.setName(args.join('-')); // new channel name
|
||||
|
||||
message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('✅ **Ticket updated**')
|
||||
.setDescription('The name has been changed.')
|
||||
.setFooter(client.user.username, client.user.displayAvatarURL())
|
||||
);
|
||||
}
|
||||
};
|
@ -1,34 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const { MessageEmbed } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: 'stats',
|
||||
description: 'View ticket stats.',
|
||||
usage: '',
|
||||
aliases: ['data', 'statistics'],
|
||||
|
||||
args: false,
|
||||
async execute(client, message, _args, {config, Ticket}) {
|
||||
const guild = client.guilds.cache.get(config.guild);
|
||||
|
||||
let open = await Ticket.count({ where: { open: true } });
|
||||
let closed = await Ticket.count({ where: { open: false } });
|
||||
|
||||
message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setTitle(':bar_chart: Statistics')
|
||||
.addField('Open tickets', open, true)
|
||||
.addField('Closed tickets', closed, true)
|
||||
.addField('Total tickets', open + closed, true)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
};
|
@ -1,116 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const { MessageEmbed } = require('discord.js');
|
||||
const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
|
||||
module.exports = {
|
||||
name: 'tickets',
|
||||
description: 'List your recent tickets to access transcripts / archives.',
|
||||
usage: '[@member]',
|
||||
aliases: ['list'],
|
||||
args: false,
|
||||
async execute(client, message, args, {config, Ticket}) {
|
||||
const guild = client.guilds.cache.get(config.guild);
|
||||
|
||||
const supportRole = guild.roles.cache.get(config.staff_role);
|
||||
if (!supportRole) {
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setTitle('❌ **Error**')
|
||||
.setDescription(`${config.name} has not been set up correctly. Could not find a 'support team' role with the id \`${config.staff_role}\``)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
let context = 'self';
|
||||
let user = message.mentions.users.first() || guild.members.cache.get(args[0]);
|
||||
|
||||
if (user) {
|
||||
if (!message.member.roles.cache.has(config.staff_role)) {
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **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(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
context = 'staff';
|
||||
} else user = message.author;
|
||||
|
||||
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 MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(user.username, user.displayAvatarURL())
|
||||
.setTitle(`${context === 'self' ? 'Your' : user.username + '\'s'} tickets`)
|
||||
.setFooter(guild.name + ' | This message will be deleted in 60 seconds', 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 = '';
|
||||
let c = closedTickets.rows[t].channel;
|
||||
if (config.transcripts.web.enabled || fs.existsSync(join(__dirname, `../../user/transcripts/text/${c}.txt`))) {
|
||||
transcript = `\n> Type \`${config.prefix}transcript ${closedTickets.rows[t].id}\` to view.`;
|
||||
}
|
||||
|
||||
closed.push(`> **#${closedTickets.rows[t].id}**: \`${desc}${desc.length > 20 ? '...' : ''}\`${transcript}`);
|
||||
|
||||
}
|
||||
|
||||
let pre = context === 'self' ? 'You have' : user.username + ' 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);
|
||||
|
||||
message.delete({timeout: 15000});
|
||||
|
||||
let channel;
|
||||
try {
|
||||
channel = message.author.dmChannel || await message.author.createDM();
|
||||
message.channel.send('Sent to DM').then(msg => msg.delete({timeout: 15000}));
|
||||
} catch (e) {
|
||||
channel = message.channel;
|
||||
}
|
||||
|
||||
let m = await channel.send(embed);
|
||||
m.delete({timeout: 60000});
|
||||
},
|
||||
};
|
@ -1,71 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const { MessageEmbed } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: 'topic',
|
||||
description: 'Edit a ticket topic',
|
||||
usage: '<topic>',
|
||||
aliases: ['edit'],
|
||||
example: 'topic need help error',
|
||||
args: true,
|
||||
async execute(client, message, args, {config, Ticket}) {
|
||||
const guild = client.guilds.cache.get(config.guild);
|
||||
|
||||
let ticket = await Ticket.findOne({
|
||||
where: {
|
||||
channel: message.channel.id
|
||||
}
|
||||
});
|
||||
|
||||
if (!ticket) {
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **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(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
let topic = args.join(' ');
|
||||
if (topic.length > 256) {
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **Description too long**')
|
||||
.setDescription('Please limit your ticket topic to less than 256 characters. A short sentence will do.')
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
message.channel.setTopic(`<@${ticket.creator}> | ` + topic);
|
||||
|
||||
Ticket.update({
|
||||
topic: topic
|
||||
}, {
|
||||
where: {
|
||||
channel: message.channel.id
|
||||
}
|
||||
});
|
||||
|
||||
message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('✅ **Ticket updated**')
|
||||
.setDescription('The topic has been changed.')
|
||||
.setFooter(client.user.username, client.user.displayAvatarURL())
|
||||
);
|
||||
}
|
||||
};
|
@ -1,95 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
|
||||
const {
|
||||
MessageEmbed
|
||||
} = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: 'transcript',
|
||||
description: 'Download a transcript',
|
||||
usage: '<ticket-id>',
|
||||
aliases: ['archive', 'download'],
|
||||
example: 'transcript 57',
|
||||
args: true,
|
||||
async execute(client, message, args, {config, Ticket}) {
|
||||
const guild = client.guilds.cache.get(config.guild);
|
||||
const id = args[0];
|
||||
|
||||
let ticket = await Ticket.findOne({
|
||||
where: {
|
||||
id: id,
|
||||
open: false
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
if (!ticket) {
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **Unknown ticket**')
|
||||
.setDescription('Couldn\'t find a closed ticket with that ID')
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
if (message.author.id !== ticket.creator && !message.member.roles.cache.has(config.staff_role)) {
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **No permission**')
|
||||
.setDescription(`You don't have permission to view ticket ${id} as it does not belong to you and you are not staff.`)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
let res = {};
|
||||
const embed = new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle(`Ticket ${id}`)
|
||||
.setFooter(guild.name, guild.iconURL());
|
||||
|
||||
let file = `../../user/transcripts/text/${ticket.channel}.txt`;
|
||||
if (fs.existsSync(join(__dirname, file))) {
|
||||
embed.addField('Text transcript', 'See attachment');
|
||||
res.files = [
|
||||
{
|
||||
attachment: join(__dirname, file),
|
||||
name: `ticket-${id}-${ticket.channel}.txt`
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
const BASE_URL = config.transcripts.web.server;
|
||||
if (config.transcripts.web.enabled) embed.addField('Web archive', `${BASE_URL}/${ticket.creator}/${ticket.channel}`);
|
||||
|
||||
if (embed.fields.length < 1) embed.setDescription(`No text transcripts or archive data exists for ticket ${id}`);
|
||||
|
||||
res.embed = embed;
|
||||
|
||||
let channel;
|
||||
try {
|
||||
channel = message.author.dmChannel || await message.author.createDM();
|
||||
} catch (e) {
|
||||
channel = message.channel;
|
||||
}
|
||||
|
||||
channel.send(res).then(m => {
|
||||
if (channel.id === message.channel.id) m.delete({timeout: 15000});
|
||||
});
|
||||
message.delete({timeout: 1500});
|
||||
}
|
||||
};
|
@ -1,87 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const { MessageEmbed } = require('discord.js');
|
||||
|
||||
module.exports = {
|
||||
name: 'transfer',
|
||||
description: 'Transfer ownership of a ticket channel',
|
||||
usage: '<@member>',
|
||||
aliases: ['none'],
|
||||
example: 'transfer @user',
|
||||
args: true,
|
||||
async execute(client, message, args, { config, Ticket }) {
|
||||
const guild = client.guilds.cache.get(config.guild);
|
||||
|
||||
let ticket = await Ticket.findOne({
|
||||
where: {
|
||||
channel: message.channel.id
|
||||
}
|
||||
});
|
||||
|
||||
if (!ticket) {
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **This isn\'t a ticket channel**')
|
||||
.setDescription('Use this command in the ticket channel you want to change owner.')
|
||||
.addField('Usage', `\`${config.prefix}${this.name} ${this.usage}\`\n`)
|
||||
.addField('Help', `Type \`${config.prefix}help ${this.name}\` for more information`)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
if (!message.member.roles.cache.has(config.staff_role))
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **No permission**')
|
||||
.setDescription('You don\'t have permission to change ownership of this channel 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(guild.name, guild.iconURL())
|
||||
);
|
||||
|
||||
let member = guild.member(message.mentions.users.first() || guild.members.cache.get(args[0]));
|
||||
|
||||
if (!member) {
|
||||
return message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('❌ **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(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
message.channel.setTopic(`${member} | ${ticket.topic}`);
|
||||
|
||||
Ticket.update({
|
||||
creator: member.user.id
|
||||
}, {
|
||||
where: {
|
||||
channel: message.channel.id
|
||||
}
|
||||
});
|
||||
|
||||
message.channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(message.author.username, message.author.displayAvatarURL())
|
||||
.setTitle('✅ **Ticket transferred**')
|
||||
.setDescription(`Ownership of this ticket has been transferred to ${member}.`)
|
||||
.setFooter(client.user.username, client.user.displayAvatarURL())
|
||||
);
|
||||
}
|
||||
};
|
@ -1,14 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
event: 'debug',
|
||||
execute(_client, log, [e]) {
|
||||
log.debug(e);
|
||||
}
|
||||
};
|
@ -1,14 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
event: 'error',
|
||||
execute(_client, log, [e]) {
|
||||
log.error(e);
|
||||
}
|
||||
};
|
@ -1,113 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const { Collection, MessageEmbed } = require('discord.js');
|
||||
const archive = require('../modules/archive');
|
||||
|
||||
module.exports = {
|
||||
event: 'message',
|
||||
async execute(client, log, [message], {config, Ticket, Setting}) {
|
||||
|
||||
const guild = client.guilds.cache.get(config.guild);
|
||||
|
||||
if (message.channel.type === 'dm' && !message.author.bot) {
|
||||
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 **${guild}**.
|
||||
Type \`${config.prefix}new\` on the server to create a new ticket.`);
|
||||
} // stop here if is DM
|
||||
|
||||
/**
|
||||
* Ticket transcripts
|
||||
* (bots currently still allowed)
|
||||
*/
|
||||
|
||||
let ticket = await Ticket.findOne({ where: { channel: message.channel.id } });
|
||||
if (ticket) {
|
||||
archive.add(message); // add message to archive
|
||||
// Update the ticket updated at so closeall can get most recent
|
||||
ticket.changed('updatedAt', true);
|
||||
ticket.save();
|
||||
}
|
||||
|
||||
if (message.author.bot || message.author.id === client.user.id) return; // goodbye bots
|
||||
|
||||
|
||||
/**
|
||||
* Command handler
|
||||
* (no bots / self)
|
||||
*/
|
||||
|
||||
const regex = new RegExp(`^(<@!?${client.user.id}>|\\${config.prefix.toLowerCase()})\\s*`);
|
||||
if (!regex.test(message.content.toLowerCase())) return; // not a command
|
||||
|
||||
const [, prefix] = message.content.toLowerCase().match(regex);
|
||||
const args = message.content.slice(prefix.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; // not an existing command
|
||||
|
||||
if (message.guild.id !== guild.id) return message.reply(`This bot can only be used within the "${guild}" server`); // not in this server
|
||||
|
||||
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 MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setTitle('❌ No permission')
|
||||
.setDescription(`**You do not have permission to use the \`${command.name}\` command** (requires \`${command.permission}\`).`)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
if (command.args && !args.length) {
|
||||
return message.channel.send(
|
||||
new 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(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
if (!client.cooldowns.has(command.name)) client.cooldowns.set(command.name, new 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 MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setDescription(`❌ Please wait ${timeLeft.toFixed(1)} second(s) before reusing the \`${command.name}\` command.`)
|
||||
.setFooter(guild.name, guild.iconURL())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
timestamps.set(message.author.id, now);
|
||||
setTimeout(() => timestamps.delete(message.author.id), cooldownAmount);
|
||||
|
||||
try {
|
||||
command.execute(client, message, args, log, {config, Ticket, Setting});
|
||||
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(`❌ An error occurred whilst executing the \`${command.name}\` command.`);
|
||||
}
|
||||
}
|
||||
};
|
@ -1,46 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
|
||||
module.exports = {
|
||||
event: 'messageDelete',
|
||||
async execute(_client, log, [message], {config, Ticket}) {
|
||||
if (!config.transcripts.web.enabled) return;
|
||||
|
||||
if (message.partial) {
|
||||
try {
|
||||
await message.fetch();
|
||||
} catch (err) {
|
||||
log.warn('Failed to fetch deleted message');
|
||||
log.error(err.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let ticket = await Ticket.findOne({ where: { channel: message.channel.id } });
|
||||
if (!ticket) return;
|
||||
|
||||
|
||||
let path = `../../user/transcripts/raw/${message.channel.id}.log`;
|
||||
let embeds = [];
|
||||
for (let embed in message.embeds) embeds.push(message.embeds[embed].toJSON());
|
||||
|
||||
fs.appendFileSync(join(__dirname, path), JSON.stringify({
|
||||
id: message.id,
|
||||
author: message.author.id,
|
||||
content: message.content, // do not use cleanContent!
|
||||
time: message.createdTimestamp,
|
||||
embeds: embeds,
|
||||
attachments: [...message.attachments.values()],
|
||||
edited: message.edits.length > 1,
|
||||
deleted: true // delete the message
|
||||
}) + '\n');
|
||||
}
|
||||
};
|
@ -1,191 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const { MessageEmbed } = require('discord.js');
|
||||
const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
|
||||
module.exports = {
|
||||
event: 'messageReactionAdd',
|
||||
async execute(client, log, [r, u], {config, Ticket, Setting}) {
|
||||
if (r.partial) {
|
||||
try {
|
||||
await r.fetch();
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let panelID = await Setting.findOne({ where: { key: 'panel_msg_id' } });
|
||||
if (!panelID) return;
|
||||
|
||||
if (r.message.id !== panelID.get('value')) return;
|
||||
|
||||
if (u.id === client.user.id) return;
|
||||
|
||||
if (r.emoji.name !== config.panel.reaction && r.emoji.id !== config.panel.reaction) return;
|
||||
|
||||
let channel = r.message.channel;
|
||||
|
||||
const supportRole = channel.guild.roles.cache.get(config.staff_role);
|
||||
if (!supportRole) {
|
||||
return channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setTitle('❌ **Error**')
|
||||
.setDescription(`${config.name} has not been set up correctly. Could not find a 'support team' role with the id \`${config.staff_role}\``)
|
||||
.setFooter(channel.guild.name, channel.guild.iconURL())
|
||||
);
|
||||
}
|
||||
|
||||
// everything is cool
|
||||
|
||||
await r.users.remove(u.id); // effectively cancel reaction
|
||||
|
||||
let tickets = await Ticket.findAndCountAll({
|
||||
where: {
|
||||
creator: u.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, 30);
|
||||
ticketList
|
||||
.push(`<#${tickets.rows[t].channel}>: \`${desc}${desc.length > 30 ? '...' : ''}\``);
|
||||
}
|
||||
let dm = u.dmChannel || await u.createDM();
|
||||
|
||||
try {
|
||||
return dm.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(u.username, u.displayAvatarURL())
|
||||
.setTitle(`❌ **You already have ${tickets.count} or more open tickets**`)
|
||||
.setDescription(`Use \`${config.prefix}close\` in a server channel to close unneeded tickets.\n\n${ticketList.join(',\n')}`)
|
||||
.setFooter(channel.guild.name, channel.guild.iconURL())
|
||||
);
|
||||
} catch (e) {
|
||||
let m = await channel.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.err_colour)
|
||||
.setAuthor(u.username, u.displayAvatarURL())
|
||||
.setTitle(`❌ **You already have ${tickets.count} or more open tickets**`)
|
||||
.setDescription(`Use \`${config.prefix}close\` to close unneeded tickets.\n\n${ticketList.join(',\n')}`)
|
||||
.setFooter(channel.guild.name + ' | This message will be deleted in 15 seconds', channel.guild.iconURL())
|
||||
);
|
||||
return m.delete({ timeout: 15000 });
|
||||
}
|
||||
}
|
||||
|
||||
let topic = config.tickets.default_topic.command;
|
||||
|
||||
let ticket = await Ticket.create({
|
||||
channel: '',
|
||||
creator: u.id,
|
||||
open: true,
|
||||
archived: false,
|
||||
topic: topic
|
||||
});
|
||||
|
||||
let name = 'ticket-' + ticket.id;
|
||||
|
||||
channel.guild.channels.create(name, {
|
||||
type: 'text',
|
||||
topic: `${u} | ${topic}`,
|
||||
parent: config.tickets.category,
|
||||
permissionOverwrites: [{
|
||||
id: channel.guild.roles.everyone,
|
||||
deny: ['VIEW_CHANNEL', 'SEND_MESSAGES']
|
||||
},
|
||||
{
|
||||
id: client.user,
|
||||
allow: ['VIEW_CHANNEL', 'SEND_MESSAGES', 'ATTACH_FILES', 'READ_MESSAGE_HISTORY']
|
||||
},
|
||||
{
|
||||
id: channel.guild.member(u),
|
||||
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 (panel reaction)'
|
||||
}).then(async c => {
|
||||
Ticket.update({
|
||||
channel: c.id
|
||||
}, {
|
||||
where: {
|
||||
id: ticket.id
|
||||
}
|
||||
});
|
||||
|
||||
// require('../modules/archive').create(client, c); // create files
|
||||
|
||||
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 + `${u} has created a new ticket`);
|
||||
|
||||
if (config.tickets.send_img) {
|
||||
const images = fs.readdirSync(join(__dirname, '../../user/images'));
|
||||
await c.send({
|
||||
files: [
|
||||
join(__dirname, '../../user/images', images[Math.floor(Math.random() * images.length)])
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
let text = config.tickets.text
|
||||
.replace(/{{ ?name ?}}/gmi, u.username)
|
||||
.replace(/{{ ?(tag|mention) ?}}/gmi, u);
|
||||
|
||||
|
||||
let w = await c.send(
|
||||
new MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(u.username, u.displayAvatarURL())
|
||||
.setDescription(text)
|
||||
.addField('Topic', `\`${topic}\``)
|
||||
.setFooter(channel.guild.name, channel.guild.iconURL())
|
||||
);
|
||||
|
||||
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 MessageEmbed()
|
||||
.setColor(config.colour)
|
||||
.setAuthor(u.username, u.displayAvatarURL())
|
||||
.setTitle('New ticket (via panel)')
|
||||
.setDescription(`\`${topic}\``)
|
||||
.addField('Creator', u, true)
|
||||
.addField('Channel', c, true)
|
||||
.setFooter(channel.guild.name, channel.guild.iconURL())
|
||||
.setTimestamp()
|
||||
);
|
||||
|
||||
log.info(`${u.tag} created a new ticket (#${name}) via panel`);
|
||||
}).catch(log.error);
|
||||
}
|
||||
};
|
@ -1,53 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
|
||||
module.exports = {
|
||||
event: 'messageUpdate',
|
||||
async execute(_client, log, [o, n], {config, Ticket}) {
|
||||
if (!config.transcripts.web.enabled) return;
|
||||
|
||||
if (o.partial) {
|
||||
try {
|
||||
await o.fetch();
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (n.partial) {
|
||||
try {
|
||||
await n.fetch();
|
||||
} catch (err) {
|
||||
log.error(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let ticket = await Ticket.findOne({ where: { channel: n.channel.id } });
|
||||
if (!ticket) return;
|
||||
|
||||
let path = `../../user/transcripts/raw/${n.channel.id}.log`;
|
||||
let embeds = [];
|
||||
for (let embed in n.embeds) embeds.push({ ...n.embeds[embed] });
|
||||
|
||||
fs.appendFileSync(join(__dirname, path), JSON.stringify({
|
||||
id: n.id,
|
||||
author: n.author.id,
|
||||
content: n.content, // do not use cleanContent!
|
||||
time: n.createdTimestamp,
|
||||
embeds: embeds,
|
||||
attachments: [...n.attachments.values()],
|
||||
edited: true
|
||||
}) + '\n');
|
||||
|
||||
}
|
||||
};
|
@ -1,15 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
event: 'rateLimit',
|
||||
execute(_client, log, [limit]) {
|
||||
log.warn('Rate-limited! (Enable debug mode in config for details)');
|
||||
log.debug(limit);
|
||||
}
|
||||
};
|
@ -1,41 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const Logger = require('leekslazylogger');
|
||||
const log = new Logger();
|
||||
const config = require('../../user/' + require('../').config);
|
||||
|
||||
module.exports = {
|
||||
event: 'ready',
|
||||
execute(client, log) {
|
||||
log.success(`Authenticated as ${client.user.tag}`);
|
||||
|
||||
const updatePresence = () => {
|
||||
const presence = config.presences[Math.floor(Math.random() * config.presences.length)];
|
||||
let activity = presence.activity + config.append_presence;
|
||||
activity = activity.replace(/%s/g, config.prefix);
|
||||
client.user.setPresence({
|
||||
activity: {
|
||||
name: activity,
|
||||
type: presence.type.toUpperCase()
|
||||
}
|
||||
}).catch(log.error);
|
||||
log.debug(`Updated presence: ${activity} ${presence.type}`);
|
||||
};
|
||||
|
||||
updatePresence();
|
||||
setInterval(() => {
|
||||
updatePresence();
|
||||
}, 60000);
|
||||
|
||||
|
||||
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');
|
||||
}
|
||||
};
|
@ -1,14 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
event: 'warn',
|
||||
execute(_client, log, [e]) {
|
||||
log.warn(e);
|
||||
}
|
||||
};
|
192
src/index.js
192
src/index.js
@ -1,174 +1,22 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
* DiscordTickets Copyright (C) 2020 Isaac "eartharoid" Saunders
|
||||
* This program comes with ABSOLUTELY NO WARRANTY.
|
||||
* This is free software, and you are welcome to redistribute it
|
||||
* under certain conditions. See the included LICENSE file for details.
|
||||
*
|
||||
* DiscordTickets
|
||||
* Copyright (C) 2021 Isaac Saunders
|
||||
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
* @name @eartharoid/discordtickets
|
||||
* @description An open-source & self-hosted Discord bot for ticket management.
|
||||
* @copyright 2021 Isaac Saunders
|
||||
* @license GNU-GPLv3
|
||||
*/
|
||||
|
||||
const version = Number(process.version.split('.')[0].replace('v', ''));
|
||||
if (version < 12) return console.log('Please upgrade to Node v12 or higher');
|
||||
|
||||
const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
|
||||
let dev = fs.existsSync(join(__dirname, '../user/dev.env')) && fs.existsSync(join(__dirname, '../user/dev.config.js'));
|
||||
|
||||
require('dotenv').config({ path: join(__dirname, '../user/', dev ? 'dev.env' : '.env') });
|
||||
|
||||
module.exports.config = dev ? 'dev.config.js' : 'config.js';
|
||||
const config = require(join(__dirname, '../user/', module.exports.config));
|
||||
|
||||
const Discord = require('discord.js');
|
||||
const client = new Discord.Client({
|
||||
autoReconnect: true,
|
||||
partials: ['MESSAGE', 'CHANNEL', 'REACTION'],
|
||||
});
|
||||
|
||||
client.events = new Discord.Collection();
|
||||
client.commands = new Discord.Collection();
|
||||
client.cooldowns = new Discord.Collection();
|
||||
|
||||
const utils = require('./modules/utils');
|
||||
const leeks = require('leeks.js');
|
||||
|
||||
require('./modules/banner')(leeks); // big coloured text thing
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
require('./modules/updater')(); // check for updates
|
||||
|
||||
/**
|
||||
* storage
|
||||
*/
|
||||
const { Sequelize, Model, DataTypes } = require('sequelize');
|
||||
|
||||
let sequelize;
|
||||
|
||||
switch (config.storage.type) {
|
||||
case '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
|
||||
});
|
||||
break;
|
||||
case 'mariadb':
|
||||
log.info('Connecting to MariaDB database...');
|
||||
sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASS, {
|
||||
dialect: 'mariadb',
|
||||
host: process.env.DB_HOST,
|
||||
logging: log.debug
|
||||
});
|
||||
break;
|
||||
case 'postgre':
|
||||
log.info('Connecting to PostgreSQL database...');
|
||||
sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASS, {
|
||||
dialect: 'postgres',
|
||||
host: process.env.DB_HOST,
|
||||
logging: log.debug
|
||||
});
|
||||
break;
|
||||
case 'microsoft':
|
||||
log.info('Connecting to Microsoft SQL Server database...');
|
||||
sequelize = new Sequelize(process.env.DB_NAME, process.env.DB_USER, process.env.DB_PASS, {
|
||||
dialect: 'mssql',
|
||||
host: process.env.DB_HOST,
|
||||
logging: log.debug
|
||||
});
|
||||
break;
|
||||
default:
|
||||
log.info('Using SQLite storage');
|
||||
sequelize = new Sequelize({
|
||||
dialect: 'sqlite',
|
||||
storage: join(__dirname, '../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'
|
||||
});
|
||||
|
||||
class Setting extends Model {}
|
||||
Setting.init({
|
||||
key: DataTypes.STRING,
|
||||
value: DataTypes.STRING,
|
||||
}, {
|
||||
sequelize,
|
||||
modelName: 'setting'
|
||||
});
|
||||
|
||||
Ticket.sync();
|
||||
Setting.sync();
|
||||
|
||||
/**
|
||||
* event loader
|
||||
*/
|
||||
const events = fs.readdirSync(join(__dirname, '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, Setting));
|
||||
client.on(event.event, (e1, e2) => client.events.get(event.event).execute(client, log, [e1, e2], {config, Ticket, Setting}));
|
||||
log.console(log.format(`> Loaded &7${event.event}&f event`));
|
||||
}
|
||||
|
||||
/**
|
||||
* command loader
|
||||
*/
|
||||
const commands = fs.readdirSync(join(__dirname, '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`);
|
||||
|
||||
const one_day = 1000 * 60 * 60 * 24;
|
||||
const txt = '../user/transcripts/text';
|
||||
const clean = () => {
|
||||
const files = fs.readdirSync(join(__dirname, txt)).filter(file => file.endsWith('.txt'));
|
||||
let total = 0;
|
||||
for (const file of files) {
|
||||
let diff = (new Date() - new Date(fs.statSync(join(__dirname, txt, file)).mtime));
|
||||
if (Math.floor(diff / one_day) > config.transcripts.text.keep_for) {
|
||||
fs.unlinkSync(join(__dirname, txt, file));
|
||||
total++;
|
||||
}
|
||||
}
|
||||
if (total > 0) log.info(`Deleted ${total} old text ${utils.plural('transcript', total)}`);
|
||||
};
|
||||
|
||||
if (config.transcripts.text.enabled) {
|
||||
clean();
|
||||
setInterval(clean, one_day);
|
||||
}
|
||||
|
||||
process.on('unhandledRejection', error => {
|
||||
log.warn('An error was not caught');
|
||||
log.warn(`Uncaught ${error.name}: ${error.message}`);
|
||||
log.error(error);
|
||||
});
|
||||
|
||||
client.login(process.env.TOKEN);
|
||||
|
@ -1,161 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
const Logger = require('leekslazylogger');
|
||||
const log = new Logger();
|
||||
const Readlines = require('n-readlines');
|
||||
const fs = require('fs');
|
||||
const { join } = require('path');
|
||||
const dtf = require('@eartharoid/dtf');
|
||||
const config = require('../../user/' + require('../').config);
|
||||
const fetch = require('node-fetch');
|
||||
|
||||
module.exports.add = (message) => {
|
||||
|
||||
if (message.type !== 'DEFAULT') return;
|
||||
|
||||
if (config.transcripts.text.enabled) { // text transcripts
|
||||
let path = `../../user/transcripts/text/${message.channel.id}.txt`,
|
||||
time = dtf('HH:mm:ss n_D MMM YY', message.createdAt),
|
||||
msg = message.cleanContent;
|
||||
message.attachments.each(a => msg += '\n' + a.url);
|
||||
let string = `[${time}] [${message.author.tag}] :> ${msg}`;
|
||||
fs.appendFileSync(join(__dirname, path), string + '\n');
|
||||
}
|
||||
|
||||
if (config.transcripts.web.enabled) { // web archives
|
||||
let raw = `../../user/transcripts/raw/${message.channel.id}.log`,
|
||||
json = `../../user/transcripts/raw/entities/${message.channel.id}.json`;
|
||||
|
||||
let embeds = [];
|
||||
for (let embed in message.embeds) embeds.push({ ...message.embeds[embed] });
|
||||
|
||||
// message
|
||||
fs.appendFileSync(join(__dirname, raw), JSON.stringify({
|
||||
id: message.id,
|
||||
author: message.author.id,
|
||||
content: message.content, // do not use cleanContent, we want to include the mentions!
|
||||
time: message.createdTimestamp,
|
||||
embeds: embeds,
|
||||
attachments: [...message.attachments.values()]
|
||||
}) + '\n');
|
||||
|
||||
// channel entities
|
||||
if (!fs.existsSync(join(__dirname, json)))
|
||||
fs.writeFileSync(join(__dirname, json), JSON.stringify({
|
||||
entities: {
|
||||
users: {},
|
||||
channels: {},
|
||||
roles: {}
|
||||
}
|
||||
})); // create new
|
||||
|
||||
let data = JSON.parse(fs.readFileSync(join(__dirname, json)));
|
||||
|
||||
// if (!data.entities.users[message.author.id])
|
||||
data.entities.users[message.author.id] = {
|
||||
avatar: message.author.displayAvatarURL(),
|
||||
username: message.author.username,
|
||||
discriminator: message.author.discriminator,
|
||||
displayName: message.member.displayName,
|
||||
color: message.member.displayColor === 0 ? null : message.member.displayColor,
|
||||
badge: message.author.bot ? 'bot' : null
|
||||
};
|
||||
|
||||
// mentions.users
|
||||
message.mentions.members.each(m => data.entities.users[m.id] = { // for mentions
|
||||
avatar: m.user.displayAvatarURL(),
|
||||
username: m.user.username,
|
||||
discriminator: m.user.discriminator,
|
||||
displayName: m.user.displayName || m.user.username,
|
||||
color: m.displayColor === 0 ? null : m.displayColor,
|
||||
badge: m.user.bot ? 'bot' : null
|
||||
});
|
||||
|
||||
message.mentions.channels.each(c => data.entities.channels[c.id] = { // for mentions only
|
||||
name: c.name
|
||||
});
|
||||
|
||||
message.mentions.roles.each(r => data.entities.roles[r.id] = { // for mentions only
|
||||
name: r.name,
|
||||
color: r.color === 0 ? 7506394 : r.color
|
||||
});
|
||||
|
||||
fs.writeFileSync(join(__dirname, json), JSON.stringify(data));
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.export = (Ticket, channel) => new Promise((resolve, reject) => {
|
||||
|
||||
(async () => {
|
||||
let ticket = await Ticket.findOne({
|
||||
where: {
|
||||
channel: channel.id
|
||||
}
|
||||
});
|
||||
|
||||
let raw = `../../user/transcripts/raw/${channel.id}.log`,
|
||||
json = `../../user/transcripts/raw/entities/${channel.id}.json`;
|
||||
|
||||
if (!config.transcripts.web.enabled || !fs.existsSync(join(__dirname, raw)) || !fs.existsSync(join(__dirname, json))) return reject(false);
|
||||
|
||||
let data = JSON.parse(fs.readFileSync(join(__dirname, json)));
|
||||
|
||||
data.ticket = {
|
||||
id: ticket.id,
|
||||
name: channel.name,
|
||||
creator: ticket.creator,
|
||||
channel: channel.id,
|
||||
topic: channel.topic
|
||||
};
|
||||
|
||||
data.messages = [];
|
||||
let line;
|
||||
|
||||
const lineByLine = new Readlines(join(__dirname, raw));
|
||||
|
||||
// eslint-disable-next-line no-cond-assign
|
||||
while (line = lineByLine.next()) {
|
||||
let message = JSON.parse(line.toString('utf8'));
|
||||
let index = data.messages.findIndex(m => m.id === message.id);
|
||||
if (index === -1) data.messages.push(message);
|
||||
else data.messages[index] = message;
|
||||
}
|
||||
|
||||
let endpoint = config.transcripts.web.server;
|
||||
|
||||
if (endpoint[endpoint.length - 1] === '/') endpoint = endpoint.slice(0, -1);
|
||||
|
||||
endpoint += `/${data.ticket.creator}/${data.ticket.channel}/?key=${process.env.ARCHIVES_KEY}`;
|
||||
|
||||
fetch(encodeURI(endpoint), {
|
||||
method: 'post',
|
||||
body: JSON.stringify(data),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
})
|
||||
.then(res => res.json())
|
||||
.then(res => {
|
||||
if (res.status !== 200) {
|
||||
log.warn(res);
|
||||
return resolve(new Error(`${res.status} (${res.message})`));
|
||||
}
|
||||
|
||||
log.success(`Uploaded ticket #${ticket.id} archive to server`);
|
||||
|
||||
fs.unlinkSync(join(__dirname, raw));
|
||||
fs.unlinkSync(join(__dirname, json));
|
||||
|
||||
resolve(res.url);
|
||||
}).catch(e => {
|
||||
log.warn(e);
|
||||
return resolve(e);
|
||||
});
|
||||
})();
|
||||
});
|
@ -1,34 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const { version, homepage } = require('../../package.json');
|
||||
const link = require('terminal-link');
|
||||
|
||||
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 + '\n'));
|
||||
console.log(leeks.colours.cyanBright(`Please ${link('donate', 'https://ko-fi.com/eartharoid')} if you find this bot useful`));
|
||||
console.log('\n\n');
|
||||
};
|
@ -1,41 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
const Logger = require('leekslazylogger');
|
||||
const log = new Logger();
|
||||
const fetch = require('node-fetch');
|
||||
const config = require('../../user/' + require('../').config);
|
||||
let {version} = require('../../package.json');
|
||||
version = 'v' + version;
|
||||
const boxen = require('boxen');
|
||||
const link = require('terminal-link');
|
||||
|
||||
module.exports = async () => {
|
||||
if (!config.updater) return;
|
||||
const json = await (await fetch('https://api.github.com/repos/eartharoid/DiscordTickets/releases')).json();
|
||||
const update = json[0];
|
||||
let notice = [];
|
||||
|
||||
if (version !== update.tag_name) {
|
||||
log.notice(log.f(`There is an update available for Discord Tickets (${version} -> ${update.tag_name})`));
|
||||
|
||||
notice.push(`&6You are currently using &c${version}&6, the latest is &a${update.tag_name}&6.`);
|
||||
notice.push(`&6Download "&f${update.name}&6" from`);
|
||||
notice.push(link('&6the GitHub releases page', 'https://github.com/eartharoid/DiscordTickets/releases/'));
|
||||
|
||||
console.log(
|
||||
boxen(log.f(notice.join('\n')), {
|
||||
padding: 1,
|
||||
margin: 1,
|
||||
align: 'center',
|
||||
borderColor: 'yellow',
|
||||
borderStyle: 'round'
|
||||
})
|
||||
);
|
||||
}
|
||||
};
|
@ -1,18 +0,0 @@
|
||||
/**
|
||||
*
|
||||
* @name DiscordTickets
|
||||
* @author eartharoid <contact@eartharoid.me>
|
||||
* @license GNU-GPLv3
|
||||
*
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* @description Appends 's' to a word if plural number
|
||||
* @param {string} word - singular version of word
|
||||
* @param {number} num - integer
|
||||
*/
|
||||
plural(word, num) {
|
||||
return num !== 1 ? word + 's' : word;
|
||||
}
|
||||
};
|
120
user/config.js
120
user/config.js
@ -10,110 +10,48 @@
|
||||
* Quick Start
|
||||
* ---------------------
|
||||
*
|
||||
* > For detailed instructions, visit the GitHub repository and read the documentation:
|
||||
* https://github.com/eartharoid/DiscordTickets/wiki
|
||||
*
|
||||
* > IMPORTANT: Also edit the TOKEN in 'user/.env'
|
||||
* > For detailed instructions, visit the documentation: https://eartharoid.github.io/discordtickets
|
||||
*
|
||||
* ---------------------
|
||||
* Support
|
||||
* ---------------------
|
||||
*
|
||||
* > Information: https://github.com/eartharoid/DiscordTickets/#readme
|
||||
* > Discord Support Server: https://go.eartharoid.me/discord
|
||||
* > Wiki: https://github.com/eartharoid/DiscordTickets/wiki
|
||||
* > Discord support server: https://go.eartharoid.me/discord
|
||||
* > Wiki: https://eartharoid.github.io/discordtickets
|
||||
*
|
||||
* ###############################################################################################
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
prefix: '-',
|
||||
name: 'DiscordTickets',
|
||||
presences: [
|
||||
{
|
||||
activity: '%snew',
|
||||
type: 'PLAYING'
|
||||
},
|
||||
{
|
||||
activity: 'with tickets',
|
||||
type: 'PLAYING'
|
||||
},
|
||||
{
|
||||
activity: 'for new tickets',
|
||||
type: 'WATCHING'
|
||||
}
|
||||
],
|
||||
append_presence: ' | %shelp',
|
||||
colour: '#009999',
|
||||
err_colour: 'RED',
|
||||
cooldown: 3,
|
||||
guild: '', // ID of your guild (REQUIRED)
|
||||
staff_role: '', // ID of your Support Team role (REQUIRED)
|
||||
|
||||
tickets: {
|
||||
category: '', // ID of your tickets category (REQUIRED)
|
||||
send_img: true,
|
||||
ping: 'here',
|
||||
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! :)`,
|
||||
pin: false,
|
||||
max: 3,
|
||||
default_topic: {
|
||||
command: 'No topic given',
|
||||
panel: 'Created via panel'
|
||||
}
|
||||
},
|
||||
|
||||
commands: {
|
||||
close: {
|
||||
confirmation: true,
|
||||
send_transcripts: true
|
||||
},
|
||||
delete: {
|
||||
confirmation: true
|
||||
},
|
||||
new: {
|
||||
enabled: true
|
||||
},
|
||||
closeall: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
|
||||
transcripts: {
|
||||
text: {
|
||||
enabled: true,
|
||||
keep_for: 90,
|
||||
},
|
||||
web: {
|
||||
enabled: false,
|
||||
server: 'https://tickets.example.com',
|
||||
},
|
||||
channel: '' // ID of your archives channel
|
||||
},
|
||||
|
||||
panel: {
|
||||
title: 'Support Tickets',
|
||||
description: 'Need help? No problem! React to this panel to create a new support ticket so our Support Team can assist you.',
|
||||
reaction: '🧾'
|
||||
},
|
||||
|
||||
storage: {
|
||||
type: 'sqlite'
|
||||
},
|
||||
|
||||
logs: {
|
||||
files: {
|
||||
enabled: true,
|
||||
keep_for: 7
|
||||
},
|
||||
discord: {
|
||||
enabled: false,
|
||||
channel: '' // ID of your log channel
|
||||
}
|
||||
portal: {
|
||||
enabled: true,
|
||||
host: 'https://tickets.eartharoid.me'
|
||||
},
|
||||
presences: [
|
||||
{
|
||||
activity: '%snew | %shelp',
|
||||
type: 'PLAYING'
|
||||
},
|
||||
{
|
||||
activity: 'with tickets | %shelp',
|
||||
type: 'PLAYING'
|
||||
},
|
||||
{
|
||||
activity: 'for new tickets | %shelp',
|
||||
type: 'WATCHING'
|
||||
}
|
||||
],
|
||||
defaults: {
|
||||
prefix: '-',
|
||||
colour: '#009999',
|
||||
},
|
||||
logs: {
|
||||
enabled: true,
|
||||
keep_for: 30
|
||||
},
|
||||
|
||||
debug: false,
|
||||
updater: true
|
||||
update_notice: true,
|
||||
};
|
57
user/example.config.js
Normal file
57
user/example.config.js
Normal file
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* ###############################################################################################
|
||||
* ____ _ _____ _ _
|
||||
* | _ \ (_) ___ ___ ___ _ __ __| | |_ _| (_) ___ | | __ ___ | |_ ___
|
||||
* | | | | | | / __| / __| / _ \ | '__| / _` | | | | | / __| | |/ / / _ \ | __| / __|
|
||||
* | |_| | | | \__ \ | (__ | (_) | | | | (_| | | | | | | (__ | < | __/ | |_ \__ \
|
||||
* |____/ |_| |___/ \___| \___/ |_| \__,_| |_| |_| \___| |_|\_\ \___| \__| |___/
|
||||
*
|
||||
* ---------------------
|
||||
* Quick Start
|
||||
* ---------------------
|
||||
*
|
||||
* > For detailed instructions, visit the documentation: https://eartharoid.github.io/discordtickets
|
||||
*
|
||||
* ---------------------
|
||||
* Support
|
||||
* ---------------------
|
||||
*
|
||||
* > Discord support server: https://go.eartharoid.me/discord
|
||||
* > Wiki: https://eartharoid.github.io/discordtickets
|
||||
*
|
||||
* ###############################################################################################
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
storage: {
|
||||
type: 'sqlite'
|
||||
},
|
||||
portal: {
|
||||
enabled: false,
|
||||
host: 'https://tickets.example.com'
|
||||
},
|
||||
presences: [
|
||||
{
|
||||
activity: '%snew | %shelp',
|
||||
type: 'PLAYING'
|
||||
},
|
||||
{
|
||||
activity: 'with tickets | %shelp',
|
||||
type: 'PLAYING'
|
||||
},
|
||||
{
|
||||
activity: 'for new tickets | %shelp',
|
||||
type: 'WATCHING'
|
||||
}
|
||||
],
|
||||
defaults: {
|
||||
prefix: '-',
|
||||
colour: '#009999',
|
||||
},
|
||||
logs: {
|
||||
enabled: true,
|
||||
keep_for: 30
|
||||
},
|
||||
debug: false,
|
||||
update_notice: true,
|
||||
};
|
Binary file not shown.
Before Width: | Height: | Size: 698 KiB |
Binary file not shown.
Before Width: | Height: | Size: 577 KiB |
Binary file not shown.
Before Width: | Height: | Size: 1.3 MiB |
Loading…
Reference in New Issue
Block a user