diff --git a/.github/assets/example/feeds.toml b/.github/assets/example/feeds.toml index f241297..211bee9 100644 --- a/.github/assets/example/feeds.toml +++ b/.github/assets/example/feeds.toml @@ -13,8 +13,18 @@ xmlUrl = "https://feed2.com/rss" name = "list 2" [[lists.feeds]] -title = "feed 1" -xmlUrl = "https://feed1.com/rss" +title = "bluesky" +type = "bluesky" +id = "username" [[lists.feeds]] -title = "feed 2" -xmlUrl = "https://feed2.com/rss" +title = "note" +type = "note" +id = "username" +[[lists.feeds]] +title = "reddit" +type = "reddit" +id = "subreddit-name" +[[lists.feeds]] +title = "sizu.me" +type = "sizu.me" +id = "username" diff --git a/.github/workflows/deps-update.yml b/.github/workflows/deps-update.yml index b9f427c..13ddf87 100644 --- a/.github/workflows/deps-update.yml +++ b/.github/workflows/deps-update.yml @@ -15,7 +15,7 @@ jobs: steps: - name: 🚚 Checkout Repository - uses: actions/checkout@v4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: ref: dev diff --git a/.github/workflows/gist-update.yml b/.github/workflows/gist-update.yml index fd55f4b..d25e983 100644 --- a/.github/workflows/gist-update.yml +++ b/.github/workflows/gist-update.yml @@ -11,10 +11,10 @@ jobs: steps: - name: 🚚 Checkout Repository - uses: actions/checkout@v4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: 🦕 Setup Deno - uses: denoland/setup-deno@v1 + uses: denoland/setup-deno@041b854f97b325bd60e53e9dc2de9cb9f9ac0cba # v1.1.4 with: deno-version: v1.x diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1a6f646..289b65e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -21,10 +21,10 @@ jobs: steps: - name: 🚚 Checkout Repository - uses: actions/checkout@v4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - name: 🦕 Setup Deno - uses: denoland/setup-deno@v1 + uses: denoland/setup-deno@041b854f97b325bd60e53e9dc2de9cb9f9ac0cba # v1.1.4 with: deno-version: v1.x @@ -41,6 +41,6 @@ jobs: run: deno check ./**/*.ts - name: ☂️ Upload Coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 with: token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 0f06f34..c57ccee 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ +/feeds.toml +/outputs/ /coverage /coverage.lcov diff --git a/src/libs/convert.ts b/src/libs/convert.ts index 3c59a27..485cac1 100644 --- a/src/libs/convert.ts +++ b/src/libs/convert.ts @@ -1,12 +1,15 @@ import { stringify } from "@libs/xml"; import { parse } from "@std/toml"; +import { transcodeXmlUrl } from "./sites.ts"; import type { Feed, List, Lists, OPMLOutline } from "../types/mod.ts"; -export function convertToTOML(data: string): Lists { +export function convertFromTOML(data: string): Lists { const lists: Lists = parse(data) as Lists; lists.lists.map((list: List) => { list.feeds.map((feed: Feed) => { - feed.xmlUrl = new URL(feed.xmlUrl); + feed.xmlUrl = feed.xmlUrl + ? new URL(feed.xmlUrl) + : transcodeXmlUrl(feed.title, feed.type, feed.id); }); }); return lists; @@ -18,7 +21,9 @@ export function convertToOPML(list: List): string { return { "@title": feed.title, "@text": feed.title, - "@xmlUrl": feed.xmlUrl, + "@xmlUrl": feed.xmlUrl + ? new URL(feed.xmlUrl) + : transcodeXmlUrl(feed.title, feed.type, feed.id), "@type": "rss", }; }), diff --git a/src/libs/io.ts b/src/libs/io.ts index 1edb027..cd4176c 100644 --- a/src/libs/io.ts +++ b/src/libs/io.ts @@ -1,12 +1,12 @@ import { paramCase } from "@wok/case"; import { format } from "@std/path"; -import { convertToOPML, convertToTOML } from "./convert.ts"; +import { convertFromTOML, convertToOPML } from "./convert.ts"; import type { List, Lists } from "../types/mod.ts"; export async function readTOML(file: string): Promise { try { const data: string = await Deno.readTextFile(file); - return convertToTOML(data); + return convertFromTOML(data); } catch (error) { if (error instanceof Deno.errors.NotFound) { throw new Error(`File not found: "${file}"`); diff --git a/src/libs/sites.ts b/src/libs/sites.ts new file mode 100644 index 0000000..311b1a8 --- /dev/null +++ b/src/libs/sites.ts @@ -0,0 +1,23 @@ +import type { site } from "../types/mod.ts"; + +const sites: site[] = [ + { type: "bluesky", url: new URL("https://bsky.app/profile/{id}/rss") }, + { type: "note", url: new URL("https://note.com/{id}/rss") }, + { type: "reddit", url: new URL("https://www.reddit.com/r/{id}/.rss") }, + { type: "sizu.me", url: new URL("https://sizu.me/{id}/rss") }, +]; + +export function transcodeXmlUrl( + title: string, + type: string | undefined, + id: string | undefined, +): URL { + if (!type) throw new Error(`Parameter not set: "type" of "${title}"`); + if (!id) throw new Error(`Parameter not set: "id" of "${title}"`); + + const url: URL | undefined = sites + .find((site: site) => site.type === type)?.url; + if (!url) throw new Error(`Site not found: "${type}" of "${title}"`); + + return new URL(url.href.replace(encodeURI("{id}"), id)); +} diff --git a/src/types/toml.ts b/src/types/feed.ts similarity index 74% rename from src/types/toml.ts rename to src/types/feed.ts index 881c0f4..d185870 100644 --- a/src/types/toml.ts +++ b/src/types/feed.ts @@ -7,6 +7,7 @@ export type List = { }; export type Feed = { title: string; - text: string; - xmlUrl: URL; + type?: string; + id?: string; + xmlUrl?: URL; }; diff --git a/src/types/mod.ts b/src/types/mod.ts index 9de745d..e4ab14f 100644 --- a/src/types/mod.ts +++ b/src/types/mod.ts @@ -1,2 +1,3 @@ -export * from "./toml.ts"; export * from "./opml.ts"; +export * from "./feed.ts"; +export * from "./sites.ts"; diff --git a/src/types/sites.ts b/src/types/sites.ts new file mode 100644 index 0000000..3b4381b --- /dev/null +++ b/src/types/sites.ts @@ -0,0 +1,4 @@ +export type site = { + type: string; + url: URL; +}; diff --git a/test/libs/convert_test.ts b/test/libs/convert_test.ts index 4540fb2..32a0c0e 100644 --- a/test/libs/convert_test.ts +++ b/test/libs/convert_test.ts @@ -1,37 +1,57 @@ import { assertEquals } from "@std/assert"; -import { convertToOPML, convertToTOML } from "../../src/libs/convert.ts"; +import { convertFromTOML, convertToOPML } from "../../src/libs/convert.ts"; import type { List, Lists } from "../../src/types/mod.ts"; -Deno.test("Parse TOML", () => { +Deno.test("Parse TOML (RSS)", () => { + const toml = ` +[[lists]] +name = "list name" + +[[lists.feeds]] +title = "feed title" +xmlUrl = "https://example.com/feed" +`; + const feeds: Lists = { - lists: [ - { - name: "list name", - feeds: [ - { - title: "feed title", - text: "feed title", - xmlUrl: new URL("https://example.com/feed"), - }, - ], - }, - ], + lists: [{ + name: "list name", + feeds: [{ + title: "feed title", + xmlUrl: new URL("https://example.com/feed"), + }], + }], }; + assertEquals(convertFromTOML(toml), feeds); +}); + +Deno.test("Parse TOML (Site)", () => { const toml = ` [[lists]] name = "list name" [[lists.feeds]] title = "feed title" -text = "feed title" -xmlUrl = "https://example.com/feed" +type = "bluesky" +id = "username" `; - assertEquals(convertToTOML(toml), feeds); + const feeds: Lists = { + lists: [{ + name: "list name", + feeds: [{ + title: "feed title", + type: "bluesky", + id: "username", + xmlUrl: new URL("https://bsky.app/profile/username/rss"), + }], + }], + }; + + assertEquals(convertFromTOML(toml), feeds); }); -Deno.test("Convert Lists to OPML", () => { +Deno.test("Convert Lists to OPML (RSS)", () => { const xml = `\ @@ -43,14 +63,33 @@ Deno.test("Convert Lists to OPML", () => { const list: List = { name: "list name", - feeds: [ - { - title: "feed title", - text: "feed title", - xmlUrl: new URL("https://example.com/feed"), - }, - ], + feeds: [{ + title: "feed title", + xmlUrl: new URL("https://example.com/feed"), + }], + }; + + assertEquals(convertToOPML(list), xml); +}); + +Deno.test("Convert Lists to OPML (Site)", () => { + const xml = `\ + + + + + + +`; + + const list: List = { + name: "list name", + feeds: [{ + title: "feed title", + type: "bluesky", + id: "username", + }], }; - assertEquals(xml, convertToOPML(list)); + assertEquals(convertToOPML(list), xml); }); diff --git a/test/libs/io_test.ts b/test/libs/io_test.ts index a0ec944..9f4ba99 100644 --- a/test/libs/io_test.ts +++ b/test/libs/io_test.ts @@ -1,7 +1,7 @@ import { assertEquals, assertIsError } from "@std/assert"; import { join } from "@std/path"; import { readTOML, writeXML } from "../../src/libs/io.ts"; -import { convertToOPML, convertToTOML } from "../../src/libs/convert.ts"; +import { convertFromTOML, convertToOPML } from "../../src/libs/convert.ts"; import type { Lists } from "../../src/types/mod.ts"; Deno.test("Read TOML", async () => { @@ -18,7 +18,7 @@ xmlUrl = "https://example.com/feed" await Deno.writeTextFile(file, toml); const lists: Lists = await readTOML(file); - assertEquals(convertToTOML(toml), lists); + assertEquals(convertFromTOML(toml), lists); }); Deno.test("Read TOML (File not found)", async () => { @@ -55,7 +55,6 @@ Deno.test("Write XML", async () => { feeds: [ { title: "feed title", - text: "feed title", xmlUrl: new URL("https://example.com/feed"), }, ], diff --git a/test/libs/sites_test.ts b/test/libs/sites_test.ts new file mode 100644 index 0000000..cd2f1a9 --- /dev/null +++ b/test/libs/sites_test.ts @@ -0,0 +1,45 @@ +import { assertEquals } from "@std/assert"; +import { transcodeXmlUrl } from "../../src/libs/sites.ts"; +import type { Feed } from "../../src/types/mod.ts"; + +Deno.test("Get XML URL (Site)", () => { + const feed: Feed = { + title: "feed title", + type: "bluesky", + id: "username", + }; + assertEquals( + transcodeXmlUrl(feed.title, feed.type, feed.id), + new URL("https://bsky.app/profile/username/rss"), + ); +}); + +Deno.test("Get XML URL (type not set)", () => { + const feed: Feed = { title: "feed title", id: "username" }; + try { + transcodeXmlUrl(feed.title, undefined, feed.id); + } catch (error) { + assertEquals(error.message, `Parameter not set: "type" of "${feed.title}"`); + } +}); + +Deno.test("Get XML URL (id not set)", () => { + const feed: Feed = { title: "feed title", type: "bluesky" }; + try { + transcodeXmlUrl(feed.title, feed.type, undefined); + } catch (error) { + assertEquals(error.message, `Parameter not set: "id" of "${feed.title}"`); + } +}); + +Deno.test("Get XML URL (Site not found)", () => { + const feed: Feed = { title: "feed title", type: "unknown", id: "username" }; + try { + transcodeXmlUrl(feed.title, feed.type, feed.id); + } catch (error) { + assertEquals( + error.message, + `Site not found: "${feed.type}" of "${feed.title}"`, + ); + } +});