|
|
@@ -0,0 +1,176 @@
|
|
|
+import { createRestAPIClient, mastodon } from "masto";
|
|
|
+import { CronJob } from "cron";
|
|
|
+
|
|
|
+import RedisClient from "../../libs/redis-client";
|
|
|
+import Emojis from "../../enums/emojis";
|
|
|
+import LogLevels from "../../enums/log-levels";
|
|
|
+import config from "../../config";
|
|
|
+
|
|
|
+import { type ICountdown } from "./interfaces";
|
|
|
+
|
|
|
+export default class Countdown {
|
|
|
+ private readonly _name: string = "Countdown";
|
|
|
+ private readonly _mastodonRestClient: mastodon.rest.Client;
|
|
|
+ private readonly _redisClient: RedisClient;
|
|
|
+
|
|
|
+ constructor(accessToken = config.MASTODON_TEST_ACCESS_TOKEN) {
|
|
|
+ accessToken = config.DEVELOP ? config.MASTODON_TEST_ACCESS_TOKEN : accessToken;
|
|
|
+ this._mastodonRestClient = createRestAPIClient({ url: config.MASTODON_URL, accessToken });
|
|
|
+ this._redisClient = new RedisClient();
|
|
|
+ }
|
|
|
+
|
|
|
+ private async publish(status: string): Promise<void> {
|
|
|
+ let result: mastodon.v1.Status;
|
|
|
+
|
|
|
+ console.log(`${this._name} | Sending status\n`, status);
|
|
|
+ result = await this._mastodonRestClient.v1.statuses.create({ status });
|
|
|
+
|
|
|
+ if (config.LOG_LEVEL === LogLevels.DEBUG) {
|
|
|
+ console.log(`${this._name} | Result`, result);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private parseCountdownEvents(): ICountdown[] {
|
|
|
+ const eventsString = config.COUNTDOWN_EVENTS;
|
|
|
+ if (!eventsString || eventsString.trim() === "") {
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+
|
|
|
+ const events: ICountdown[] = [];
|
|
|
+ const eventPairs = eventsString.split(";");
|
|
|
+
|
|
|
+ eventPairs.forEach((eventPair, index) => {
|
|
|
+ const [dateStr, message, hashTags] = eventPair.split("|");
|
|
|
+ if (dateStr && message) {
|
|
|
+ const targetDate = new Date(dateStr.trim());
|
|
|
+ const id = `countdown_${index}_${dateStr.trim()}`;
|
|
|
+
|
|
|
+ events.push({
|
|
|
+ id,
|
|
|
+ targetDate,
|
|
|
+ message: message.trim(),
|
|
|
+ hashtags: hashTags.split(",")
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ return events;
|
|
|
+ }
|
|
|
+
|
|
|
+ private async hasPublishedToday(eventId: string): Promise<boolean> {
|
|
|
+ const key = `countdown:published:${eventId}:${new Date().toISOString().split("T")[0]}`;
|
|
|
+ const result = await this._redisClient.retrieve(key);
|
|
|
+ return result !== null && result !== "";
|
|
|
+ }
|
|
|
+
|
|
|
+ private async isEventCompleted(eventId: string): Promise<boolean> {
|
|
|
+ const key = `countdown:completed:${eventId}`;
|
|
|
+ const result = await this._redisClient.retrieve(key);
|
|
|
+ return result !== null && result !== "";
|
|
|
+ }
|
|
|
+
|
|
|
+ private async markPublished(eventId: string): Promise<void> {
|
|
|
+ const key = `countdown:published:${eventId}:${new Date().toISOString().split("T")[0]}`;
|
|
|
+ await this._redisClient.store(key, "published", { EX: 60 * 60 * 24 });
|
|
|
+ }
|
|
|
+
|
|
|
+ private async markEventCompleted(eventId: string): Promise<void> {
|
|
|
+ const key = `countdown:completed:${eventId}`;
|
|
|
+ await this._redisClient.store(key, "completed");
|
|
|
+ }
|
|
|
+
|
|
|
+ private calculateDaysRemaining(targetDate: Date): number {
|
|
|
+ const today = new Date();
|
|
|
+ today.setHours(0, 0, 0, 0);
|
|
|
+ const target = new Date(targetDate);
|
|
|
+ target.setHours(0, 0, 0, 0);
|
|
|
+ const diffTime = target.getTime() - today.getTime();
|
|
|
+ return Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
|
|
+ }
|
|
|
+
|
|
|
+ private formatMessage(daysRemaining: number, message: string, hashtags?: string[]): string | null {
|
|
|
+ if (daysRemaining < 0) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ let status = `${Emojis.HOURGLASS} Quedan ${daysRemaining} días para ${message}`;
|
|
|
+
|
|
|
+ if (daysRemaining === 0) {
|
|
|
+ status = `${Emojis.TADA} ¡Hoy es el día! ${message} ${Emojis.TADA}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ // status += `\n\n${Emojis.CALENDAR} ${daysRemaining} días más para disfrutar este momento especial ${Emojis.SPARKLES}`;
|
|
|
+
|
|
|
+ if (hashtags && hashtags.length > 0) {
|
|
|
+ status += `\n\n${Emojis.TAGS} ${hashtags.map((h) => `#${h}`).join(" ")}`;
|
|
|
+ }
|
|
|
+
|
|
|
+ return status;
|
|
|
+ }
|
|
|
+
|
|
|
+ public async run(): Promise<void> {
|
|
|
+ try {
|
|
|
+ console.log(`${this._name} | Starting countdown check`);
|
|
|
+
|
|
|
+ const events = this.parseCountdownEvents();
|
|
|
+
|
|
|
+ if (events.length === 0) {
|
|
|
+ console.log(`${this._name} | No events configured`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`${this._name} | Found ${events.length} event(s)`);
|
|
|
+
|
|
|
+ for (const event of events) {
|
|
|
+ const daysRemaining = this.calculateDaysRemaining(event.targetDate);
|
|
|
+
|
|
|
+ if (await this.isEventCompleted(event.id) || await this.hasPublishedToday(event.id)) {
|
|
|
+ console.log(`${this._name} | Event already completed or published today for ${event.id}`);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ const status = this.formatMessage(daysRemaining, event.message, event.hashtags);
|
|
|
+
|
|
|
+ if (status === null) {
|
|
|
+ console.log(`${this._name} | Event already passed for ${event.id}`);
|
|
|
+ await this.markEventCompleted(event.id);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ await this.publish(status);
|
|
|
+ await this.markPublished(event.id);
|
|
|
+
|
|
|
+ if (daysRemaining === 0) {
|
|
|
+ await this.markEventCompleted(event.id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log(`${this._name} | Countdown check completed`);
|
|
|
+ } catch (error: any) {
|
|
|
+ console.error(`${this._name} | Error occurred`, error.message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public getHandler(): import("aws-lambda").Handler {
|
|
|
+ return async () => {
|
|
|
+ await this.run();
|
|
|
+ };
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+try {
|
|
|
+ const countdown = new Countdown(config.DEVELOP ? config.MASTODON_TEST_ACCESS_TOKEN : config.MASTODON_KEY_COUNTDOWN);
|
|
|
+ new CronJob(
|
|
|
+ "0 0 12 * * *",
|
|
|
+ () => countdown.run(),
|
|
|
+ null,
|
|
|
+ true,
|
|
|
+ config.DEFAULT_TIMEZONE
|
|
|
+ );
|
|
|
+
|
|
|
+ if (config.LOG_LEVEL === LogLevels.DEBUG) {
|
|
|
+ countdown.run();
|
|
|
+ }
|
|
|
+} catch (error) {
|
|
|
+ console.error(error);
|
|
|
+}
|