feat: working hours (#304)

This commit is contained in:
Isaac 2023-03-17 21:45:53 +00:00
parent 8b692fa5e2
commit faf6edc463
No known key found for this signature in database
GPG Key ID: 0DE40AE37BBA5C33
4 changed files with 113 additions and 54 deletions

View File

@ -1,6 +1,6 @@
{
"name": "discord-tickets",
"version": "4.0.0-beta.8",
"version": "4.0.0-beta.9",
"private": "true",
"description": "An open-source Discord bot for ticket management",
"main": "src/",
@ -40,7 +40,7 @@
"node": ">=18"
},
"dependencies": {
"@discord-tickets/settings": "^2.1.1",
"@discord-tickets/settings": "^2.1.3",
"@eartharoid/dbf": "^0.3.3",
"@eartharoid/dtf": "^2.0.1",
"@eartharoid/i18n": "^1.2.1",
@ -65,6 +65,7 @@
"object-diffy": "^1.0.4",
"prisma": "^4.11.0",
"semver": "^7.3.8",
"spacetime": "^7.4.1",
"terminal-link": "^2.1.1",
"yaml": "^1.10.2"
},

View File

@ -3,7 +3,7 @@ lockfileVersion: 5.4
specifiers:
'@commitlint/cli': ^17.4.4
'@commitlint/config-conventional': ^17.4.4
'@discord-tickets/settings': ^2.1.1
'@discord-tickets/settings': ^2.1.3
'@eartharoid/dbf': ^0.3.3
'@eartharoid/dtf': ^2.0.1
'@eartharoid/i18n': ^1.2.1
@ -37,13 +37,14 @@ specifiers:
object-diffy: ^1.0.4
prisma: ^4.11.0
semver: ^7.3.8
spacetime: ^7.4.1
terminal-link: ^2.1.1
utf-8-validate: ^5.0.10
yaml: ^1.10.2
zlib-sync: ^0.1.8
dependencies:
'@discord-tickets/settings': 2.1.1_svelte@3.56.0
'@discord-tickets/settings': 2.1.3_svelte@3.57.0
'@eartharoid/dbf': 0.3.3_3cxu5zja4e2r5wmvge7mdcljwq
'@eartharoid/dtf': 2.0.1
'@eartharoid/i18n': 1.2.1
@ -68,6 +69,7 @@ dependencies:
object-diffy: 1.0.4
prisma: 4.11.0
semver: 7.3.8
spacetime: 7.4.1
terminal-link: 2.1.1
yaml: 1.10.2
@ -204,15 +206,15 @@ packages:
'@commitlint/execute-rule': 17.4.0
'@commitlint/resolve-extends': 17.4.4
'@commitlint/types': 17.4.4
'@types/node': 18.15.1
'@types/node': 18.15.3
chalk: 4.1.2
cosmiconfig: 8.1.0
cosmiconfig-typescript-loader: 4.3.0_jad34rn52rvsukepwt6d7357fa
cosmiconfig: 8.1.2
cosmiconfig-typescript-loader: 4.3.0_kpxsywhbgzx5buqgdnmsfobuwi
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
lodash.uniq: 4.5.0
resolve-from: 5.0.0
ts-node: 10.9.1_r2vohjtqb453xa4ljp4dw3sqb4
ts-node: 10.9.1_cbfmry4sbbh4vatmdrsmatfg5a
typescript: 4.9.5
transitivePeerDependencies:
- '@swc/core'
@ -293,8 +295,8 @@ packages:
'@jridgewell/trace-mapping': 0.3.9
dev: true
/@discord-tickets/settings/2.1.1_svelte@3.56.0:
resolution: {integrity: sha512-fqxK0SRyFPBddtccu6iFiJJvfToOQvkjazlYVguekEiYdmMdT61vi83r1SXYx3Bs0qKyXMUnmWGOA/+4jIOMGA==}
/@discord-tickets/settings/2.1.3_svelte@3.57.0:
resolution: {integrity: sha512-rKwt87oXBydudfnKkSmgbnNtCFbkYM0ZBuZLqSfl+H8N1kT+bylGoyXfAF2OLoEc5vK/EyBCEP9trZMO60774g==}
dependencies:
'@fortawesome/fontawesome-free': 6.3.0
'@skyra/discord-components-core': 3.6.0
@ -304,7 +306,7 @@ packages:
ms: 2.1.3
postcss: 8.4.21
sortablejs: 1.15.0
svelte-modals: 1.2.1_svelte@3.56.0
svelte-modals: 1.2.1_svelte@3.57.0
svelte-toasts: 1.1.2
transitivePeerDependencies:
- svelte
@ -317,7 +319,7 @@ packages:
'@discordjs/formatters': 0.2.0
'@discordjs/util': 0.2.0
'@sapphire/shapeshift': 3.8.1
discord-api-types: 0.37.35
discord-api-types: 0.37.36
fast-deep-equal: 3.1.3
ts-mixer: 6.0.3
tslib: 2.5.0
@ -332,7 +334,7 @@ packages:
resolution: {integrity: sha512-vn4oMSXuMZUm8ITqVOtvE7/fMMISj4cI5oLsR09PEQXHKeKDAMLltG/DWeeIs7Idfy6V8Fk3rn1e69h7NfzuNA==}
engines: {node: '>=16.9.0'}
dependencies:
discord-api-types: 0.37.35
discord-api-types: 0.37.36
dev: false
/@discordjs/rest/1.6.0:
@ -343,7 +345,7 @@ packages:
'@discordjs/util': 0.2.0
'@sapphire/async-queue': 1.5.0
'@sapphire/snowflake': 3.4.0
discord-api-types: 0.37.35
discord-api-types: 0.37.36
file-type: 18.2.1
tslib: 2.5.0
undici: 5.21.0
@ -376,8 +378,8 @@ packages:
resolution: {integrity: sha512-nMQdHrGgpw+vNL5DbivELW2K3KAUGaMvTjjmFsEPf8mUW8+LAgRjvfFn2gkJq1mnlD6HoqUaHqEL4YpWr2T5MA==}
dev: false
/@eslint-community/eslint-utils/4.2.0_eslint@8.36.0:
resolution: {integrity: sha512-gB8T4H4DEfX2IV9zGDJPOBgP1e/DbfCPDTtEqUMckpvzS1OYtva8JdFYBqMwYk7xAQ429WGF/UPqn8uQ//h2vQ==}
/@eslint-community/eslint-utils/4.3.0_eslint@8.36.0:
resolution: {integrity: sha512-v3oplH6FYCULtFuCeqyuTd9D2WKO937Dxdq+GmHOLL72TTRriLxz2VLlNfkZRsvj6PKnOPAtuT6dwrs/pA5DvA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
@ -639,13 +641,13 @@ packages:
resolution: {integrity: sha512-13aVNoleHyAMqd4lcfcfjEduV4QavlHn9P2TUlTUPQC8m8cd8n8wSUTDUxKhQbYpzJ0hn7AHnlmLixfiqQT4FQ==}
engines: {node: '>=v14.0.0'}
dependencies:
'@stencil/core': 2.22.2
'@stencil/core': 2.22.3
clsx: 1.2.1
hex-to-rgba: 2.0.1
dev: false
/@stencil/core/2.22.2:
resolution: {integrity: sha512-r+vbxsGNcBaV1VDOYW25lv4QfXTlNoIb5GpUX7rZ+cr59yqYCZC5tlV+IzX6YgHKW62ulCc9M3RYtTfHtNbNNw==}
/@stencil/core/2.22.3:
resolution: {integrity: sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==}
engines: {node: '>=12.10.0', npm: '>=6.0.0'}
hasBin: true
dev: false
@ -674,8 +676,8 @@ packages:
resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==}
dev: true
/@types/node/18.15.1:
resolution: {integrity: sha512-U2TWca8AeHSmbpi314QBESRk7oPjSZjDsR+c+H4ECC1l+kFgpZf8Ydhv3SJpPy51VyZHHqxlb6mTTqYNNRVAIw==}
/@types/node/18.15.3:
resolution: {integrity: sha512-p6ua9zBxz5otCmbpb5D3U4B5Nanw6Pk3PPyX05xnxbB/fRv71N7CPmORg7uAD5P70T0xmx1pzAx/FUfa5X+3cw==}
/@types/normalize-package-data/2.4.1:
resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==}
@ -684,7 +686,7 @@ packages:
/@types/ws/8.5.4:
resolution: {integrity: sha512-zdQDHKUgcX/zBc4GrwsE/7dVdAD8JR4EuiAXiiUhhfyIJXXb2+PrGshFyeXWQPMmmZ2XxgaqclgpIC7eTXc1mg==}
dependencies:
'@types/node': 18.15.1
'@types/node': 18.15.3
dev: false
/JSONStream/1.3.5:
@ -1313,7 +1315,7 @@ packages:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
dev: true
/cosmiconfig-typescript-loader/4.3.0_jad34rn52rvsukepwt6d7357fa:
/cosmiconfig-typescript-loader/4.3.0_kpxsywhbgzx5buqgdnmsfobuwi:
resolution: {integrity: sha512-NTxV1MFfZDLPiBMjxbHRwSh5LaLcPMwNdCutmnHJCKoVnlvldPWlllonKwrsRJ5pYZBIBGRWWU2tfvzxgeSW5Q==}
engines: {node: '>=12', npm: '>=6'}
peerDependencies:
@ -1322,14 +1324,14 @@ packages:
ts-node: '>=10'
typescript: '>=3'
dependencies:
'@types/node': 18.15.1
cosmiconfig: 8.1.0
ts-node: 10.9.1_r2vohjtqb453xa4ljp4dw3sqb4
'@types/node': 18.15.3
cosmiconfig: 8.1.2
ts-node: 10.9.1_cbfmry4sbbh4vatmdrsmatfg5a
typescript: 4.9.5
dev: true
/cosmiconfig/8.1.0:
resolution: {integrity: sha512-0tLZ9URlPGU7JsKq0DQOQ3FoRsYX8xDZ7xMiATQfaiGMz7EHowNkbU9u1coAOmnh9p/1ySpm0RB3JNWRXM5GCg==}
/cosmiconfig/8.1.2:
resolution: {integrity: sha512-rmpUFKMZiawLfug8sP4NbpBSOpWftZB6UACOLEiNbnRAYM1TzgQuTWlMYFRuPgmoTCkcOxSMwQJQpJmiXv/eHw==}
engines: {node: '>=14'}
dependencies:
import-fresh: 3.3.0
@ -1423,8 +1425,8 @@ packages:
engines: {node: '>=0.3.1'}
dev: true
/discord-api-types/0.37.35:
resolution: {integrity: sha512-iyKZ/82k7FX3lcmHiAvvWu5TmyfVo78RtghBV/YsehK6CID83k5SI03DKKopBcln+TiEIYw5MGgq7SJXSpNzMg==}
/discord-api-types/0.37.36:
resolution: {integrity: sha512-Nlxmp10UpVr/utgZ9uODQvG2Or+5w7LFrvFMswyeKC9l/+UaqGT6H0OVgEFhu9GEO4U6K7NNO5W8Carv7irnCA==}
dev: false
/discord.js/14.8.0_3cxu5zja4e2r5wmvge7mdcljwq:
@ -1438,7 +1440,7 @@ packages:
'@discordjs/util': 0.2.0
'@sapphire/snowflake': 3.4.0
'@types/ws': 8.5.4
discord-api-types: 0.37.35
discord-api-types: 0.37.36
fast-deep-equal: 3.1.3
lodash.snakecase: 4.1.1
tslib: 2.5.0
@ -1553,7 +1555,7 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true
dependencies:
'@eslint-community/eslint-utils': 4.2.0_eslint@8.36.0
'@eslint-community/eslint-utils': 4.3.0_eslint@8.36.0
'@eslint-community/regexpp': 4.4.0
'@eslint/eslintrc': 2.0.1
'@eslint/js': 8.36.0
@ -1655,8 +1657,8 @@ packages:
strip-final-newline: 2.0.0
dev: true
/execa/7.1.0:
resolution: {integrity: sha512-T6nIJO3LHxUZ6ahVRaxXz9WLEruXLqdcluA+UuTptXmLM7nDAn9lx9IfkxPyzEL21583qSt4RmL44pO71EHaJQ==}
/execa/7.1.1:
resolution: {integrity: sha512-wH0eMf/UXckdUYnO21+HDztteVv05rq2GXksxT4fCGeHkBhw1DROXh40wcjMcRqDOWE7iPJ4n3M7e2+YFP+76Q==}
engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0}
dependencies:
cross-spawn: 7.0.3
@ -1765,7 +1767,7 @@ packages:
rfdc: 1.3.0
secure-json-parse: 2.7.0
semver: 7.3.8
tiny-lru: 10.1.0
tiny-lru: 10.2.1
transitivePeerDependencies:
- supports-color
dev: false
@ -1882,7 +1884,7 @@ packages:
resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==}
engines: {node: '>=12'}
dependencies:
graceful-fs: 4.2.10
graceful-fs: 4.2.11
jsonfile: 6.1.0
universalify: 2.0.0
dev: false
@ -1891,7 +1893,7 @@ packages:
resolution: {integrity: sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw==}
engines: {node: '>=14.14'}
dependencies:
graceful-fs: 4.2.10
graceful-fs: 4.2.11
jsonfile: 6.1.0
universalify: 2.0.0
dev: true
@ -2007,8 +2009,8 @@ packages:
type-fest: 0.20.2
dev: true
/graceful-fs/4.2.10:
resolution: {integrity: sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==}
/graceful-fs/4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
/grapheme-splitter/1.0.4:
resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==}
@ -2309,7 +2311,7 @@ packages:
dependencies:
universalify: 2.0.0
optionalDependencies:
graceful-fs: 4.2.10
graceful-fs: 4.2.11
/jsonparse/1.3.1:
resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==}
@ -2378,7 +2380,7 @@ packages:
cli-truncate: 3.1.0
commander: 10.0.0
debug: 4.3.4
execa: 7.1.0
execa: 7.1.1
lilconfig: 2.1.0
listr2: 5.0.8
micromatch: 4.0.5
@ -2415,7 +2417,7 @@ packages:
resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==}
engines: {node: '>=4'}
dependencies:
graceful-fs: 4.2.10
graceful-fs: 4.2.11
parse-json: 4.0.0
pify: 3.0.0
strip-bom: 3.0.0
@ -3380,6 +3382,10 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/spacetime/7.4.1:
resolution: {integrity: sha512-khQpvLNMhHFzfFJslMfunqqsjOxdmoDDYX5Wh4qYb8N6f8vBPI6HvbT7Wb2wcMa+oP1Xh1HpEcwfPFrj8UfvHQ==}
dev: false
/spdx-correct/3.2.0:
resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==}
dependencies:
@ -3544,20 +3550,20 @@ packages:
engines: {node: '>= 0.4'}
dev: true
/svelte-modals/1.2.1_svelte@3.56.0:
/svelte-modals/1.2.1_svelte@3.57.0:
resolution: {integrity: sha512-7MEKUx5wb5YppkXWFGeRlYM5FMGEnpix39u9Y6GtCNTMXRDZ7DB2Z50IYLMRTMW5lOsCdtJgFbB0E3iZMKmsAA==}
peerDependencies:
svelte: ^3.0.0
dependencies:
svelte: 3.56.0
svelte: 3.57.0
dev: false
/svelte-toasts/1.1.2:
resolution: {integrity: sha512-m+yL4eEKXyJoyjTYaH1j1GFwF0Pi8YDqnVfwWPDmwi4712iZesv+TNCmToSNlav3R5Vkmc8ZBRkT8DOcu3sywQ==}
dev: false
/svelte/3.56.0:
resolution: {integrity: sha512-LvXiJbjdvJKwB/0CQyYpDX0q+hFqCyWmybzC2G6eK1tJJA/RSRCytTfNmjHv+RHlLuA70vWG7nXp6gbeErYvRA==}
/svelte/3.57.0:
resolution: {integrity: sha512-WMXEvF+RtAaclw0t3bPDTUe19pplMlfyKDsixbHQYgCWi9+O9VN0kXU1OppzrB9gPAvz4NALuoca2LfW2bOjTQ==}
engines: {node: '>= 8'}
dev: false
@ -3614,8 +3620,8 @@ packages:
readable-stream: 3.6.2
dev: true
/tiny-lru/10.1.0:
resolution: {integrity: sha512-8nOOqPPLrvE4gcBQZcvPZbJANNIwY0Mwu1h3kklq7mraCGG/JDNcu/QBZt4HQArGClrGxmQ3V/wgHy9xmK38gQ==}
/tiny-lru/10.2.1:
resolution: {integrity: sha512-cxcNyX4O50FDnB5x9jdEJupYC+9PPutt6wT16TaOtXeHfV9t41aFqg0VniB9YK5KmBeqmZaYriNMajm/5TtA7Q==}
engines: {node: '>=12'}
dev: false
@ -3666,7 +3672,7 @@ packages:
resolution: {integrity: sha512-k43M7uCG1AkTyxgnmI5MPwKoUvS/bRvLvUb7+Pgpdlmok8AoqmUaZxUUw8zKM5B1lqZrt41GjYgnvAi0fppqgQ==}
dev: false
/ts-node/10.9.1_r2vohjtqb453xa4ljp4dw3sqb4:
/ts-node/10.9.1_cbfmry4sbbh4vatmdrsmatfg5a:
resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==}
hasBin: true
peerDependencies:
@ -3685,7 +3691,7 @@ packages:
'@tsconfig/node12': 1.0.11
'@tsconfig/node14': 1.0.3
'@tsconfig/node16': 1.0.3
'@types/node': 18.15.1
'@types/node': 18.15.3
acorn: 8.8.2
acorn-walk: 8.2.0
arg: 4.1.3

View File

@ -420,3 +420,14 @@ ticket:
topic: Topic
title: Reference
released: ♻️ {user} has released this ticket.
working_hours:
next:
description:
We'll be back at <t:{timestamp}:F> (<t:{timestamp}:R>), although
you may receive a response before then.
title: 🕗 We're not working at the moment
today:
description:
You may receive a response before, but we don't start working until
<t:{timestamp}:t> today (<t:{timestamp}:R>).
title: 🕗 We're not working at the moment

View File

@ -18,6 +18,7 @@ const ExtendedEmbedBuilder = require('../embed');
const { logTicketEvent } = require('../logging');
const { isStaff } = require('../users');
const { Collection } = require('discord.js');
const spacetime = require('spacetime');
const Cryptr = require('cryptr');
const {
decrypt,
@ -440,8 +441,6 @@ module.exports = class TicketManager {
),
];
// TODO: !staff || workingHours
if (answers) {
embeds.push(
new ExtendedEmbedBuilder()
@ -618,6 +617,7 @@ module.exports = class TicketManager {
};
if (referencesTicketId) data.referencesTicket = { connect: { id: referencesTicketId } };
if (answers) data.questionAnswers = { createMany: { data: answers } };
await interaction.editReply({
components: [],
embeds: [
@ -645,7 +645,7 @@ module.exports = class TicketManager {
if (category.guild.archive && message) {
if (
await this.client.prisma.archivedMessage.findUnique({ where: { id: message.id } })||
await this.client.prisma.archivedMessage.findUnique({ where: { id: message.id } }) ||
await this.archiver.saveMessage(ticket.id, message, true)
) {
await this.client.prisma.ticket.update({
@ -682,6 +682,47 @@ module.exports = class TicketManager {
],
});
}
const workingHours = category.guild.workingHours;
const timezone = workingHours[0];
workingHours.shift(); // remove timezone
const now = spacetime.now(timezone);
const currentHours = workingHours[now.day()];
const start = now.time(currentHours[0]);
const end = now.time(currentHours[1]);
if (currentHours[0] === currentHours[1] || now.isAfter(end)) { // staff have the day off or have finished for the day
// first look for the next working day *this* week (after today)
let nextIndex = workingHours.findIndex((hours, i) => i > now.day() && hours[0] !== hours[1]);
// if there isn't one, look for the next working day *next* week (before and including today's weekday)
if (!nextIndex) nextIndex = workingHours.findIndex((hours, i) => i <= now.day() && hours[0] !== hours[1]);
if (nextIndex) {
const next = workingHours[nextIndex];
let then = now.add(nextIndex - now.day(), 'day');
if (nextIndex <= now.day()) then = then.add(1, 'week');
const timestamp = Math.ceil(then.time(next[0]).d.getTime() / 1000); // in seconds
await channel.send({
embeds: [
new ExtendedEmbedBuilder()
.setColor(category.guild.primaryColour)
.setTitle(getMessage('ticket.working_hours.next.title'))
.setDescription(getMessage('ticket.working_hours.next.description', { timestamp })),
],
});
}
} else if (now.isBefore(start)) { // staff haven't started working yet
const timestamp = Math.ceil(start.d.getTime() / 1000); // in seconds
await channel.send({
embeds: [
new ExtendedEmbedBuilder()
.setColor(category.guild.primaryColour)
.setTitle(getMessage('ticket.working_hours.today.title'))
.setDescription(getMessage('ticket.working_hours.today.description', { timestamp })),
],
});
}
// TODO: !staff
}
/**