| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199 |
- import { type Handler } from "aws-lambda";
- import { createRestAPIClient, mastodon } from "masto";
- import { CronJob } from "cron";
- import Scraper from "../../utils/scraper";
- import ScraperMethods from "../../enums/scraper-methods";
- import Emojis from "../../enums/emojis";
- import LogLevels from "../../enums/log-levels";
- import config from "../../config";
- interface IStreamConfig {
- id: number;
- location: string;
- url: string;
- hashtags: string[];
- screenshotSelector?: string;
- clickSelector?: string;
- screenshotDelay?: number;
- }
- export default class VideoStreams {
- private readonly _name: string = "Video Streams";
- private readonly _mastodonClient: mastodon.rest.Client;
- private readonly _scraper: Scraper;
- private readonly _defaultLocation: string = "Transmisión en vivo";
- constructor(accessToken = config.MASTODON_TEST_ACCESS_TOKEN) {
- accessToken = config.DEVELOP ? config.MASTODON_TEST_ACCESS_TOKEN : accessToken;
- this._mastodonClient = createRestAPIClient({ url: config.MASTODON_URL, accessToken });
- this._scraper = new Scraper();
- }
- private parseStreams(): IStreamConfig[] {
- const streamsString = config.VIDEO_STREAMS;
- if (!streamsString || streamsString.trim() === "") {
- return [];
- }
- try {
- const parsed = JSON.parse(streamsString);
- if (!Array.isArray(parsed)) {
- throw new Error("VIDEO_STREAMS must be a JSON array");
- }
- let id = 1;
- const streams: IStreamConfig[] = [];
- parsed.forEach((item: any) => {
- const location = String(item.location ?? "").trim();
- const url = String(item.url ?? "").trim();
- if (url === "") {
- return;
- }
- streams.push({
- id: id++,
- location,
- url,
- hashtags: Array.isArray(item.hashtags)
- ? item.hashtags.map((h: unknown) => String(h).trim()).filter((h: string) => h !== "")
- : [],
- screenshotSelector: item.screenshotSelector !== undefined ? String(item.screenshotSelector).trim() : undefined,
- clickSelector: item.clickSelector !== undefined ? String(item.clickSelector).trim() : undefined,
- screenshotDelay: item.screenshotDelay !== undefined ? Number(item.screenshotDelay) : undefined
- });
- });
- return streams;
- } catch (err: any) {
- console.error(`${this._name} | Error parsing VIDEO_STREAMS: ${err.message}`);
- throw err;
- }
- }
- private base64ToFile(base64: string, fileName: string): File {
- const buffer = Buffer.from(base64, "base64");
- return new File([buffer], fileName, { type: "image/png" });
- }
- private generateUniqueTag(id: number): string {
- return `EnVivoCL_${String(id).padStart(3, "0")}`;
- }
- private buildMessage(stream: IStreamConfig): string {
- const location = stream.location !== "" ? stream.location : this._defaultLocation;
- const uniqueTag = this.generateUniqueTag(stream.id);
- const tags = [uniqueTag, ...stream.hashtags];
- let message = `${Emojis.VIDEO_CAMERA} Transmisión en vivo desde ${location}`;
- message += `\n\n${Emojis.LINK} ${stream.url}`;
- if (tags.length > 0) {
- message += `\n\n${tags.map((hashtag) => `#${hashtag}`).join(" ")}`;
- }
- return message;
- }
- private async publish(stream: IStreamConfig, screenshotBase64: string): Promise<void> {
- try {
- const media = await this._mastodonClient.v2.media.create({
- file: this.base64ToFile(screenshotBase64, `${stream.location.replace(/\s+/g, "_")}.png`),
- description: `Transmisión en vivo desde ${stream.location}`
- });
- const message = this.buildMessage(stream);
- console.log(`\n${this._name} | Sending\n`, message);
- await this._mastodonClient.v1.statuses.create({ status: message, mediaIds: [media.id] });
- } catch (err: any) {
- console.error(`${this._name} | Error publishing stream ${stream.location}`);
- console.error(err.message);
- }
- }
- public async run(event?: any, context?: any): Promise<void> {
- try {
- const streams = this.parseStreams();
- if (streams.length === 0) {
- console.log(`${this._name} | No streams configured`);
- return;
- }
- console.log(`${this._name} | Found ${streams.length} stream(s)`);
- for (const stream of streams) {
- console.log(`${this._name} | Capturing ${stream.location}`);
- const scrapeOptions: any = {
- url: stream.url,
- scraperMethod: ScraperMethods.PUPPETEER,
- screenshotDelay: stream.screenshotDelay ?? config.VIDEO_STREAMS_SCREENSHOT_DELAY
- };
- if (stream.screenshotSelector !== undefined && stream.screenshotSelector !== "") {
- scrapeOptions.screenshot = true;
- scrapeOptions.screenshotSelector = stream.screenshotSelector;
- }
- if (stream.clickSelector !== undefined && stream.clickSelector !== "") {
- scrapeOptions.clickSelector = stream.clickSelector;
- }
- const response = await this._scraper.scrape(scrapeOptions);
- const screenshotBase64 = response?.data?.data?.screenshot;
- if (!screenshotBase64 || typeof screenshotBase64 !== "string") {
- console.log(`${this._name} | No screenshot obtained for ${stream.location}`);
- continue;
- }
- await this.publish(stream, screenshotBase64);
- }
- console.log(`${this._name} | Finished`);
- } catch (err: any) {
- console.log(`${this._name} | An error has occurred\n`);
- console.error(err.message);
- if (config.LOG_LEVEL === LogLevels.DEBUG) {
- if (event !== undefined) {
- console.debug("\nEvent\n");
- console.debug(event);
- }
- if (context !== undefined) {
- console.debug("\nContext\n");
- console.debug(context);
- }
- }
- }
- }
- public getHandler(): Handler {
- return async (event, context) => {
- await this.run(event, context);
- };
- }
- }
- try {
- const videoStreams = new VideoStreams(config.DEVELOP ? config.MASTODON_TEST_ACCESS_TOKEN : config.MASTODON_KEY_VIDEO_STREAMS);
- new CronJob(
- "0 0 * * * *",
- () => videoStreams.run(),
- null,
- true,
- config.DEFAULT_TIMEZONE
- );
- if (config.DEVELOP) {
- videoStreams.run();
- }
- } catch (error) {
- console.error(error);
- }
|