@skyhelperbot/utils 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +11 -0
- package/dist/classes/LeaderBoardCard.d.ts +59 -0
- package/dist/classes/LeaderBoardCard.js +249 -0
- package/dist/classes/SkytimesUtils.d.ts +73 -0
- package/dist/classes/SkytimesUtils.js +119 -0
- package/dist/classes/UpdateEvent.d.ts +30 -0
- package/dist/classes/UpdateEvent.js +52 -0
- package/dist/classes/UpdateTs.d.ts +35 -0
- package/dist/classes/UpdateTs.js +62 -0
- package/dist/classes/WinnerCard.d.ts +13 -0
- package/dist/classes/WinnerCard.js +118 -0
- package/dist/classes/index.d.ts +7 -0
- package/dist/classes/index.js +7 -0
- package/dist/classes/shardsUtil.d.ts +41 -0
- package/dist/classes/shardsUtil.js +122 -0
- package/dist/classes/utils.d.ts +13 -0
- package/dist/classes/utils.js +22 -0
- package/dist/constants/eventDatas.d.ts +31 -0
- package/dist/constants/eventDatas.js +105 -0
- package/dist/constants/index.d.ts +5 -0
- package/dist/constants/index.js +5 -0
- package/dist/constants/shardsInfo.d.ts +50 -0
- package/dist/constants/shardsInfo.js +424 -0
- package/dist/constants/shardsTimeline.d.ts +19 -0
- package/dist/constants/shardsTimeline.js +57 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +5 -0
- package/dist/typings.d.ts +62 -0
- package/dist/typings.js +3 -0
- package/dist/utils/buildTimesHTML.d.ts +14 -0
- package/dist/utils/buildTimesHTML.js +208 -0
- package/dist/utils/index.d.ts +5 -0
- package/dist/utils/index.js +5 -0
- package/dist/utils/parseDateFormat.d.ts +9 -0
- package/dist/utils/parseDateFormat.js +25 -0
- package/dist/utils/parsePerms.d.ts +49 -0
- package/dist/utils/parsePerms.js +54 -0
- package/dist/utils/postToBin.d.ts +5 -0
- package/dist/utils/postToBin.js +15 -0
- package/dist/utils/recursiveReadDir.d.ts +15 -0
- package/dist/utils/recursiveReadDir.js +37 -0
- package/package.json +44 -0
- package/shared/assets/Point.png +0 -0
- package/shared/assets/Win.png +0 -0
- package/shared/assets/medal_champion_award_winner_olympic_icon_207790.png +0 -0
- package/shared/assets/server.svg +41 -0
- package/shared/fonts/circularstd-black.otf +0 -0
- package/shared/fonts/notoemoji-bold.ttf +0 -0
- package/shared/fonts/notosans-black.ttf +0 -0
- package/shared/fonts/notosans-jp-black.ttf +0 -0
- package/shared/fonts/notosans-kr-black.ttf +0 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Naiyar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
|
|
2
|
+
### <p align="center"> Utility Package for <a href="https://github.com/imnaiyar/skyhelper">SkyHelper</a> bot.</p>
|
|
3
|
+
|
|
4
|
+
This package contains various utilities for SkyHelper discord bot!
|
|
5
|
+
|
|
6
|
+
Run this to install
|
|
7
|
+
```
|
|
8
|
+
npm install skyhelper-utils
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
**Documentation**: https://utils.skyhelper.xyz/
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { userData, colorsType, LeaderboardOptions } from "../typings.js";
|
|
2
|
+
/**
|
|
3
|
+
* Represents a Quiz Leaderboard Card. A class for generating a leaderboard card for a quiz or game.
|
|
4
|
+
* @returns - The generated image buffer.
|
|
5
|
+
*/
|
|
6
|
+
export declare class LeaderboardCard {
|
|
7
|
+
private usersData;
|
|
8
|
+
private background;
|
|
9
|
+
private abbreviateNumber;
|
|
10
|
+
private opacity;
|
|
11
|
+
private scoreMessage;
|
|
12
|
+
private colors;
|
|
13
|
+
constructor(options: LeaderboardOptions);
|
|
14
|
+
/**
|
|
15
|
+
* .setUsersData
|
|
16
|
+
* @param usersData ```js
|
|
17
|
+
* [{ top: int, avatar: "string", tag: "string", score: int}, games: int]
|
|
18
|
+
* ```
|
|
19
|
+
* @example setUsersData([{top:1,avatar:"https://someone-image.png",tag:"fivesobes",score:5, games:8}])
|
|
20
|
+
*/
|
|
21
|
+
setUsersData(usersData: userData[]): this;
|
|
22
|
+
/**
|
|
23
|
+
* .setScoreMessage
|
|
24
|
+
* @param message Set Custom Score Message
|
|
25
|
+
* @example setScoreMessage("Message")
|
|
26
|
+
*/
|
|
27
|
+
setScoreMessage(message: string): this;
|
|
28
|
+
/**
|
|
29
|
+
* .setColors
|
|
30
|
+
* @param colors ```json
|
|
31
|
+
* {box: "hexcolor", username: "hexcolor", score: "hexcolor", firstRank: "hexcolor", secondRank: "hexcolor", thirdRank: "hexcolor"}
|
|
32
|
+
* ```
|
|
33
|
+
* @example
|
|
34
|
+
* setColors({ box: '#212121', username: '#ffffff', score: '#ffffff', firstRank: '#f7c716', secondRank: '#9e9e9e', thirdRank: '#94610f' })
|
|
35
|
+
*/
|
|
36
|
+
setColors(colors: colorsType): this;
|
|
37
|
+
/**
|
|
38
|
+
* .setabbreviateNumber
|
|
39
|
+
* @param bool must be "true" or "false"
|
|
40
|
+
* @example setabbreviateNumber(true)
|
|
41
|
+
*/
|
|
42
|
+
setabbreviateNumber(bool: boolean): this;
|
|
43
|
+
/**
|
|
44
|
+
* .setOpacity
|
|
45
|
+
* @param opacity must be between 0 and 1
|
|
46
|
+
* @example setOpacity(0.6)
|
|
47
|
+
*/
|
|
48
|
+
setOpacity(opacity?: number): this;
|
|
49
|
+
/**
|
|
50
|
+
* .setBackground
|
|
51
|
+
* @param type "image" or "color"
|
|
52
|
+
* @param value "url" or "hexcolor"
|
|
53
|
+
* @example setBackground("image","https://someone-image.png")
|
|
54
|
+
* @example setBackground("color","#000")
|
|
55
|
+
*/
|
|
56
|
+
setBackground(type: string, value: string): this;
|
|
57
|
+
build(): Promise<Buffer>;
|
|
58
|
+
private fillRoundRect;
|
|
59
|
+
}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { createCanvas, loadImage, GlobalFonts } from "@napi-rs/canvas";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Represents a Quiz Leaderboard Card. A class for generating a leaderboard card for a quiz or game.
|
|
5
|
+
* @returns - The generated image buffer.
|
|
6
|
+
*/
|
|
7
|
+
export class LeaderboardCard {
|
|
8
|
+
usersData;
|
|
9
|
+
background;
|
|
10
|
+
abbreviateNumber;
|
|
11
|
+
opacity;
|
|
12
|
+
scoreMessage;
|
|
13
|
+
colors;
|
|
14
|
+
constructor(options) {
|
|
15
|
+
this.usersData = options?.usersData || [];
|
|
16
|
+
this.background = {
|
|
17
|
+
type: "none",
|
|
18
|
+
background: "none",
|
|
19
|
+
};
|
|
20
|
+
this.abbreviateNumber = false;
|
|
21
|
+
this.opacity = 0;
|
|
22
|
+
this.scoreMessage = "";
|
|
23
|
+
this.colors = options?.colors || {
|
|
24
|
+
box: "#212121",
|
|
25
|
+
username: "#ffffff",
|
|
26
|
+
score: "#ffffff",
|
|
27
|
+
firstRank: "#f7c716",
|
|
28
|
+
secondRank: "#9e9e9e",
|
|
29
|
+
thirdRank: "#94610f",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* .setUsersData
|
|
34
|
+
* @param usersData ```js
|
|
35
|
+
* [{ top: int, avatar: "string", tag: "string", score: int}, games: int]
|
|
36
|
+
* ```
|
|
37
|
+
* @example setUsersData([{top:1,avatar:"https://someone-image.png",tag:"fivesobes",score:5, games:8}])
|
|
38
|
+
*/
|
|
39
|
+
setUsersData(usersData) {
|
|
40
|
+
if (usersData.length > 10) {
|
|
41
|
+
throw new Error("setUsersData values cannot be greater than 10.");
|
|
42
|
+
}
|
|
43
|
+
this.usersData = usersData;
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* .setScoreMessage
|
|
48
|
+
* @param message Set Custom Score Message
|
|
49
|
+
* @example setScoreMessage("Message")
|
|
50
|
+
*/
|
|
51
|
+
setScoreMessage(message) {
|
|
52
|
+
this.scoreMessage = message;
|
|
53
|
+
return this;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* .setColors
|
|
57
|
+
* @param colors ```json
|
|
58
|
+
* {box: "hexcolor", username: "hexcolor", score: "hexcolor", firstRank: "hexcolor", secondRank: "hexcolor", thirdRank: "hexcolor"}
|
|
59
|
+
* ```
|
|
60
|
+
* @example
|
|
61
|
+
* setColors({ box: '#212121', username: '#ffffff', score: '#ffffff', firstRank: '#f7c716', secondRank: '#9e9e9e', thirdRank: '#94610f' })
|
|
62
|
+
*/
|
|
63
|
+
setColors(colors) {
|
|
64
|
+
this.colors = colors;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* .setabbreviateNumber
|
|
69
|
+
* @param bool must be "true" or "false"
|
|
70
|
+
* @example setabbreviateNumber(true)
|
|
71
|
+
*/
|
|
72
|
+
setabbreviateNumber(bool) {
|
|
73
|
+
if (typeof bool !== "boolean") {
|
|
74
|
+
throw new Error("You must give a abbreviate number true or false argument.");
|
|
75
|
+
}
|
|
76
|
+
this.abbreviateNumber = bool;
|
|
77
|
+
return this;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* .setOpacity
|
|
81
|
+
* @param opacity must be between 0 and 1
|
|
82
|
+
* @example setOpacity(0.6)
|
|
83
|
+
*/
|
|
84
|
+
setOpacity(opacity = 0) {
|
|
85
|
+
if (opacity >= 0 && opacity <= 1) {
|
|
86
|
+
this.opacity = opacity;
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
throw new Error("The value of the opacity of setOpacity method must be between 0 and 1 (0 and 1 included).");
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* .setBackground
|
|
95
|
+
* @param type "image" or "color"
|
|
96
|
+
* @param value "url" or "hexcolor"
|
|
97
|
+
* @example setBackground("image","https://someone-image.png")
|
|
98
|
+
* @example setBackground("color","#000")
|
|
99
|
+
*/
|
|
100
|
+
setBackground(type, value) {
|
|
101
|
+
if (type === "color") {
|
|
102
|
+
if (/^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/.test(value)) {
|
|
103
|
+
this.background.type = "color";
|
|
104
|
+
this.background.background = value;
|
|
105
|
+
return this;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
throw new Error("Invalid color for the second argument in setForeground method. You must give a hexadecimal color.");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (type === "image") {
|
|
112
|
+
this.background.type = "image";
|
|
113
|
+
this.background.background = value;
|
|
114
|
+
return this;
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
throw new Error("The first argument of setBackground must be 'color' or 'image'.");
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async build() {
|
|
121
|
+
GlobalFonts.registerFromPath(path.join(__dirname, `../../shared/fonts/circularstd-black.otf`), "circular-std");
|
|
122
|
+
GlobalFonts.registerFromPath(path.join(__dirname, `../../shared/fonts/notosans-jp-black.ttf`), "noto-sans-jp");
|
|
123
|
+
GlobalFonts.registerFromPath(path.join(__dirname, `../../shared/fonts/notosans-black.ttf`), "noto-sans");
|
|
124
|
+
GlobalFonts.registerFromPath(path.join(__dirname, `../../shared/fonts/notoemoji-bold.ttf`), "noto-emoji");
|
|
125
|
+
GlobalFonts.registerFromPath(path.join(__dirname, `../../shared/fonts/notosans-kr-black.ttf`), "noto-sans-kr");
|
|
126
|
+
const abbreviateNumber = (value) => {
|
|
127
|
+
let newValue = value;
|
|
128
|
+
if (value >= 1000) {
|
|
129
|
+
const suffixes = ["", "K", "M", "B", "T"];
|
|
130
|
+
const suffixNum = Math.floor(("" + value).length / 3);
|
|
131
|
+
let shortValue = "";
|
|
132
|
+
for (let precision = 2; precision >= 1; precision--) {
|
|
133
|
+
shortValue = parseFloat((suffixNum != 0 ? value / Math.pow(1000, suffixNum) : value).toPrecision(precision));
|
|
134
|
+
const dotLessShortValue = (shortValue + "").replace(/[^a-zA-Z 0-9]+/g, "");
|
|
135
|
+
if (dotLessShortValue.length <= 2) {
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (typeof shortValue === "number" && shortValue % 1 != 0)
|
|
140
|
+
shortValue = shortValue.toFixed(1);
|
|
141
|
+
newValue = shortValue + suffixes[suffixNum];
|
|
142
|
+
}
|
|
143
|
+
return newValue.toString();
|
|
144
|
+
};
|
|
145
|
+
const yuksek = this.usersData.length * 74.5;
|
|
146
|
+
const canvas = createCanvas(680, yuksek);
|
|
147
|
+
const ctx = canvas.getContext("2d");
|
|
148
|
+
ctx.globalAlpha = 1;
|
|
149
|
+
if (this.background.type === "color") {
|
|
150
|
+
ctx.fillStyle = this.background.background;
|
|
151
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
152
|
+
}
|
|
153
|
+
else if (this.background.type === "image") {
|
|
154
|
+
try {
|
|
155
|
+
ctx.drawImage(await loadImage(this.background.background), 0, 0, canvas.width, canvas.height);
|
|
156
|
+
}
|
|
157
|
+
catch {
|
|
158
|
+
throw new Error("The image given in the second parameter of the setBackground method is not valid or you are not connected to the internet.");
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (this.usersData) {
|
|
162
|
+
let Box_Y = 0, Avatar_Y = 0, Tag_Y = 45, XP_Y = 45, Level_Y = 30, Rank_Y = 45;
|
|
163
|
+
for (let i = 0; i < this.usersData.length; i++) {
|
|
164
|
+
ctx.save();
|
|
165
|
+
ctx.fillStyle = this.colors.box;
|
|
166
|
+
ctx.globalAlpha = this.opacity;
|
|
167
|
+
this.fillRoundRect(ctx, 0, Box_Y, canvas.width, 70, 15, true, false);
|
|
168
|
+
ctx.globalAlpha = 1;
|
|
169
|
+
try {
|
|
170
|
+
const avatar = await loadImage(this.usersData[i].avatar);
|
|
171
|
+
ctx.clip();
|
|
172
|
+
ctx.drawImage(avatar, 0, Avatar_Y, 70, 70);
|
|
173
|
+
}
|
|
174
|
+
catch (err) {
|
|
175
|
+
console.error("Failed to load avatar", err);
|
|
176
|
+
}
|
|
177
|
+
ctx.shadowBlur = 10;
|
|
178
|
+
ctx.shadowOffsetX = 8;
|
|
179
|
+
ctx.shadowOffsetY = 6;
|
|
180
|
+
ctx.shadowColor = "#0a0a0a";
|
|
181
|
+
ctx.fillStyle = this.colors.username;
|
|
182
|
+
ctx.font = `bold 25px circular-std, noto-emoji, noto-sans-jp, noto-sans, noto-sans-kr`;
|
|
183
|
+
ctx.textAlign = "left";
|
|
184
|
+
ctx.fillText(this.usersData[i].tag, 80, Tag_Y, 260);
|
|
185
|
+
ctx.fillStyle = this.colors.score;
|
|
186
|
+
ctx.font = `bold 20px circular-std, noto-emoji, noto-sans-jp, noto-sans, noto-sans-kr`;
|
|
187
|
+
ctx.textAlign = "right";
|
|
188
|
+
ctx.fillText(`${this.scoreMessage} ${this.abbreviateNumber == true ? `${abbreviateNumber(this.usersData[i].score)}` : `${this.usersData[i].score}`}/${this.abbreviateNumber == true ? `${abbreviateNumber(this.usersData[i].games)}` : `${this.usersData[i].games}`}`, 560, XP_Y, 200);
|
|
189
|
+
if (this.usersData[i].top === 1) {
|
|
190
|
+
ctx.fillStyle = this.colors.firstRank;
|
|
191
|
+
}
|
|
192
|
+
else if (this.usersData[i].top === 2) {
|
|
193
|
+
ctx.fillStyle = this.colors.secondRank;
|
|
194
|
+
}
|
|
195
|
+
else if (this.usersData[i].top === 3) {
|
|
196
|
+
ctx.fillStyle = this.colors.thirdRank;
|
|
197
|
+
}
|
|
198
|
+
ctx.font = `bold 30px circular-std, noto-emoji, noto-sans-jp, noto-sans, noto-sans-kr`;
|
|
199
|
+
ctx.textAlign = "right";
|
|
200
|
+
ctx.fillText("#" + this.usersData[i].top, 660, Rank_Y, 75);
|
|
201
|
+
Box_Y = Box_Y + 75;
|
|
202
|
+
Avatar_Y = Avatar_Y + 75;
|
|
203
|
+
Tag_Y = Tag_Y + 75;
|
|
204
|
+
XP_Y = XP_Y + 75;
|
|
205
|
+
Level_Y = Level_Y + 75;
|
|
206
|
+
Rank_Y = Rank_Y + 75;
|
|
207
|
+
ctx.restore();
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
ctx.font = `bold 40px circular-std, noto-emoji, noto-sans-jp, noto-sans, noto-sans-kr`;
|
|
212
|
+
ctx.fillStyle = "#ffffff";
|
|
213
|
+
ctx.textAlign = "center";
|
|
214
|
+
ctx.shadowBlur = 10;
|
|
215
|
+
ctx.shadowOffsetX = 8;
|
|
216
|
+
ctx.shadowOffsetY = 6;
|
|
217
|
+
ctx.shadowColor = "#0a0a0a";
|
|
218
|
+
ctx.fillText("Not found!", 340, 370, 500);
|
|
219
|
+
}
|
|
220
|
+
return canvas.toBuffer("image/png");
|
|
221
|
+
}
|
|
222
|
+
fillRoundRect(ctx, x, y, w, h, r, f, s) {
|
|
223
|
+
if (typeof r === "number") {
|
|
224
|
+
r = { tl: r, tr: r, br: r, bl: r };
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
const defaultRadius = { tl: 0, tr: 0, br: 0, bl: 0 };
|
|
228
|
+
for (const side in defaultRadius) {
|
|
229
|
+
r[side] =
|
|
230
|
+
r[side] || defaultRadius[side];
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
ctx.beginPath();
|
|
234
|
+
ctx.moveTo(x + r.tl, y);
|
|
235
|
+
ctx.lineTo(x + w - r.tr, y);
|
|
236
|
+
ctx.quadraticCurveTo(x + w, y, x + w, y + r.tr);
|
|
237
|
+
ctx.lineTo(x + w, y + h - r.br);
|
|
238
|
+
ctx.quadraticCurveTo(x + w, y + h, x + w - r.br, y + h);
|
|
239
|
+
ctx.lineTo(x + r.bl, y + h);
|
|
240
|
+
ctx.quadraticCurveTo(x, y + h, x, y + h - r.bl);
|
|
241
|
+
ctx.lineTo(x, y + r.tl);
|
|
242
|
+
ctx.quadraticCurveTo(x, y, x + r.tl, y);
|
|
243
|
+
ctx.closePath();
|
|
244
|
+
if (f)
|
|
245
|
+
ctx.fill();
|
|
246
|
+
if (s)
|
|
247
|
+
ctx.stroke();
|
|
248
|
+
}
|
|
249
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import moment from "moment-timezone";
|
|
2
|
+
import "moment-duration-format";
|
|
3
|
+
import { eventData } from "../constants/eventDatas.js";
|
|
4
|
+
type EventKey = keyof typeof eventData;
|
|
5
|
+
/**
|
|
6
|
+
* Utilities for skytimes
|
|
7
|
+
*/
|
|
8
|
+
export declare class SkytimesUtils {
|
|
9
|
+
/** Get all occurences of an event for the given date
|
|
10
|
+
* @param eventTime The moment date for which to get all occurences
|
|
11
|
+
* @param interval The interval between the event occurence's
|
|
12
|
+
*/
|
|
13
|
+
static getAllTimes(eventTime: moment.Moment, interval?: number): string;
|
|
14
|
+
/**
|
|
15
|
+
* Get the date in moment on which the event will occur (if it's not a daily event)
|
|
16
|
+
* @param event The event for which to get the date-time
|
|
17
|
+
*/
|
|
18
|
+
private static getOccurenceDay;
|
|
19
|
+
/**
|
|
20
|
+
* Get the next occurence of the event relative to now
|
|
21
|
+
* @param eventName The key of the event
|
|
22
|
+
*/
|
|
23
|
+
private static getNextEventOccurrence;
|
|
24
|
+
/**
|
|
25
|
+
* Get the details about an event, their status, next occurence, al occurences for the day
|
|
26
|
+
* @param key The event key
|
|
27
|
+
*/
|
|
28
|
+
static getEventDetails(key: EventKey): EventDetails;
|
|
29
|
+
/**
|
|
30
|
+
* Same as {@apilink SkytimesUtils.getEventDetails | getEventDetails} but for all of the events
|
|
31
|
+
* @returns An array of event details
|
|
32
|
+
*/
|
|
33
|
+
static allEventDetails(): [EventKey, EventDetails][];
|
|
34
|
+
/**
|
|
35
|
+
* Returns the event status of a given event
|
|
36
|
+
* @param event The event to get the status for
|
|
37
|
+
* @param nextOccurence The next occurence of the event relative to "now"
|
|
38
|
+
* @returns The event status (or null if there is no active duration)
|
|
39
|
+
*/
|
|
40
|
+
static getEventStatus(event: (typeof eventData)[keyof typeof eventData], nextOccurence: moment.Moment): Times;
|
|
41
|
+
}
|
|
42
|
+
export interface BaseTimes {
|
|
43
|
+
/** Whether the event is active or not */
|
|
44
|
+
active: boolean;
|
|
45
|
+
/** The time when the event starts */
|
|
46
|
+
nextTime: moment.Moment;
|
|
47
|
+
/** This will be the countdown for when the event ends if it's active,
|
|
48
|
+
* otherwise it'll be the countdown to the next occurence
|
|
49
|
+
*/
|
|
50
|
+
duration: string;
|
|
51
|
+
}
|
|
52
|
+
export interface ActiveTimes extends BaseTimes {
|
|
53
|
+
active: true;
|
|
54
|
+
/** The time when the event started if active */
|
|
55
|
+
startTime: moment.Moment;
|
|
56
|
+
/** The time when the event ends if active */
|
|
57
|
+
endTime: moment.Moment;
|
|
58
|
+
}
|
|
59
|
+
export interface NotActiveTimes extends BaseTimes {
|
|
60
|
+
active: false;
|
|
61
|
+
/** The time when the event started if active */
|
|
62
|
+
startTime?: moment.Moment;
|
|
63
|
+
/** The time when the event ends if active */
|
|
64
|
+
endTime?: moment.Moment;
|
|
65
|
+
}
|
|
66
|
+
export type Times = ActiveTimes | NotActiveTimes;
|
|
67
|
+
export interface EventDetails {
|
|
68
|
+
event: (typeof eventData)[EventKey];
|
|
69
|
+
nextOccurence: moment.Moment;
|
|
70
|
+
allOccurences: string;
|
|
71
|
+
status: Times;
|
|
72
|
+
}
|
|
73
|
+
export {};
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Base, time } from "discord.js";
|
|
2
|
+
import moment from "moment-timezone";
|
|
3
|
+
import "moment-duration-format";
|
|
4
|
+
import { eventData } from "../constants/eventDatas.js";
|
|
5
|
+
/**
|
|
6
|
+
* Utilities for skytimes
|
|
7
|
+
*/
|
|
8
|
+
export class SkytimesUtils {
|
|
9
|
+
/** Get all occurences of an event for the given date
|
|
10
|
+
* @param eventTime The moment date for which to get all occurences
|
|
11
|
+
* @param interval The interval between the event occurence's
|
|
12
|
+
*/
|
|
13
|
+
static getAllTimes(eventTime, interval) {
|
|
14
|
+
const clonedTime = eventTime.clone();
|
|
15
|
+
const timeBuilt = [];
|
|
16
|
+
while (eventTime.date() === clonedTime.date()) {
|
|
17
|
+
timeBuilt.push(time(clonedTime.toDate(), "t"));
|
|
18
|
+
clonedTime.add(interval || 0, "minutes");
|
|
19
|
+
}
|
|
20
|
+
return timeBuilt.join(" • ");
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Get the date in moment on which the event will occur (if it's not a daily event)
|
|
24
|
+
* @param event The event for which to get the date-time
|
|
25
|
+
*/
|
|
26
|
+
static getOccurenceDay(event) {
|
|
27
|
+
const nextOccurrence = moment().tz("America/Los_Angeles").startOf("day").add(event.offset, "minutes"); // Start with the offset from the beginning of the day
|
|
28
|
+
if (event.occursOn) {
|
|
29
|
+
// If the event occurs on specific weekdays
|
|
30
|
+
if (event.occursOn.weekDays) {
|
|
31
|
+
while (!event.occursOn.weekDays.includes(nextOccurrence.isoWeekday())) {
|
|
32
|
+
nextOccurrence.add(1, "day");
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
// If the event occurs on a specific day of the month
|
|
36
|
+
if (event.occursOn.dayOfTheMonth) {
|
|
37
|
+
while (nextOccurrence.date() !== event.occursOn.dayOfTheMonth) {
|
|
38
|
+
nextOccurrence.add(1, "day");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return nextOccurrence;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Get the next occurence of the event relative to now
|
|
46
|
+
* @param eventName The key of the event
|
|
47
|
+
*/
|
|
48
|
+
static getNextEventOccurrence(eventName) {
|
|
49
|
+
const event = eventData[eventName];
|
|
50
|
+
if (!event)
|
|
51
|
+
throw new Error("Unknow Event");
|
|
52
|
+
const now = moment().tz("America/Los_Angeles"); // Current time
|
|
53
|
+
const nextOccurrence = this.getOccurenceDay(event);
|
|
54
|
+
// Loop to calculate the next occurrence based on the interval
|
|
55
|
+
while (nextOccurrence.isBefore(now)) {
|
|
56
|
+
nextOccurrence.add(event.interval || 0, "minutes");
|
|
57
|
+
}
|
|
58
|
+
return nextOccurrence;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get the details about an event, their status, next occurence, al occurences for the day
|
|
62
|
+
* @param key The event key
|
|
63
|
+
*/
|
|
64
|
+
static getEventDetails(key) {
|
|
65
|
+
const nextOccurence = this.getNextEventOccurrence(key);
|
|
66
|
+
return {
|
|
67
|
+
event: eventData[key],
|
|
68
|
+
nextOccurence,
|
|
69
|
+
allOccurences: this.getAllTimes(this.getOccurenceDay(eventData[key]), eventData[key].interval),
|
|
70
|
+
status: this.getEventStatus(eventData[key], nextOccurence),
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Same as {@apilink SkytimesUtils.getEventDetails | getEventDetails} but for all of the events
|
|
75
|
+
* @returns An array of event details
|
|
76
|
+
*/
|
|
77
|
+
static allEventDetails() {
|
|
78
|
+
const keys = Object.keys(eventData).sort((a, b) => eventData[a].index - eventData[b].index);
|
|
79
|
+
const occurences = [];
|
|
80
|
+
for (const key of keys) {
|
|
81
|
+
occurences.push([key, this.getEventDetails(key)]);
|
|
82
|
+
}
|
|
83
|
+
return occurences;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Returns the event status of a given event
|
|
87
|
+
* @param event The event to get the status for
|
|
88
|
+
* @param nextOccurence The next occurence of the event relative to "now"
|
|
89
|
+
* @returns The event status (or null if there is no active duration)
|
|
90
|
+
*/
|
|
91
|
+
static getEventStatus(event, nextOccurence) {
|
|
92
|
+
const now = moment().tz("America/Los_Angeles");
|
|
93
|
+
const BASE = {
|
|
94
|
+
active: false,
|
|
95
|
+
nextTime: nextOccurence,
|
|
96
|
+
duration: moment.duration(nextOccurence.diff(now)).format("d[d] h[h] m[m] s[s]"),
|
|
97
|
+
};
|
|
98
|
+
if (!event.duration)
|
|
99
|
+
return BASE;
|
|
100
|
+
// Substract the interval because nextOccurence always calculates the next upcoming event
|
|
101
|
+
// So we substract the interval to get the last occurence, and add the active duration to it, and check if now is between those
|
|
102
|
+
const start = nextOccurence.clone().subtract(event.interval || 0, "minutes");
|
|
103
|
+
const end = start.clone().add(event.duration, "minutes");
|
|
104
|
+
// When active
|
|
105
|
+
if (now.isBetween(start, end)) {
|
|
106
|
+
return {
|
|
107
|
+
active: true,
|
|
108
|
+
startTime: start,
|
|
109
|
+
endTime: end,
|
|
110
|
+
// TODO: This maybe not needed as nextoccurence is inncluded in original object returned from `eventOccurence()`
|
|
111
|
+
nextTime: nextOccurence,
|
|
112
|
+
duration: moment.duration(end.diff(now)).format("d[d] h[h] m[m] s[s]"),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
return BASE;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { SpecialEventData } from "../typings.js";
|
|
2
|
+
/**
|
|
3
|
+
* @class
|
|
4
|
+
* @classdesc A class to update Events details in the client constructor
|
|
5
|
+
* @method update Updates the event details
|
|
6
|
+
*/
|
|
7
|
+
export declare class UpdateEvent {
|
|
8
|
+
readonly data: SpecialEventData;
|
|
9
|
+
constructor(data: SpecialEventData);
|
|
10
|
+
/**
|
|
11
|
+
* @param name Name of the event
|
|
12
|
+
*/
|
|
13
|
+
setName(name: string): this;
|
|
14
|
+
/**
|
|
15
|
+
* @param date Start date of the Event. Format DD-MM-YYYY
|
|
16
|
+
* @example
|
|
17
|
+
* new UpdateEvent().setDate('22-09-2023')
|
|
18
|
+
*/
|
|
19
|
+
setStart(date: string): this;
|
|
20
|
+
/**
|
|
21
|
+
* @param date End date of the Event. Format DD-MM-YYYY
|
|
22
|
+
* @example
|
|
23
|
+
* new UpdateEvent().setDate('22-09-2023')
|
|
24
|
+
*/
|
|
25
|
+
setEnd(date: string): this;
|
|
26
|
+
/**
|
|
27
|
+
* @returns The updated event
|
|
28
|
+
*/
|
|
29
|
+
update(): Promise<SpecialEventData>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import moment from "moment-timezone";
|
|
2
|
+
/**
|
|
3
|
+
* @class
|
|
4
|
+
* @classdesc A class to update Events details in the client constructor
|
|
5
|
+
* @method update Updates the event details
|
|
6
|
+
*/
|
|
7
|
+
export class UpdateEvent {
|
|
8
|
+
data;
|
|
9
|
+
constructor(data) {
|
|
10
|
+
this.data = data;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* @param name Name of the event
|
|
14
|
+
*/
|
|
15
|
+
setName(name) {
|
|
16
|
+
if (!name || typeof name !== "string") {
|
|
17
|
+
throw new TypeError("Name must be a non-empty string.");
|
|
18
|
+
}
|
|
19
|
+
this.data.name = name;
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* @param date Start date of the Event. Format DD-MM-YYYY
|
|
24
|
+
* @example
|
|
25
|
+
* new UpdateEvent().setDate('22-09-2023')
|
|
26
|
+
*/
|
|
27
|
+
setStart(date) {
|
|
28
|
+
if (!date || typeof date !== "string") {
|
|
29
|
+
throw new TypeError("Date must be a non-empty string.");
|
|
30
|
+
}
|
|
31
|
+
this.data.startDate = date;
|
|
32
|
+
return this;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* @param date End date of the Event. Format DD-MM-YYYY
|
|
36
|
+
* @example
|
|
37
|
+
* new UpdateEvent().setDate('22-09-2023')
|
|
38
|
+
*/
|
|
39
|
+
setEnd(date) {
|
|
40
|
+
if (!date || typeof date !== "string") {
|
|
41
|
+
throw new TypeError("Date must be a non-empty string.");
|
|
42
|
+
}
|
|
43
|
+
this.data.endDate = date;
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* @returns The updated event
|
|
48
|
+
*/
|
|
49
|
+
async update() {
|
|
50
|
+
return await this.data.save();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { TSData } from "../typings.js";
|
|
2
|
+
/**
|
|
3
|
+
* @class
|
|
4
|
+
* @classdesc A class to update traveling spirit details in the client constructor
|
|
5
|
+
* @method update updates the ts details
|
|
6
|
+
* @returns {Object}
|
|
7
|
+
*/
|
|
8
|
+
export declare class UpdateTS {
|
|
9
|
+
readonly data: TSData;
|
|
10
|
+
constructor(data: TSData);
|
|
11
|
+
/**
|
|
12
|
+
* Sets the name of the TS
|
|
13
|
+
* @param name Name of the returning TS
|
|
14
|
+
*/
|
|
15
|
+
setName(name: string): this;
|
|
16
|
+
/**
|
|
17
|
+
* Sets the visit date of the ts
|
|
18
|
+
* @param date Returnig date. Format: DD-MM-YYYY
|
|
19
|
+
*/
|
|
20
|
+
setVisit(date: string): this;
|
|
21
|
+
/**
|
|
22
|
+
* Sets the value of the t spirit
|
|
23
|
+
* @param value The value of the spirit in the spiritsData
|
|
24
|
+
*/
|
|
25
|
+
setValue(value: string): this;
|
|
26
|
+
/**
|
|
27
|
+
* Sets the index of the returning ts
|
|
28
|
+
* @param index The returning index of the TS
|
|
29
|
+
*/
|
|
30
|
+
setIndex(index: number): this;
|
|
31
|
+
/**
|
|
32
|
+
* returns the updated ts details
|
|
33
|
+
*/
|
|
34
|
+
update(): Promise<TSData>;
|
|
35
|
+
}
|