ソースを参照

refactoriced every portal

Pablo Barrera Yaksic 7 ヶ月 前
コミット
0b2c6d210b

+ 4 - 3
src/interfaces/article-scaper-options.ts

@@ -1,9 +1,10 @@
-export interface IArticleScraperOptions {
+export interface IScraperArticlesOptions {
   url: string
   articlesSelector: string
-  titleSelector?: string
-  contentSelector: string
+  titleSelector: string
+  contentSelector?: string
   linkSelector: string
+  linkPrefix?: string
   imageSelector?: string
   authorSelector?: string
   dateSelector?: string

+ 4 - 1
src/libs/redis-client.ts

@@ -15,7 +15,10 @@ export default class RedisClient {
   }
 
   private async connect (): Promise<void> {
-    this._client = await createClient({ url: config.REDIS_CONN }).connect();
+    this._client = await createClient({ url: config.REDIS_CONN }).connect().catch((err) => {
+      console.error("Redis connection error", err);
+      throw err;
+    });
   }
 
   private async disconnect (): Promise<void> {

+ 10 - 74
src/portales/elciudadano/handler.ts

@@ -1,78 +1,14 @@
-import type { Handler } from "aws-lambda";
-import { createRestAPIClient } from "masto";
-import "dotenv/config";
-
-import ScraperArticles from "../../utils/scraper-articles";
-import RedisClient from "../../libs/redis-client";
+import { type Handler } from "aws-lambda";
 
 import config from "../../config";
-import LogLevels from "../../enums/log-levels";
-import Emojis from "../../enums/emojis";
-
-export const handler: Handler = async (event, context) => {
-  const name = "El Ciudadano";
-  try {
-    const redisClient = new RedisClient();
-    const mastodon = createRestAPIClient({
-      url: config.MASTODON_URL,
-      accessToken: config.MASTODON_ACCESS_TOKEN
-    });
-    const scraperArticles = new ScraperArticles(name, {
-      url: config.ELCIUDADANO,
-      articlesSelector: "article",
-      titleSelector: "div div.col-md-7 a h3",
-      contentSelector: "div div.col-md-7 a p",
-      linkSelector: "div div.col-md-7 a",
-      imageSelector: "div div.col-md-5 img",
-      authorSelector: "",
-      dateSelector: ""
-    });
-
-    const articles = await scraperArticles.getArticles();
-    if (config.LOG_LEVEL === LogLevels.DEBUG) {
-      console.log("Articles", articles);
-    }
-
-    let totalPublished = 0;
-    const length = articles.length;
-
-    // Order has to be reversed to appear in the correct order when posting
-    for (let i = length - 1; i >= 0; i--) {
-      const article = articles[i];
-      const exists = await redisClient.retrieve(article.link);
-      if (exists !== null) {
-        continue;
-      }
-
-      const date = new Date(Date.now()).toLocaleDateString();
-      let message = `${Emojis.NEWS} ${article.title}.\n\n${article.content}\n${article.link}`;
-
-      if (message.trim().length === 0) {
-        continue;
-      }
-
-      if (message.length > 400) {
-        message = `${Emojis.NEWS} ${article.title}.\n\n${article.content}`.substring(0, 397) + "...";
-        message = `${message}\n${article.link}`;
-      }
-
-      console.log("\nSending", message);
+import Portal from "../portal";
 
-      await mastodon.v1.statuses.create({ status: message });
-      await redisClient.store(article.link, date, { EX: 60 * 60 * 24 }); // EX: 24 hrs expiration
-      totalPublished++
-    }
-    console.log(`Published ${totalPublished} new articles`);
-  } catch (err: any) {
-    console.log('An error has occurred\n')
-    console.error(err.message);
-    if (config.LOG_LEVEL === LogLevels.DEBUG) {
-      console.debug("\nEvent\n");
-      console.debug(event);
-      console.debug("\nContext\n");
-      console.debug(context);
-    }
-  }
+const name = "El Ciudadano";
 
-  return "The End.";
-};
+export const handler: Handler = new Portal(name, {
+  url: config.ELCIUDADANO,
+  articlesSelector: "article",
+  titleSelector: "div div.col-md-7 a h3",
+  contentSelector: "div div.col-md-7 a p",
+  linkSelector: "div div.col-md-7 a"
+}).getHandler();

+ 9 - 69
src/portales/eldesconcierto/handler.ts

@@ -1,73 +1,13 @@
-import type { Handler } from "aws-lambda";
-import { createRestAPIClient } from "masto";
-import "dotenv/config";
-
-import ScraperArticles from "../../utils/scraper-articles";
-import RedisClient from "../../libs/redis-client";
+import { type Handler } from "aws-lambda";
 
 import config from "../../config";
-import LogLevels from "../../enums/log-levels";
-import Emojis from "../../enums/emojis";
-
-export const handler: Handler = async (event, context) => {
-  const name = "El Desconcierto";
-  try {
-    const redisClient = new RedisClient();
-    const mastodon = createRestAPIClient({
-      url: config.MASTODON_URL,
-      accessToken: config.MASTODON_ACCESS_TOKEN
-    });
-    const scraperArticles = new ScraperArticles(name, {
-      url: config.ELDESCONCIERTO,
-      articlesSelector: "div.the-section__rows figure",
-      titleSelector: "figcaption h2",
-      contentSelector: "figcaption h2",
-      linkSelector: "a",
-      imageSelector: "a img",
-      authorSelector: "",
-      dateSelector: ""
-    });
-
-    const articles = await scraperArticles.getArticles();
-    if (config.LOG_LEVEL === LogLevels.DEBUG) {
-      console.log("Articles", articles);
-    }
-
-    let totalPublished = 0;
-    const length = articles.length;
-
-    // Order has to be reversed to appear in the correct order when posting
-    for (let i = length - 1; i >= 0; i--) {
-      const article = articles[i];
-      const exists = await redisClient.retrieve(article.link);
-      if (exists !== null) {
-        continue;
-      }
-
-      const date = new Date(Date.now()).toLocaleDateString();
-      const message = `${Emojis.NEWS} ${article.title} \n${article.link}`;
-
-      if (message.trim().length === 0) {
-        continue;
-      }
-
-      console.log("\nSending", message);
+import Portal from "../portal";
 
-      await mastodon.v1.statuses.create({ status: message });
-      await redisClient.store(article.link, date, { EX: 60 * 60 * 24 }); // EX: 24 hrs expiration
-      totalPublished++
-    }
-    console.log(`Published ${totalPublished} new articles`);
-  } catch (err: any) {
-    console.log('An error has occurred\n')
-    console.error(err.message);
-    if (config.LOG_LEVEL === LogLevels.DEBUG) {
-      console.debug("\nEvent\n");
-      console.debug(event);
-      console.debug("\nContext\n");
-      console.debug(context);
-    }
-  }
+const name = "El Desconcierto";
 
-  return "The End.";
-};
+export const handler: Handler = new Portal(name, {
+  url: config.ELDESCONCIERTO,
+  articlesSelector: "div.the-section__rows figure",
+  titleSelector: "figcaption h2",
+  linkSelector: "a"
+}).getHandler();

+ 10 - 69
src/portales/elmostrador/handler.ts

@@ -1,73 +1,14 @@
-import type { Handler } from "aws-lambda";
-import { createRestAPIClient } from "masto";
-import "dotenv/config";
-
-import ScraperArticles from "../../utils/scraper-articles";
-import RedisClient from "../../libs/redis-client";
+import { type Handler } from "aws-lambda";
 
 import config from "../../config";
-import LogLevels from "../../enums/log-levels";
-import Emojis from "../../enums/emojis";
-
-export const handler: Handler = async (event, context) => {
-  const name = "El Mostrado";
-  try {
-    const redisClient = new RedisClient();
-    const mastodon = createRestAPIClient({
-      url: config.MASTODON_URL,
-      accessToken: config.MASTODON_ACCESS_TOKEN
-    });
-    const scraperArticles = new ScraperArticles(name, {
-      url: config.ELMOSTRADOR,
-      articlesSelector: "div.d-section__body div.d-tag-card",
-      titleSelector: "h4",
-      contentSelector: "h4",
-      linkSelector: "h4 a",
-      imageSelector: "",
-      authorSelector: "span.d-tag-card__subtitle",
-      dateSelector: "time"
-    });
-
-    const articles = await scraperArticles.getArticles();
-    if (config.LOG_LEVEL === LogLevels.DEBUG) {
-      console.log("Articles", articles);
-    }
-
-    let totalPublished = 0;
-    const length = articles.length;
-
-    // Order has to be reversed to appear in the correct order when posting
-    for (let i = length - 1; i >= 0; i--) {
-      const article = articles[i];
-      const exists = await redisClient.retrieve(article.link);
-      if (exists !== null) {
-        continue;
-      }
-
-      const date = new Date(Date.now()).toLocaleDateString();
-      const message = `${Emojis.NEWS} ${article.title}\n${article.link}`;
-
-      if (message.trim().length === 0) {
-        continue;
-      }
-
-      console.log("\nSending", message);
+import Portal from "../portal";
 
-      await mastodon.v1.statuses.create({ status: message });
-      await redisClient.store(article.link, date, { EX: 60 * 60 * 24 }); // EX: 24 hrs expiration
-      totalPublished++
-    }
-    console.log(`Published ${totalPublished} new articles`);
-  } catch (err: any) {
-    console.log('An error has occurred\n')
-    console.error(err.message);
-    if (config.LOG_LEVEL === LogLevels.DEBUG) {
-      console.debug("\nEvent\n");
-      console.debug(event);
-      console.debug("\nContext\n");
-      console.debug(context);
-    }
-  }
+const name = "El Mostrado";
 
-  return "The End.";
-};
+export const handler: Handler = new Portal(name, {
+  url: config.ELMOSTRADOR,
+  articlesSelector: "div.d-section__body div.d-tag-card",
+  titleSelector: "h4",
+  contentSelector: "h4",
+  linkSelector: "h4 a"
+}).getHandler();

+ 13 - 77
src/portales/emol/handler.ts

@@ -1,79 +1,15 @@
-import type { Handler } from "aws-lambda";
-import { createRestAPIClient } from "masto";
-import "dotenv/config";
-
-import ScraperArticles from "../../utils/scraper-articles";
-import RedisClient from "../../libs/redis-client";
+import { type Handler } from "aws-lambda";
 
 import config from "../../config";
-import LogLevels from "../../enums/log-levels";
-import Emojis from "../../enums/emojis";
-
-export const handler: Handler = async (event, context) => {
-  const name = "EMOL - El Mercurio Online";
-  try {
-    const redisClient = new RedisClient();
-    const mastodon = createRestAPIClient({
-      url: config.MASTODON_URL,
-      accessToken: config.MASTODON_ACCESS_TOKEN
-    });
-    const scraperArticles = new ScraperArticles(name, {
-      url: config.EMOL,
-      articlesSelector: "div.cont_378_e_2015 div.col_center_noticias_item div.col_center_noticia4dest-360px",
-      titleSelector: "h3 a",
-      contentSelector: "h3 a",
-      linkSelector: "h3 a",
-      imageSelector: "div.contImgListadoNoticia a img",
-      authorSelector: "",
-      dateSelector: ""
-    });
-
-    const articles = await scraperArticles.getArticles();
-    if (config.LOG_LEVEL === LogLevels.DEBUG) {
-      console.log("Articles", articles);
-    }
-
-    let totalPublished = 0;
-    const length = articles.length;
-
-    // Order has to be reversed to appear in the correct order when posting
-    for (let i = length - 1; i >= 0; i--) {
-      const article = articles[i];
-      const exists = await redisClient.retrieve(article.link);
-      if (exists !== null) {
-        continue;
-      }
-
-      const date = new Date(Date.now()).toLocaleDateString();
-      const message = `${Emojis.NEWS} ${article.title}\nhttps:${article.link}`;
-      const mediaIds = [""];
-
-      if (message.trim().length === 0) {
-        continue;
-      }
-
-      if (article.image !== null) {
-        const media = await mastodon.v2.media.create({ file: article.image, description: article.title });
-        mediaIds.push(media.id);
-      }
-
-      console.log("\nSending", message);
-
-      await mastodon.v1.statuses.create({ status: message, mediaIds });
-      await redisClient.store(article.link, date, { EX: 60 * 60 * 24 }); // EX: 24 hrs expiration
-      totalPublished++
-    }
-    console.log(`Published ${totalPublished} new articles`);
-  } catch (err: any) {
-    console.log('An error has occurred\n')
-    console.error(err.message);
-    if (config.LOG_LEVEL === LogLevels.DEBUG) {
-      console.debug("\nEvent\n");
-      console.debug(event);
-      console.debug("\nContext\n");
-      console.debug(context);
-    }
-  }
-
-  return "The End.";
-};
+import Portal from "../portal";
+
+const name = "EMOL - El Mercurio Online";
+
+export const handler: Handler = new Portal(name, {
+  url: config.EMOL,
+  articlesSelector: "div.cont_378_e_2015 div.col_center_noticias_item div.col_center_noticia4dest-360px",
+  titleSelector: "h3 a",
+  linkSelector: "h3 a",
+  linkPrefix: "https:",
+  imageSelector: "div.contImgListadoNoticia a img"
+}).getHandler();

+ 13 - 77
src/portales/interferencia/handler.ts

@@ -1,79 +1,15 @@
-import type { Handler } from "aws-lambda";
-import { createRestAPIClient } from "masto";
-import "dotenv/config";
-
-import ScraperArticles from "../../utils/scraper-articles";
-import RedisClient from "../../libs/redis-client";
+import { type Handler } from "aws-lambda";
 
 import config from "../../config";
-import LogLevels from "../../enums/log-levels";
-import Emojis from "../../enums/emojis";
-
-export const handler: Handler = async (event, context) => {
-  const name = "Interferencia";
-  const baseDomain = "https://interferencia.cl"
-  try {
-    const redisClient = new RedisClient();
-    const mastodon = createRestAPIClient({
-      url: config.MASTODON_URL,
-      accessToken: config.MASTODON_ACCESS_TOKEN
-    });
-    const scraperArticles = new ScraperArticles(name, {
-      url: config.INTERFERENCIA,
-      articlesSelector: "div.row.container:eq(6) div.row div.col-md-4, div.row.container:eq(6) div.row div.col-md-6, div.row.container:eq(6) div.row div.col-md-8",
-      titleSelector: "div.views-field-title",
-      contentSelector: "div.views-field-field-subhead div.field-content p",
-      linkSelector: "div.views-field-title a",
-      imageSelector: "",
-      authorSelector: "div.views-field-field-cover-authors div.field-content",
-      dateSelector: "div.views-field-created span.field-content"
-    });
-
-    const articles = await scraperArticles.getArticles();
-    if (config.LOG_LEVEL === LogLevels.DEBUG) {
-      console.log("Articles", articles);
-    }
-
-    let totalPublished = 0;
-    const length = articles.length;
-
-    // Order has to be reversed to appear in the correct order when posting
-    for (let i = length - 1; i >= 0; i--) {
-      const article = articles[i];
-      const exists = await redisClient.retrieve(article.link);
-      if (exists !== null) {
-        continue;
-      }
-
-      const date = new Date(Date.now()).toLocaleDateString();
-      let message = `${Emojis.NEWS} ${article.title}.\n\n${article.content}\n${baseDomain}${article.link}`;
-
-      if (message.trim().length === 0) {
-        continue;
-      }
-
-      if (message.length > 400) {
-        message = `${Emojis.NEWS} ${article.title}.\n\n${article.content}`.substring(0, 397) + "...";
-        message = `${message}\n${baseDomain}${article.link}`;
-      }
-
-      console.log("\nSending", message);
-
-      await mastodon.v1.statuses.create({ status: message });
-      await redisClient.store(article.link, date, { EX: 60 * 60 * 24 }); // EX: 24 hrs expiration
-      totalPublished++
-    }
-    console.log(`Published ${totalPublished} new articles`);
-  } catch (err: any) {
-    console.log('An error has occurred\n')
-    console.error(err.message);
-    if (config.LOG_LEVEL === LogLevels.DEBUG) {
-      console.debug("\nEvent\n");
-      console.debug(event);
-      console.debug("\nContext\n");
-      console.debug(context);
-    }
-  }
-
-  return "The End.";
-};
+import Portal from "../portal";
+
+const name = "Interferencia";
+
+export const handler: Handler = new Portal(name, {
+  url: config.INTERFERENCIA,
+  articlesSelector: "div.row.container:eq(6) div.row div.col-md-4, div.row.container:eq(6) div.row div.col-md-6, div.row.container:eq(6) div.row div.col-md-8",
+  titleSelector: "div.views-field-title",
+  contentSelector: "div.views-field-field-subhead div.field-content p",
+  linkSelector: "div.views-field-title a",
+  linkPrefix: "https://interferencia.cl"
+}).getHandler();

+ 10 - 71
src/portales/latercera/handler.ts

@@ -1,75 +1,14 @@
-import type { Handler } from "aws-lambda";
-import { createRestAPIClient } from "masto";
-import "dotenv/config";
-
-import ScraperArticles from "../../utils/scraper-articles";
-import RedisClient from "../../libs/redis-client";
+import { type Handler } from "aws-lambda";
 
 import config from "../../config";
-import LogLevels from "../../enums/log-levels";
-import Emojis from "../../enums/emojis";
-
-export const handler: Handler = async (event, context) => {
-  const name = "La Tercera";
-  const baseDomain = "https://www.latercera.cl"
-  try {
-    const redisClient = new RedisClient();
-    const mastodon = createRestAPIClient({
-      url: config.MASTODON_URL,
-      accessToken: config.MASTODON_ACCESS_TOKEN
-    });
-    // const scraper = new Scraper();
-    const scraperArticles = new ScraperArticles(name, {
-      url: config.LATERCERA,
-      articlesSelector: "article.card",
-      titleSelector: "div.headline",
-      contentSelector: "div.deck",
-      linkSelector: "div.headline a",
-      imageSelector: "div.image figure a picture img",
-      authorSelector: "div.name a small",
-      dateSelector: ""
-    });
-
-    const articles = await scraperArticles.getArticles();
-    if (config.LOG_LEVEL === LogLevels.DEBUG) {
-      console.log("Articles", articles);
-    }
-
-    let totalPublished = 0;
-    const length = articles.length;
-
-    // Order has to be reversed to appear in the correct order when posting
-    for (let i = length - 1; i >= 0; i--) {
-      const article = articles[i];
-      const exists = await redisClient.retrieve(article.link);
-      if (exists !== null) {
-        continue;
-      }
-
-      const date = new Date(Date.now()).toLocaleDateString();
-      const message = `${Emojis.NEWS} ${article.title}\n${baseDomain}${article.link}`;
-
-      if (message.trim().length === 0) {
-        continue;
-      }
-
-      console.log("\nSending", message);
+import Portal from "../portal";
 
-      await mastodon.v1.statuses.create({ status: message });
-      await redisClient.store(article.link, date, { EX: 60 * 60 * 24 }); // EX: 24 hrs expiration
-      totalPublished++
-    }
-    console.log(`Published ${totalPublished} new articles`);
-  } catch (err: any) {
-    console.log('An error has occurred\n')
-    console.error(err.message);
-    if (config.LOG_LEVEL === LogLevels.DEBUG) {
-      console.debug("\nEvent\n");
-      console.debug(event);
-      console.debug("\nContext\n");
-      console.debug(context);
-    }
-  }
+const name = "La Tercera";
 
-  return "The End.";
-};
+export const handler: Handler = new Portal(name, {
+  url: config.LATERCERA,
+  articlesSelector: "article.card",
+  titleSelector: "div.headline",
+  linkSelector: "div.headline a",
+  linkPrefix: "https://www.latercera.cl"
+}).getHandler();

+ 106 - 0
src/portales/portal.ts

@@ -0,0 +1,106 @@
+import { type Handler } from "aws-lambda";
+import { createRestAPIClient } from "masto";
+
+import ScraperArticles from "../utils/scraper-articles";
+import RedisClient from "../libs/redis-client";
+import LogLevels from "../enums/log-levels";
+import Emojis from "../enums/emojis";
+import config from "../config";
+
+import { type IScraperArticlesOptions } from "../interfaces/article-scaper-options";
+
+export default class Portal {
+  private readonly _name: string;
+  private readonly _scraperArticlesOptions: IScraperArticlesOptions
+  private readonly _redisClient: RedisClient;
+  private readonly _mastodonClient: any
+  private readonly _scraperArticles: ScraperArticles;
+
+  constructor (name: string, scraperArticlesOptions: IScraperArticlesOptions) {
+    this._name = name;
+    this._scraperArticlesOptions = scraperArticlesOptions;
+    this._scraperArticles = new ScraperArticles(this._name, this._scraperArticlesOptions);
+    this._redisClient = new RedisClient();
+    this._mastodonClient = createRestAPIClient({
+      url: config.MASTODON_URL,
+      accessToken: config.MASTODON_ACCESS_TOKEN
+    });
+  }
+
+  public async run (event?: any, context?: any): Promise<void> {
+    try {
+      const articles = await this._scraperArticles.getArticles();
+      if (config.LOG_LEVEL === LogLevels.DEBUG) {
+        console.log(`${this._name} | Articles`, articles);
+      }
+
+      let totalPublished = 0;
+      const length = articles.length;
+
+      // Order has to be reversed to appear in the correct order when posting
+      for (let i = length - 1; i >= 0; i--) {
+        const article = articles[i];
+        const exists = await this._redisClient.retrieve(article.link);
+        if (exists !== null) {
+          continue;
+        }
+
+        const date = new Date(Date.now()).toLocaleDateString();
+        let message = `${Emojis.NEWS} ${article.title}.`;
+        if (article.content !== "") {
+          message += `\n\n${article.content}`;
+        }
+        message += `\n${article.link}`;
+
+        if (message.trim().length === 0) {
+          continue;
+        }
+
+        // To avoid publiposts (I'm lookint at you La Tercera >:-|)
+        if (article.link.includes("publirreportajes")) {
+          continue;
+        }
+
+        // If the message is more than 400 characters long, its very likely due to the article.content
+        if (message.length > 400) {
+          message = `${Emojis.NEWS} ${article.title}.\n\n${article.content}`.substring(0, 397) + "...";
+          message += `\n${article.link}`;
+        }
+
+        const mediaIds: any[] = [];
+        if (article.image !== null && article.image !== undefined) {
+          const media = await this._mastodonClient.v2.media.create({ file: article.image, description: article.title });
+          mediaIds.push(media.id);
+        }
+
+        console.log(`\n${this._name} | Sending`, message);
+
+        // await this._mastodonClient.v1.statuses.create({ status: message, mediaIds });
+        // await this._redisClient.store(article.link, date, { EX: 60 * 60 * 24 }); // EX: 24 hrs expiration
+        totalPublished++
+      }
+      console.log(`${this._name} | Published ${totalPublished} new articles`);
+    } 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);
+        }
+      }
+    }
+    console.log(`${this._name} | Finished`);
+  }
+
+  public getHandler (): Handler {
+    return async (event, context) => {
+      await this.run(event, context);
+    }
+  }
+}

+ 9 - 69
src/portales/theclinic/handler.ts

@@ -1,73 +1,13 @@
-import type { Handler } from "aws-lambda";
-import { createRestAPIClient } from "masto";
-import "dotenv/config";
-
-import ScraperArticles from "../../utils/scraper-articles";
-import RedisClient from "../../libs/redis-client";
+import { type Handler } from "aws-lambda";
 
 import config from "../../config";
-import LogLevels from "../../enums/log-levels";
-import Emojis from "../../enums/emojis";
-
-export const handler: Handler = async (event, context) => {
-  const name = "The Clinic";
-  try {
-    const redisClient = new RedisClient();
-    const mastodon = createRestAPIClient({
-      url: config.MASTODON_URL,
-      accessToken: config.MASTODON_ACCESS_TOKEN
-    });
-    const scraperArticles = new ScraperArticles(name, {
-      url: config.THECLINIC,
-      articlesSelector: ".listado article",
-      titleSelector: ".titulares h2 a",
-      contentSelector: ".titulares h2 a",
-      linkSelector: ".titulares h2 a",
-      imageSelector: ".imagen-post a img",
-      authorSelector: "",
-      dateSelector: ""
-    });
-
-    const articles = await scraperArticles.getArticles();
-    if (config.LOG_LEVEL === LogLevels.DEBUG) {
-      console.log("Articles", articles);
-    }
-
-    let totalPublished = 0;
-    const length = articles.length;
-
-    // Order has to be reversed to appear in the correct order when posting
-    for (let i = length - 1; i >= 0; i--) {
-      const article = articles[i];
-      const exists = await redisClient.retrieve(article.link);
-      if (exists !== null) {
-        continue;
-      }
-
-      const date = new Date(Date.now()).toLocaleDateString();
-      const message = `${Emojis.NEWS} ${article.title}\n${article.link}`;
-
-      if (message.trim().length === 0) {
-        continue;
-      }
-
-      console.log("\nSending", message);
+import Portal from "../portal";
 
-      await mastodon.v1.statuses.create({ status: message });
-      await redisClient.store(article.link, date, { EX: 60 * 60 * 24 }); // EX: 24 hrs expiration
-      totalPublished++
-    }
-    console.log(`Published ${totalPublished} new articles`);
-  } catch (err: any) {
-    console.log('An error has occurred\n')
-    console.error(err.message);
-    if (config.LOG_LEVEL === LogLevels.DEBUG) {
-      console.debug("\nEvent\n");
-      console.debug(event);
-      console.debug("\nContext\n");
-      console.debug(context);
-    }
-  }
+const name = "The Clinic";
 
-  return "The End.";
-};
+export const handler: Handler = new Portal(name, {
+  url: config.THECLINIC,
+  articlesSelector: ".listado article",
+  titleSelector: ".titulares h2 a",
+  linkSelector: ".titulares h2 a"
+}).getHandler();

+ 8 - 6
src/utils/scraper-articles.ts

@@ -1,9 +1,10 @@
 import { type AnyNode, load } from "cheerio";
+import "dotenv/config";
 
 import config from "../config";
 import Scraper from "./scraper";
 
-import type { IArticleScraperOptions } from "../interfaces/article-scaper-options";
+import type { IScraperArticlesOptions } from "../interfaces/article-scaper-options";
 import type { IArticle } from "../interfaces/article";
 
 import LogLevels from "../enums/log-levels";
@@ -11,10 +12,10 @@ import Props from "../enums/props";
 
 export default class ScraperArticles {
   private readonly _name: string;
-  private readonly _options: IArticleScraperOptions;
+  private readonly _options: IScraperArticlesOptions;
   private readonly _scraper: Scraper;
 
-  constructor (name: string, options: IArticleScraperOptions) {
+  constructor (name: string, options: IScraperArticlesOptions) {
     this._name = name;
     this._options = options;
     this._scraper = new Scraper();
@@ -47,7 +48,8 @@ export default class ScraperArticles {
 
   private getLink (article: AnyNode): string {
     const selector = this._options.linkSelector ?? "";
-    return selector !== "" ? this.getProperty(article, selector, Props.LINK) : this._options.url;
+    const url = selector !== "" ? this.getProperty(article, selector, Props.LINK) : this._options.url
+    return this._options.linkPrefix !== undefined ? this._options.linkPrefix + url : url;
   }
 
   private async getImage (article: AnyNode): Promise<File | null> {
@@ -59,7 +61,7 @@ export default class ScraperArticles {
       imgFile = this._scraper.scrapeFile(imgUrl)
         .catch((err) => {
           if (config.LOG_LEVEL === LogLevels.DEBUG) {
-            console.debug(`${this._name} | Error raised\n`);
+            console.debug(`${this._name} | Error\n`);
             console.debug(`From ${article.type} can't get image using selector '${selector}'`);
             console.error(err.message);
           }
@@ -107,7 +109,7 @@ export default class ScraperArticles {
       }
     } catch (err) {
       if (config.LOG_LEVEL === LogLevels.DEBUG) {
-        console.debug(`${this._name} | Error raised\n`);
+        console.debug(`${this._name} | Error\n`);
         console.error(err.message);
       }
     } finally {