@skyhelperbot/utils 2.0.2 → 2.1.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.
@@ -118,11 +118,11 @@ export class LeaderboardCard {
118
118
  }
119
119
  }
120
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");
121
+ GlobalFonts.registerFromPath(path.join(import.meta.dirname, `../../shared/fonts/circularstd-black.otf`), "circular-std");
122
+ GlobalFonts.registerFromPath(path.join(import.meta.dirname, `../../shared/fonts/notosans-jp-black.ttf`), "noto-sans-jp");
123
+ GlobalFonts.registerFromPath(path.join(import.meta.dirname, `../../shared/fonts/notosans-black.ttf`), "noto-sans");
124
+ GlobalFonts.registerFromPath(path.join(import.meta.dirname, `../../shared/fonts/notoemoji-bold.ttf`), "noto-emoji");
125
+ GlobalFonts.registerFromPath(path.join(import.meta.dirname, `../../shared/fonts/notosans-kr-black.ttf`), "noto-sans-kr");
126
126
  const abbreviateNumber = (value) => {
127
127
  let newValue = value;
128
128
  if (value >= 1000) {
@@ -1,28 +1,27 @@
1
- import moment from "moment-timezone";
2
- import "moment-duration-format";
3
1
  import { eventData } from "../constants/eventDatas.js";
2
+ import { DateTime } from "luxon";
4
3
  type EventKey = keyof typeof eventData;
5
4
  /**
6
5
  * Utilities for skytimes
7
6
  */
8
7
  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
8
+ /** Get all occurrences of an event for the given date
9
+ * @param eventTime The DateTime for which to get all occurrences
10
+ * @param interval The interval between the event occurrences
12
11
  */
13
- static getAllTimes(eventTime: moment.Moment, interval?: number): string;
12
+ static getAllTimes(eventTime: DateTime, interval?: number): string;
14
13
  /**
15
- * Get the date in moment on which the event will occur (if it's not a daily event)
14
+ * Get the date in DateTime on which the event will occur (if it's not a daily event)
16
15
  * @param event The event for which to get the date-time
17
16
  */
18
- private static getOccurenceDay;
17
+ private static getOccurrenceDay;
19
18
  /**
20
- * Get the next occurence of the event relative to now
19
+ * Get the next occurrence of the event relative to now
21
20
  * @param eventName The key of the event
22
21
  */
23
22
  private static getNextEventOccurrence;
24
23
  /**
25
- * Get the details about an event, their status, next occurence, al occurences for the day
24
+ * Get the details about an event, their status, next occurrence, all occurrences for the day
26
25
  * @param key The event key
27
26
  */
28
27
  static getEventDetails(key: EventKey): EventDetails;
@@ -34,16 +33,16 @@ export declare class SkytimesUtils {
34
33
  /**
35
34
  * Returns the event status of a given event
36
35
  * @param event The event to get the status for
37
- * @param nextOccurence The next occurence of the event relative to "now"
36
+ * @param nextOccurrence The next occurrence of the event relative to "now"
38
37
  * @returns The event status (or null if there is no active duration)
39
38
  */
40
- static getEventStatus(event: (typeof eventData)[keyof typeof eventData], nextOccurence: moment.Moment): Times;
39
+ static getEventStatus(event: (typeof eventData)[keyof typeof eventData], nextOccurrence: DateTime): Times;
41
40
  }
42
41
  export interface BaseTimes {
43
42
  /** Whether the event is active or not */
44
43
  active: boolean;
45
44
  /** The time when the event starts */
46
- nextTime: moment.Moment;
45
+ nextTime: DateTime;
47
46
  /** This will be the countdown for when the event ends if it's active,
48
47
  * otherwise it'll be the countdown to the next occurence
49
48
  */
@@ -52,21 +51,21 @@ export interface BaseTimes {
52
51
  export interface ActiveTimes extends BaseTimes {
53
52
  active: true;
54
53
  /** The time when the event started if active */
55
- startTime: moment.Moment;
54
+ startTime: DateTime;
56
55
  /** The time when the event ends if active */
57
- endTime: moment.Moment;
56
+ endTime: DateTime;
58
57
  }
59
58
  export interface NotActiveTimes extends BaseTimes {
60
59
  active: false;
61
60
  /** The time when the event started if active */
62
- startTime?: moment.Moment;
61
+ startTime?: DateTime;
63
62
  /** The time when the event ends if active */
64
- endTime?: moment.Moment;
63
+ endTime?: DateTime;
65
64
  }
66
65
  export type Times = ActiveTimes | NotActiveTimes;
67
66
  export interface EventDetails {
68
67
  event: (typeof eventData)[EventKey];
69
- nextOccurence: moment.Moment;
68
+ nextOccurence: DateTime;
70
69
  allOccurences: string;
71
70
  status: Times;
72
71
  }
@@ -1,64 +1,62 @@
1
- import { Base, time } from "discord.js";
2
- import moment from "moment-timezone";
3
- import "moment-duration-format";
4
1
  import { eventData } from "../constants/eventDatas.js";
2
+ import { DateTime } from "luxon";
5
3
  /**
6
4
  * Utilities for skytimes
7
5
  */
8
6
  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
7
+ /** Get all occurrences of an event for the given date
8
+ * @param eventTime The DateTime for which to get all occurrences
9
+ * @param interval The interval between the event occurrences
12
10
  */
13
11
  static getAllTimes(eventTime, interval) {
14
- const clonedTime = eventTime.clone();
12
+ let clonedTime = eventTime;
15
13
  const timeBuilt = [];
16
- while (eventTime.date() === clonedTime.date()) {
17
- timeBuilt.push(time(clonedTime.toDate(), "t"));
18
- clonedTime.add(interval || 0, "minutes");
14
+ while (eventTime.hasSame(clonedTime, "day")) {
15
+ timeBuilt.push(`<t:${clonedTime.toUnixInteger()}:t>`);
16
+ clonedTime = clonedTime.plus({ minutes: interval || 0 });
19
17
  }
20
18
  return timeBuilt.join(" • ");
21
19
  }
22
20
  /**
23
- * Get the date in moment on which the event will occur (if it's not a daily event)
21
+ * Get the date in DateTime on which the event will occur (if it's not a daily event)
24
22
  * @param event The event for which to get the date-time
25
23
  */
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
24
+ static getOccurrenceDay(event) {
25
+ let nextOccurrence = DateTime.now().setZone("America/Los_Angeles").startOf("day").plus({ minutes: event.offset }); // Start with the offset from the beginning of the day
28
26
  if (event.occursOn) {
29
27
  // If the event occurs on specific weekdays
30
28
  if (event.occursOn.weekDays) {
31
- while (!event.occursOn.weekDays.includes(nextOccurrence.isoWeekday())) {
32
- nextOccurrence.add(1, "day");
29
+ while (!event.occursOn.weekDays.includes(nextOccurrence.weekday)) {
30
+ nextOccurrence = nextOccurrence.plus({ days: 1 });
33
31
  }
34
32
  }
35
33
  // If the event occurs on a specific day of the month
36
34
  if (event.occursOn.dayOfTheMonth) {
37
- while (nextOccurrence.date() !== event.occursOn.dayOfTheMonth) {
38
- nextOccurrence.add(1, "day");
35
+ while (nextOccurrence.day !== event.occursOn.dayOfTheMonth) {
36
+ nextOccurrence = nextOccurrence.plus({ days: 1 });
39
37
  }
40
38
  }
41
39
  }
42
40
  return nextOccurrence;
43
41
  }
44
42
  /**
45
- * Get the next occurence of the event relative to now
43
+ * Get the next occurrence of the event relative to now
46
44
  * @param eventName The key of the event
47
45
  */
48
46
  static getNextEventOccurrence(eventName) {
49
47
  const event = eventData[eventName];
50
48
  if (!event)
51
- throw new Error("Unknow Event");
52
- const now = moment().tz("America/Los_Angeles"); // Current time
53
- const nextOccurrence = this.getOccurenceDay(event);
49
+ throw new Error("Unknown Event");
50
+ const now = DateTime.now().setZone("America/Los_Angeles"); // Current time
51
+ let nextOccurrence = this.getOccurrenceDay(event);
54
52
  // Loop to calculate the next occurrence based on the interval
55
- while (nextOccurrence.isBefore(now)) {
56
- nextOccurrence.add(event.interval || 0, "minutes");
53
+ while (nextOccurrence < now) {
54
+ nextOccurrence = nextOccurrence.plus({ minutes: event.interval || 0 });
57
55
  }
58
56
  return nextOccurrence;
59
57
  }
60
58
  /**
61
- * Get the details about an event, their status, next occurence, al occurences for the day
59
+ * Get the details about an event, their status, next occurrence, all occurrences for the day
62
60
  * @param key The event key
63
61
  */
64
62
  static getEventDetails(key) {
@@ -66,7 +64,7 @@ export class SkytimesUtils {
66
64
  return {
67
65
  event: eventData[key],
68
66
  nextOccurence,
69
- allOccurences: this.getAllTimes(this.getOccurenceDay(eventData[key]), eventData[key].interval),
67
+ allOccurences: this.getAllTimes(this.getOccurrenceDay(eventData[key]), eventData[key].interval),
70
68
  status: this.getEventStatus(eventData[key], nextOccurence),
71
69
  };
72
70
  }
@@ -76,40 +74,39 @@ export class SkytimesUtils {
76
74
  */
77
75
  static allEventDetails() {
78
76
  const keys = Object.keys(eventData).sort((a, b) => eventData[a].index - eventData[b].index);
79
- const occurences = [];
77
+ const occurrences = [];
80
78
  for (const key of keys) {
81
- occurences.push([key, this.getEventDetails(key)]);
79
+ occurrences.push([key, this.getEventDetails(key)]);
82
80
  }
83
- return occurences;
81
+ return occurrences;
84
82
  }
85
83
  /**
86
84
  * Returns the event status of a given event
87
85
  * @param event The event to get the status for
88
- * @param nextOccurence The next occurence of the event relative to "now"
86
+ * @param nextOccurrence The next occurrence of the event relative to "now"
89
87
  * @returns The event status (or null if there is no active duration)
90
88
  */
91
- static getEventStatus(event, nextOccurence) {
92
- const now = moment().tz("America/Los_Angeles");
89
+ static getEventStatus(event, nextOccurrence) {
90
+ const now = DateTime.now().setZone("America/Los_Angeles");
93
91
  const BASE = {
94
92
  active: false,
95
- nextTime: nextOccurence,
96
- duration: moment.duration(nextOccurence.diff(now)).format("d[d] h[h] m[m] s[s]"),
93
+ nextTime: nextOccurrence,
94
+ duration: nextOccurrence.diff(now).toFormat("d'd' h'h' m'm' s's'"),
97
95
  };
98
96
  if (!event.duration)
99
97
  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");
98
+ // Subtract the interval because nextOccurrence always calculates the next upcoming event
99
+ // So we subtract the interval to get the last occurrence, and add the active duration to it, and check if now is between those
100
+ const start = nextOccurrence.minus({ minutes: event.interval || 0 });
101
+ const end = start.plus({ minutes: event.duration });
104
102
  // When active
105
- if (now.isBetween(start, end)) {
103
+ if (now >= start && now <= end) {
106
104
  return {
107
105
  active: true,
108
106
  startTime: start,
109
107
  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]"),
108
+ nextTime: nextOccurrence,
109
+ duration: end.diff(now).toFormat("d'd' h'h' m'm' s's'"),
113
110
  };
114
111
  }
115
112
  else {
@@ -1,11 +1,11 @@
1
- import { User, GuildMember, Client } from "discord.js";
1
+ import type { APIGuildMember, APIUser } from "discord-api-types/v10";
2
2
  export declare class GameWinnerCard {
3
+ readonly clientAvatar: string;
3
4
  private name;
4
- private client;
5
5
  private thumbnail;
6
6
  private points;
7
7
  private total;
8
- constructor(winner: User | GuildMember, wins: number, total: number, client: Client);
8
+ constructor(winner: APIUser | APIGuildMember, wins: number, total: number, clientAvatar: string);
9
9
  private path;
10
10
  private roundRect;
11
11
  private changeFontSize;
@@ -1,30 +1,29 @@
1
1
  import { createCanvas, loadImage } from "@napi-rs/canvas";
2
- import { User, GuildMember, Client } from "discord.js";
3
- import { colors, fancyCount } from "./utils.js";
2
+ import { colors, fancyCount, getUserAvatar } from "./utils.js";
4
3
  import { join } from "path";
5
4
  const size = 100;
6
5
  // TODO: Integrate it with other game types
7
6
  export class GameWinnerCard {
7
+ clientAvatar;
8
8
  name;
9
- client;
10
9
  thumbnail;
11
10
  points;
12
11
  total;
13
- constructor(winner, wins, total, client) {
14
- if (winner instanceof User) {
15
- this.name = winner?.displayName || winner?.globalName || winner?.username;
16
- this.thumbnail = winner?.displayAvatarURL({ forceStatic: true, extension: "jpg" });
12
+ constructor(winner, wins, total, clientAvatar) {
13
+ this.clientAvatar = clientAvatar;
14
+ if ("user" in winner) {
15
+ this.name = winner.nick || winner.user?.global_name || winner?.user.username;
16
+ this.thumbnail = getUserAvatar(winner.user);
17
17
  }
18
- if (winner instanceof GuildMember) {
19
- this.name = winner.user?.globalName || winner?.displayName || winner.user.username;
20
- this.thumbnail = winner.user?.displayAvatarURL({ forceStatic: true, extension: "jpg" });
18
+ else {
19
+ this.name = winner?.global_name || winner?.username;
20
+ this.thumbnail = getUserAvatar(winner);
21
21
  }
22
- this.client = client;
23
22
  this.points = wins;
24
23
  this.total = total;
25
24
  }
26
25
  path(strs) {
27
- return join(__dirname, strs);
26
+ return join(import.meta.dirname, strs);
28
27
  }
29
28
  roundRect(ctx, x, y, w, h, r) {
30
29
  if (w < 2 * r)
@@ -112,7 +111,7 @@ export class GameWinnerCard {
112
111
  ctx.fillStyle = colors.grey;
113
112
  this.roundRect(ctx, w * 0.875, h * 0.6, h * 0.4, h * 0.4, h * 0.15).clip();
114
113
  ctx.fill();
115
- ctx.drawImage(await loadImage(this.client.user.displayAvatarURL({ forceStatic: true, extension: "jpg" })), w * 0.875, h * 0.6, h * 0.4, h * 0.4);
114
+ ctx.drawImage(await loadImage(this.clientAvatar), w * 0.875, h * 0.6, h * 0.4, h * 0.4);
116
115
  return canvas.toBuffer("image/png");
117
116
  }
118
117
  }
@@ -1,7 +1,5 @@
1
1
  export { LeaderboardCard } from "./LeaderBoardCard.js";
2
2
  export { GameWinnerCard } from "./WinnerCard.js";
3
- export { UpdateTS } from "./UpdateTs.js";
4
- export { UpdateEvent } from "./UpdateEvent.js";
5
3
  export { ShardsUtil } from "./shardsUtil.js";
6
4
  export { SkytimesUtils } from "./SkytimesUtils.js";
7
5
  export * from "./SkytimesUtils.js";
@@ -1,7 +1,5 @@
1
1
  export { LeaderboardCard } from "./LeaderBoardCard.js";
2
2
  export { GameWinnerCard } from "./WinnerCard.js";
3
- export { UpdateTS } from "./UpdateTs.js";
4
- export { UpdateEvent } from "./UpdateEvent.js";
5
3
  export { ShardsUtil } from "./shardsUtil.js";
6
4
  export { SkytimesUtils } from "./SkytimesUtils.js";
7
5
  export * from "./SkytimesUtils.js";
@@ -1,5 +1,4 @@
1
- import moment from "moment-timezone";
2
- import "moment-duration-format";
1
+ import { DateTime } from "luxon";
3
2
  import type { ShardsCountdown } from "../typings.js";
4
3
  /**
5
4
  * Sequence of Shards pattern
@@ -15,15 +14,15 @@ declare const realmSequence: readonly ["prairie", "forest", "valley", "wasteland
15
14
  */
16
15
  export declare class ShardsUtil {
17
16
  /**
18
- * @method getDate - get provided date in moment
17
+ * @method getDate - get provided date in luxon
19
18
  * @param date - date to get in moment
20
19
  */
21
- static getDate(date?: string | undefined | null): moment.Moment | string;
20
+ static getDate(date?: string | undefined | null): DateTime | string;
22
21
  /**
23
22
  * Returns shards index for a given date
24
23
  * @param date
25
24
  */
26
- static shardsIndex(date: moment.Moment): {
25
+ static shardsIndex(date: DateTime): {
27
26
  currentShard: (typeof shardSequence)[number];
28
27
  currentRealm: (typeof realmSequence)[number];
29
28
  };
@@ -36,6 +35,6 @@ export declare class ShardsUtil {
36
35
  * Get all three shards status for a given date relative to the current time
37
36
  * @param date The date for which to get the status for
38
37
  */
39
- static getStatus(date: moment.Moment): ShardsCountdown[] | "No Shard";
38
+ static getStatus(date: DateTime): ShardsCountdown[] | "No Shard";
40
39
  }
41
40
  export {};
@@ -1,5 +1,4 @@
1
- import moment from "moment-timezone";
2
- import "moment-duration-format";
1
+ import { DateTime } from "luxon";
3
2
  import { shardsTimeline, shardConfig } from "../constants/index.js";
4
3
  /**
5
4
  * Sequence of Shards pattern
@@ -15,7 +14,7 @@ const realmSequence = ["prairie", "forest", "valley", "wasteland", "vault"];
15
14
  */
16
15
  export class ShardsUtil {
17
16
  /**
18
- * @method getDate - get provided date in moment
17
+ * @method getDate - get provided date in luxon
19
18
  * @param date - date to get in moment
20
19
  */
21
20
  static getDate(date) {
@@ -23,12 +22,13 @@ export class ShardsUtil {
23
22
  let currentDate;
24
23
  try {
25
24
  if (date) {
26
- currentDate = moment.tz(date, "Y-MM-DD", timezone).startOf("day");
25
+ const [year, month, day] = date.split("-").map(Number);
26
+ currentDate = DateTime.fromObject({ year, month, day }, { zone: timezone }).startOf("day");
27
27
  }
28
28
  else {
29
- currentDate = moment().tz(timezone);
29
+ currentDate = DateTime.now().setZone(timezone).startOf("day");
30
30
  }
31
- if (!currentDate.isValid()) {
31
+ if (!currentDate.isValid) {
32
32
  return "invalid";
33
33
  }
34
34
  else {
@@ -44,7 +44,7 @@ export class ShardsUtil {
44
44
  * @param date
45
45
  */
46
46
  static shardsIndex(date) {
47
- const dayOfMonth = date.date();
47
+ const dayOfMonth = date.day;
48
48
  const shardIndex = (dayOfMonth - 1) % shardSequence.length;
49
49
  const currentShard = shardSequence[shardIndex];
50
50
  const realmIndex = (dayOfMonth - 1) % realmSequence.length;
@@ -76,43 +76,43 @@ export class ShardsUtil {
76
76
  const timezone = "America/Los_Angeles";
77
77
  const { currentShard } = this.shardsIndex(date);
78
78
  const timings = shardsTimeline(date)[currentShard];
79
- const present = moment().tz(timezone);
80
- const isNoShard = shardConfig[currentShard].weekdays.includes(date.day());
79
+ const present = DateTime.now().setZone(timezone);
80
+ const isNoShard = shardConfig[currentShard].weekdays.includes(date.weekday);
81
81
  if (isNoShard)
82
82
  return "No Shard";
83
83
  const toReturn = [];
84
84
  for (let i = 0; i < timings.length; i++) {
85
85
  const eventTiming = timings[i];
86
86
  // Active
87
- if (present.isBetween(eventTiming.start, eventTiming.end)) {
87
+ if (present >= eventTiming.start && present <= eventTiming.end) {
88
88
  toReturn.push({
89
89
  index: i + 1,
90
90
  active: true,
91
91
  start: eventTiming.start,
92
92
  end: eventTiming.end,
93
- duration: moment.duration(eventTiming.end.diff(present)).format("d[d] h[h] m[m] s[s]"),
93
+ duration: eventTiming.end.diff(present, ["days", "hours", "minutes", "seconds"]).toFormat("dd'd' hh'h' mm'm' ss's'"),
94
94
  });
95
95
  continue;
96
96
  // Yet to fall
97
97
  }
98
- else if (present.isBefore(eventTiming.start)) {
98
+ else if (present < eventTiming.start) {
99
99
  toReturn.push({
100
100
  index: i + 1,
101
101
  active: false,
102
102
  start: eventTiming.start,
103
103
  end: eventTiming.end,
104
- duration: moment.duration(eventTiming.start.diff(present)).format("d[d] h[h] m[m] s[s]"),
104
+ duration: eventTiming.start.diff(present, ["days", "hours", "minutes", "seconds"]).toFormat("dd'd' hh'h' mm'm' ss's'"),
105
105
  });
106
106
  continue;
107
107
  // All ended
108
108
  }
109
- else if (present.isAfter(eventTiming.end)) {
109
+ else if (present > eventTiming.end) {
110
110
  toReturn.push({
111
111
  index: i + 1,
112
112
  ended: true,
113
113
  start: eventTiming.start,
114
114
  end: eventTiming.end,
115
- duration: moment.duration(present.diff(eventTiming.end)).format("d[d] h[h] m[m] s[s]"),
115
+ duration: present.diff(eventTiming.end, ["days", "hours", "minutes", "seconds"]).toFormat("dd'd' hh'h' mm'm' ss's'"),
116
116
  });
117
117
  continue;
118
118
  }
@@ -1,3 +1,4 @@
1
+ import { type APIUser } from "discord-api-types/v10";
1
2
  export declare const colors: {
2
3
  blue: string;
3
4
  white: string;
@@ -11,3 +12,4 @@ export declare const colors: {
11
12
  idle: string;
12
13
  };
13
14
  export declare function fancyCount(n: number): string;
15
+ export declare function getUserAvatar(user: APIUser): string;
@@ -1,3 +1,4 @@
1
+ import { CDNRoutes } from "discord-api-types/v10";
1
2
  export const colors = {
2
3
  blue: "#7289DA",
3
4
  white: "#FFFFFF",
@@ -20,3 +21,8 @@ export function fancyCount(n) {
20
21
  }
21
22
  return Math.floor(n) + "";
22
23
  }
24
+ export function getUserAvatar(user) {
25
+ return user.avatar
26
+ ? CDNRoutes.userAvatar(user.id, user.avatar, "png")
27
+ : CDNRoutes.defaultUserAvatar(Number((BigInt(user.id) >> 22n) % 5n));
28
+ }
@@ -1,7 +1,7 @@
1
1
  export const shardConfig = {
2
- a: { type: "<:ShardBlue:1233057106903699497> Black Shard", colors: "#000000", weekdays: [6, 0] },
2
+ a: { type: "<:ShardBlue:1233057106903699497> Black Shard", colors: "#000000", weekdays: [6, 7] },
3
3
  A: { type: "<:ShardRed:1233057065799254106> Red Shard", colors: "#FF0000", weekdays: [2, 3] },
4
- b: { type: "<:ShardBlue:1233057106903699497> Black Shard", colors: "#000000", weekdays: [0, 1] },
4
+ b: { type: "<:ShardBlue:1233057106903699497> Black Shard", colors: "#000000", weekdays: [7, 1] },
5
5
  B: { type: "<:ShardRed:1233057065799254106> Red Shard", colors: "#FF0000", weekdays: [3, 4] },
6
6
  C: { type: "<:ShardRed:1233057065799254106> Red Shard", colors: "#FF0000", weekdays: [1, 2] },
7
7
  };
@@ -1,10 +1,10 @@
1
- import moment from "moment-timezone";
1
+ import type { DateTime } from "luxon";
2
2
  declare const shardSequence: readonly ["C", "b", "A", "a", "B", "b", "C", "a", "A", "b", "B", "a"];
3
3
  export type TimelineType = {
4
- readonly earlySky: moment.Moment;
5
- readonly gateShard: moment.Moment;
6
- readonly start: moment.Moment;
7
- readonly end: moment.Moment;
4
+ readonly earlySky: DateTime;
5
+ readonly gateShard: DateTime;
6
+ readonly start: DateTime;
7
+ readonly end: DateTime;
8
8
  readonly shardMusic: string;
9
9
  };
10
10
  export type TimelineReturnType = Record<(typeof shardSequence)[number], [TimelineType, TimelineType, TimelineType]>;
@@ -15,5 +15,5 @@ export type TimelineReturnType = Record<(typeof shardSequence)[number], [Timelin
15
15
  * const timelines = shardsTimeline(moment())
16
16
  * const times = timelines[currentShard]
17
17
  */
18
- export declare const shardsTimeline: (currentDate: moment.Moment) => TimelineReturnType;
18
+ export declare const shardsTimeline: (currentDate: DateTime) => TimelineReturnType;
19
19
  export {};
@@ -1,4 +1,3 @@
1
- import moment from "moment-timezone";
2
1
  const shardSequence = ["C", "b", "A", "a", "B", "b", "C", "a", "A", "b", "B", "a"];
3
2
  /**
4
3
  * Returns shards fall - end times for a given date
@@ -10,20 +9,10 @@ const shardSequence = ["C", "b", "A", "a", "B", "b", "C", "a", "A", "b", "B", "a
10
9
  export const shardsTimeline = (currentDate) => {
11
10
  const getTimes = (earlySkyHours, earlySkyMinutes, earlySkySeconds, gateShardHours, gateShardMinutes, shardLandHours, shardLandMinutes, shardLandSeconds, shardEndHours, shardEndMinutes, shardMusic) => {
12
11
  return {
13
- earlySky: currentDate
14
- .clone()
15
- .startOf("day")
16
- .add(earlySkyHours, "hours")
17
- .add(earlySkyMinutes, "minutes")
18
- .add(earlySkySeconds, "seconds"),
19
- gateShard: currentDate.clone().startOf("day").add(gateShardHours, "hours").add(gateShardMinutes, "minutes"),
20
- start: currentDate
21
- .clone()
22
- .startOf("day")
23
- .add(shardLandHours, "hours")
24
- .add(shardLandMinutes, "minutes")
25
- .add(shardLandSeconds, "seconds"),
26
- end: currentDate.clone().startOf("day").add(shardEndHours, "hours").add(shardEndMinutes, "minutes"),
12
+ earlySky: currentDate.startOf("day").plus({ hours: earlySkyHours, minutes: earlySkyMinutes, seconds: earlySkySeconds }),
13
+ gateShard: currentDate.startOf("day").plus({ hours: gateShardHours, minutes: gateShardMinutes }),
14
+ start: currentDate.startOf("day").plus({ hours: shardLandHours, minutes: shardLandMinutes, seconds: shardLandSeconds }),
15
+ end: currentDate.startOf("day").plus({ hours: shardEndHours, minutes: shardEndMinutes }),
27
16
  shardMusic: shardMusic,
28
17
  };
29
18
  };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
- import { buildTimesHTML, parsePerms, postToHaste, recursiveReadDir, type Field, parseDateFormat } from "./utils/index.js";
1
+ export * from "./utils/index.js";
2
2
  export * from "./classes/index.js";
3
3
  export { type Permission } from "./utils/parsePerms.js";
4
- export { buildTimesHTML, parsePerms, postToHaste, parseDateFormat, type Field, recursiveReadDir };
5
4
  export * from "./constants/index.js";
6
5
  export type * from "./typings.ts";
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
- import { buildTimesHTML, parsePerms, postToHaste, recursiveReadDir, parseDateFormat } from "./utils/index.js";
1
+ export * from "./utils/index.js";
2
2
  export * from "./classes/index.js";
3
3
  export {} from "./utils/parsePerms.js";
4
- export { buildTimesHTML, parsePerms, postToHaste, parseDateFormat, recursiveReadDir };
5
4
  export * from "./constants/index.js";
package/dist/typings.d.ts CHANGED
@@ -1,5 +1,4 @@
1
- import { Client, Collection } from "discord.js";
2
- import moment from "moment-timezone";
1
+ import type { DateTime } from "luxon";
3
2
  import { Document } from "mongoose";
4
3
  /** Data of users provided for making a game leaderboard card */
5
4
  export interface userData {
@@ -34,13 +33,10 @@ export interface Background {
34
33
  export interface SkyEvent {
35
34
  eventActive: Boolean;
36
35
  eventName: string;
37
- eventStarts: moment.Moment;
38
- eventEnds: moment.Moment;
36
+ eventStarts: DateTime;
37
+ eventEnds: DateTime;
39
38
  eventDuration: string;
40
39
  }
41
- export interface SkyHelper extends Client {
42
- skyEvents: Collection<string, SkyEvent>;
43
- }
44
40
  export interface TSData extends Document {
45
41
  name: string;
46
42
  visitDate: string;
@@ -56,7 +52,7 @@ export interface ShardsCountdown {
56
52
  index: number;
57
53
  active?: boolean;
58
54
  ended?: boolean;
59
- start: moment.Moment;
60
- end: moment.Moment;
55
+ start: DateTime;
56
+ end: DateTime;
61
57
  duration: string;
62
58
  }
package/dist/typings.js CHANGED
@@ -1,3 +1 @@
1
- import { Client, Collection } from "discord.js";
2
- import moment from "moment-timezone";
3
1
  import { Document } from "mongoose";
@@ -1,5 +1,5 @@
1
- export { buildTimesHTML, type Field } from "./buildTimesHTML.js";
2
- export { postToHaste } from "./postToBin.js";
3
- export { parsePerms, type Permission } from "./parsePerms.js";
4
- export { recursiveReadDir } from "./recursiveReadDir.js";
5
- export { parseDateFormat } from "./parseDateFormat.js";
1
+ export * from "./postToBin.js";
2
+ export * from "./parsePerms.js";
3
+ export * from "./recursiveReadDir.js";
4
+ export * from "./parseDateFormat.js";
5
+ export * from "./resolveColors.js";
@@ -1,5 +1,5 @@
1
- export { buildTimesHTML } from "./buildTimesHTML.js";
2
- export { postToHaste } from "./postToBin.js";
3
- export { parsePerms } from "./parsePerms.js";
4
- export { recursiveReadDir } from "./recursiveReadDir.js";
5
- export { parseDateFormat } from "./parseDateFormat.js";
1
+ export * from "./postToBin.js";
2
+ export * from "./parsePerms.js";
3
+ export * from "./recursiveReadDir.js";
4
+ export * from "./parseDateFormat.js";
5
+ export * from "./resolveColors.js";
@@ -0,0 +1,79 @@
1
+ /** Original code credit to discord.js */
2
+ /**
3
+ * Can be a number, hex string, an RGB array like:
4
+ * ```js
5
+ * [255, 0, 255] // purple
6
+ * ```
7
+ * or one of the following strings:
8
+ * - `Default`
9
+ * - `White`
10
+ * - `Aqua`
11
+ * - `Green`
12
+ * - `Blue`
13
+ * - `Yellow`
14
+ * - `Purple`
15
+ * - `LuminousVividPink`
16
+ * - `Fuchsia`
17
+ * - `Gold`
18
+ * - `Orange`
19
+ * - `Red`
20
+ * - `Grey`
21
+ * - `Navy`
22
+ * - `DarkAqua`
23
+ * - `DarkGreen`
24
+ * - `DarkBlue`
25
+ * - `DarkPurple`
26
+ * - `DarkVividPink`
27
+ * - `DarkGold`
28
+ * - `DarkOrange`
29
+ * - `DarkRed`
30
+ * - `DarkGrey`
31
+ * - `DarkerGrey`
32
+ * - `LightGrey`
33
+ * - `DarkNavy`
34
+ * - `Blurple`
35
+ * - `Greyple`
36
+ * - `DarkButNotBlack`
37
+ * - `NotQuiteBlack`
38
+ * - `Random`
39
+ */
40
+ export type ColorResolvable = keyof typeof Colors | "Random" | "Default" | `#${string}` | number | number[];
41
+ /**
42
+ * Resolves a ColorResolvable into a color number.
43
+ * @param color Color to resolve
44
+ * @returns A color
45
+ */
46
+ export declare function resolveColor(color: ColorResolvable): number;
47
+ declare const Colors: {
48
+ Default: number;
49
+ White: number;
50
+ Aqua: number;
51
+ Green: number;
52
+ Blue: number;
53
+ Yellow: number;
54
+ Purple: number;
55
+ LuminousVividPink: number;
56
+ Fuchsia: number;
57
+ Gold: number;
58
+ Orange: number;
59
+ Red: number;
60
+ Grey: number;
61
+ Navy: number;
62
+ DarkAqua: number;
63
+ DarkGreen: number;
64
+ DarkBlue: number;
65
+ DarkPurple: number;
66
+ DarkVividPink: number;
67
+ DarkGold: number;
68
+ DarkOrange: number;
69
+ DarkRed: number;
70
+ DarkGrey: number;
71
+ DarkerGrey: number;
72
+ LightGrey: number;
73
+ DarkNavy: number;
74
+ Blurple: number;
75
+ Greyple: number;
76
+ DarkButNotBlack: number;
77
+ NotQuiteBlack: number;
78
+ };
79
+ export {};
@@ -0,0 +1,63 @@
1
+ /** Original code credit to discord.js */
2
+ /**
3
+ * Resolves a ColorResolvable into a color number.
4
+ * @param color Color to resolve
5
+ * @returns A color
6
+ */
7
+ export function resolveColor(color) {
8
+ let resolvedColor;
9
+ if (typeof color === "string") {
10
+ if (color === "Random")
11
+ return Math.floor(Math.random() * (0xffffff + 1));
12
+ if (color === "Default")
13
+ return 0;
14
+ if (/^#?[\da-f]{6}$/i.test(color))
15
+ return parseInt(color.replace("#", ""), 16);
16
+ resolvedColor = Colors[color];
17
+ }
18
+ else if (Array.isArray(color)) {
19
+ resolvedColor = (color[0] << 16) + (color[1] << 8) + color[2];
20
+ }
21
+ else {
22
+ resolvedColor = color;
23
+ }
24
+ if (!Number.isInteger(resolvedColor)) {
25
+ throw new Error("Not a number");
26
+ }
27
+ if (resolvedColor < 0 || resolvedColor > 0xffffff) {
28
+ throw new Error("Not in color range");
29
+ }
30
+ return resolvedColor;
31
+ }
32
+ const Colors = {
33
+ Default: 0x000000,
34
+ White: 0xffffff,
35
+ Aqua: 0x1abc9c,
36
+ Green: 0x57f287,
37
+ Blue: 0x3498db,
38
+ Yellow: 0xfee75c,
39
+ Purple: 0x9b59b6,
40
+ LuminousVividPink: 0xe91e63,
41
+ Fuchsia: 0xeb459e,
42
+ Gold: 0xf1c40f,
43
+ Orange: 0xe67e22,
44
+ Red: 0xed4245,
45
+ Grey: 0x95a5a6,
46
+ Navy: 0x34495e,
47
+ DarkAqua: 0x11806a,
48
+ DarkGreen: 0x1f8b4c,
49
+ DarkBlue: 0x206694,
50
+ DarkPurple: 0x71368a,
51
+ DarkVividPink: 0xad1457,
52
+ DarkGold: 0xc27c0e,
53
+ DarkOrange: 0xa84300,
54
+ DarkRed: 0x992d22,
55
+ DarkGrey: 0x979c9f,
56
+ DarkerGrey: 0x7f8c8d,
57
+ LightGrey: 0xbcc0c0,
58
+ DarkNavy: 0x2c3e50,
59
+ Blurple: 0x5865f2,
60
+ Greyple: 0x99aab5,
61
+ DarkButNotBlack: 0x2c2f33,
62
+ NotQuiteBlack: 0x23272a,
63
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skyhelperbot/utils",
3
- "version": "2.0.2",
3
+ "version": "2.1.0",
4
4
  "description": "Utilities for SkyHelper bot",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -27,16 +27,18 @@
27
27
  "homepage": "https://github.com/imnaiyar/skyhelper/packages/utils#readme",
28
28
  "dependencies": {
29
29
  "@napi-rs/canvas": "^0.1.51",
30
- "discord.js": "14.16.2",
31
- "moment-duration-format": "^2.3.2",
32
- "moment-timezone": "^0.5.45",
30
+ "discord-api-types": "^0.37.93",
33
31
  "typescript": "^5.4.3",
34
- "undici": "^6.13.0"
32
+ "undici": "^7.2.3"
35
33
  },
36
34
  "devDependencies": {
37
- "@types/moment-duration-format": "^2.2.6",
35
+ "@types/luxon": "^3.4.2",
36
+ "luxon": "^3.5.0",
38
37
  "mongoose": "^8.3.2"
39
38
  },
39
+ "peerDependencies": {
40
+ "luxon": "^3.5.0"
41
+ },
40
42
  "scripts": {
41
43
  "build": "tsc && echo 'Files compiled'",
42
44
  "build:docs": "typedoc"
@@ -1,30 +0,0 @@
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
- }
@@ -1,52 +0,0 @@
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
- }
@@ -1,35 +0,0 @@
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
- }
@@ -1,62 +0,0 @@
1
- /**
2
- * @class
3
- * @classdesc A class to update traveling spirit details in the client constructor
4
- * @method update updates the ts details
5
- * @returns {Object}
6
- */
7
- export class UpdateTS {
8
- data;
9
- constructor(data) {
10
- this.data = data;
11
- }
12
- /**
13
- * Sets the name of the TS
14
- * @param name Name of the returning TS
15
- */
16
- setName(name) {
17
- if (!name || typeof name !== "string") {
18
- throw new TypeError("Name must be a non-empty string.");
19
- }
20
- this.data.name = name;
21
- return this;
22
- }
23
- /**
24
- * Sets the visit date of the ts
25
- * @param date Returnig date. Format: DD-MM-YYYY
26
- */
27
- setVisit(date) {
28
- if (!date || typeof date !== "string") {
29
- throw new TypeError("Date must be a non-empty string.");
30
- }
31
- this.data.visitDate = date;
32
- return this;
33
- }
34
- /**
35
- * Sets the value of the t spirit
36
- * @param value The value of the spirit in the spiritsData
37
- */
38
- setValue(value) {
39
- if (!value || typeof value !== "string") {
40
- throw new TypeError("Value must be a non-empty string.");
41
- }
42
- this.data.value = value;
43
- return this;
44
- }
45
- /**
46
- * Sets the index of the returning ts
47
- * @param index The returning index of the TS
48
- */
49
- setIndex(index) {
50
- if (!index || typeof index !== "number") {
51
- throw new TypeError("Index must be a number.");
52
- }
53
- this.data.index = index;
54
- return this;
55
- }
56
- /**
57
- * returns the updated ts details
58
- */
59
- async update() {
60
- return await this.data.save();
61
- }
62
- }
@@ -1,14 +0,0 @@
1
- import { type ChatInputCommandInteraction } from "discord.js";
2
- export interface Field {
3
- name: string;
4
- example: string;
5
- value: string;
6
- }
7
- /**
8
- * Dynamically builds a timestamp sweb page html with the given data
9
- * @param interaction The interaction that intiated this
10
- * @param fieldsData The data about times
11
- * @param offset Offset of the timezone
12
- * @param providedTime
13
- */
14
- export declare const buildTimesHTML: (interaction: ChatInputCommandInteraction, fieldsData: Field[], offset: string, providedTime: string) => string;
@@ -1,208 +0,0 @@
1
- import {} from "discord.js";
2
- /**
3
- * Dynamically builds a timestamp sweb page html with the given data
4
- * @param interaction The interaction that intiated this
5
- * @param fieldsData The data about times
6
- * @param offset Offset of the timezone
7
- * @param providedTime
8
- */
9
- export const buildTimesHTML = (interaction, fieldsData, offset, providedTime) => {
10
- return `
11
- <!DOCTYPE html>
12
-
13
-
14
- <html lang="en">
15
-
16
- <head>
17
- <meta charset="UTF-8">
18
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
- <!--=============== FAVICON ===============-->
20
- <link rel="shortcut icon" href="/assets/img/boticon.png" type="image/x-icon">
21
-
22
- <!--=============== BOXICONS ===============-->
23
- <link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
24
-
25
- <!--=============== SWIPER CSS ===============-->
26
- <link rel="stylesheet" href="/assets/css/swiper-bundle.min.css">
27
-
28
- <!--=============== CSS ===============-->
29
- <link rel="stylesheet" href="/assets/css/styles.css">
30
-
31
- <title>Timestamp - SkyHelper</title>
32
- </head>
33
- <body>
34
- <!--==================== HEADER ====================-->
35
-
36
- <header class="header" id="header">
37
-
38
- <nav class="nav container">
39
- <a href="/" class="nav__logo">
40
- <img src="/assets/img/boticon.png" style="top: 50px; left: 50px; widht: 40px; height: 40px;"> <p class="nav__logo-text">SkyHelper</p>
41
- </a>
42
- <a href="/" class="nav__link">
43
-
44
- <i class='bx bx-home-alt-2'></i>
45
- <span>Home</span>
46
- </a>
47
-
48
- <!-- Theme change button -->
49
- <i class='bx bx-moon change-theme' id="theme-button"></i>
50
- <a href="/commands" class="nav__link">
51
- <i class='bx bx-code-alt com-icon'></i><p class="nav__com">Commands</p>
52
- </a>
53
- <a href="https://discord.com/invite/u9zUjWbbQ4" class="button nav__button">
54
- Invite Me
55
- </a>
56
- </nav>
57
- </header>
58
-
59
- <!-- Content -->
60
- <main class="main">
61
- <section class="home section" id="home">
62
- <div class="home__container container grid">
63
- <div class="home__data">
64
- <h1 class="home__title">
65
- Timestamp
66
- </h1>
67
- </div>
68
- </div>
69
- </section>
70
-
71
- </main>
72
-
73
- <!-- timestamp -->
74
- <section class="value section" id="value">
75
- <div class="value__container container times__data">
76
- <h2 class="section__title">
77
- Timestamp for <img src="${interaction.user.displayAvatarURL()}" style="width:25px;height:25px;border-radius:50%;"><span> ${interaction.user.username}</span>
78
- </h2>
79
- <div class="alert alert-info">Provided Time: ${providedTime}<br>Offset: <strong>${offset}</strong>
80
- </div>
81
- <div class="value__accordion-item2">
82
- ${fieldsData
83
- .map((field) => `
84
-
85
- <div class="time__header">
86
- <strong>${field.name} (<span class="discUnix">${field.example}</span>)</strong></div>
87
-
88
- <br> <span class="code-block">${sanitizeField(field.value)}</span> <button class="copyBtn">
89
- <span><svg class ="copySvg" viewBox="0 0 467 512.22" clip-rule="evenodd" fill-rule="evenodd" image-rendering="optimizeQuality" text-rendering="geometricPrecision" shape-rendering="geometricPrecision" xmlns="http://www.w3.org/2000/svg" height="12" width="12"><path d="M131.07 372.11c.37 1 .57 2.08.57 3.2 0 1.13-.2 2.21-.57 3.21v75.91c0 10.74 4.41 20.53 11.5 27.62s16.87 11.49 27.62 11.49h239.02c10.75 0 20.53-4.4 27.62-11.49s11.49-16.88 11.49-27.62V152.42c0-10.55-4.21-20.15-11.02-27.18l-.47-.43c-7.09-7.09-16.87-11.5-27.62-11.5H170.19c-10.75 0-20.53 4.41-27.62 11.5s-11.5 16.87-11.5 27.61v219.69zm-18.67 12.54H57.23c-15.82 0-30.1-6.58-40.45-17.11C6.41 356.97 0 342.4 0 326.52V57.79c0-15.86 6.5-30.3 16.97-40.78l.04-.04C27.51 6.49 41.94 0 57.79 0h243.63c15.87 0 30.3 6.51 40.77 16.98l.03.03c10.48 10.48 16.99 24.93 16.99 40.78v36.85h50c15.9 0 30.36 6.5 40.82 16.96l.54.58c10.15 10.44 16.43 24.66 16.43 40.24v302.01c0 15.9-6.5 30.36-16.96 40.82-10.47 10.47-24.93 16.97-40.83 16.97H170.19c-15.9 0-30.35-6.5-40.82-16.97-10.47-10.46-16.97-24.92-16.97-40.82v-69.78zM340.54 94.64V57.79c0-10.74-4.41-20.53-11.5-27.63-7.09-7.08-16.86-11.48-27.62-11.48H57.79c-10.78 0-20.56 4.38-27.62 11.45l-.04.04c-7.06 7.06-11.45 16.84-11.45 27.62v268.73c0 10.86 4.34 20.79 11.38 27.97 6.95 7.07 16.54 11.49 27.17 11.49h55.17V152.42c0-15.9 6.5-30.35 16.97-40.82 10.47-10.47 24.92-16.96 40.82-16.96h170.35z" fill-rule="nonzero"></path></svg> Copy</span>
90
- <span>Copied</span>
91
- </button>
92
- <br><br>`)
93
- .join("")}
94
-
95
- </div>
96
- </div>
97
- </section>
98
- <!--==================== FOOTER ====================-->
99
-
100
-
101
-
102
- <footer class="footer section">
103
-
104
- <div class="footer__container container grid">
105
- <div>
106
- <a href="#" class="footer__logo">
107
- <img src="/assets/img/boticon.png" style="top: 50px; left: 50px; widht: 20px; height: 20px;"> SkyHelper
108
- </a>
109
- <p class="footer__description">
110
- A discord bot for the game Sky: Children of the Light
111
- </p>
112
- </div>
113
- <div class="footer__content">
114
- <div>
115
- <h3 class="footer__title">
116
- About
117
- </h3>
118
- <ul class="footer__links">
119
- <li>
120
- <a href="/" class="footer__link">About Me</a>
121
- </li>
122
-
123
- <li>
124
-
125
- <a href="/#popular" class="footer__link">Features</a>
126
-
127
- </li>
128
- <li>
129
-
130
- <a href="https://docs.skyhelper.xyz" class="footer__link">Documentation</a>
131
-
132
- </li>
133
- </ul>
134
- </div>
135
- <div>
136
-
137
- <h3 class="footer__title">
138
-
139
- Support
140
- </h3>
141
- <ul class="footer__links">
142
- <li>
143
- <a href="/#value" class="footer__link">FAQs</a>
144
- </li>
145
- <li>
146
-
147
- <a href="https://discord.com/invite/u9zUjWbbQ4" class="footer__link">Support Server</a>
148
-
149
- </li>
150
- <li>
151
-
152
- <a href="/contact-us" class="footer__link">Contact Us</a>
153
-
154
- </li>
155
- </ul>
156
- </div>
157
- <div>
158
-
159
- <h3 class="footer__title">
160
-
161
- Socials
162
- </h3>
163
- <ul class="footer__social">
164
- <a href="https://github.com/imnaiyar/SkyHelper" target="_blank" class="footer__social-link">
165
- <i class='bx bxl-github'></i>
166
- </a>
167
- <a href="https://discord.com/invite/u9zUjWbbQ4" target="_blank" class="footer__social-link">
168
- <i class='bx bxl-discord' ></i>
169
- </a>
170
- </ul>
171
- </div>
172
- </div>
173
- </div>
174
-
175
- <div class="footer__info container">
176
- <span class="footer__copy">
177
- &#169; SkyHelper. All rights reserved
178
- </span>
179
-
180
- <div class="footer__privacy">
181
- <a href="/tos">Terms of Service</a>
182
- <a href="/privacy">Privacy Policy</a>
183
- </div>
184
- </div>
185
- </footer>
186
-
187
-
188
- <!--========== SCROLL UP ==========-->
189
- <a href="#" class="scrollup" id="scroll-up">
190
- <i class='bx bx-chevrons-up' ></i>
191
- </a>
192
-
193
- <!--=============== SCROLLREVEAL ===============-->
194
- <script src="/assets/js/scrollreveal.min.js"></script>
195
-
196
- <!--=============== SWIPER JS ===============-->
197
- <script src="/assets/js/swiper-bundle.min.js"></script>
198
-
199
- <!--=============== MAIN JS ===============-->
200
- <script src="/assets/js/main.js"></script>
201
- </body>
202
- </html>
203
- `;
204
- };
205
- function sanitizeField(value) {
206
- // Remove backticks, <, and > characters
207
- return value.replace(/`/g, "").replace(/</g, "&lt;").replace(/>/g, "&gt;");
208
- }