diff options
| author | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-04-09 00:04:15 +1000 |
|---|---|---|
| committer | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-04-09 00:04:15 +1000 |
| commit | ed25a8790c43555d58c3debfcc2ec951fd4516fb (patch) | |
| tree | cdf4ee5213d61b4324558e4f9018ef1b9933daaa /src | |
| parent | players: fix duplicate players (diff) | |
| download | caelestia-shell-ed25a8790c43555d58c3debfcc2ec951fd4516fb.tar.gz caelestia-shell-ed25a8790c43555d58c3debfcc2ec951fd4516fb.tar.bz2 caelestia-shell-ed25a8790c43555d58c3debfcc2ec951fd4516fb.zip | |
feat: news service
Diffstat (limited to 'src')
| -rw-r--r-- | src/config/defaults.ts | 8 | ||||
| -rw-r--r-- | src/config/index.ts | 1 | ||||
| -rw-r--r-- | src/config/types.ts | 6 | ||||
| -rw-r--r-- | src/services/news.ts | 109 |
4 files changed, 124 insertions, 0 deletions
diff --git a/src/config/defaults.ts b/src/config/defaults.ts index 59cc215..cccc421 100644 --- a/src/config/defaults.ts +++ b/src/config/defaults.ts @@ -164,4 +164,12 @@ export default { exact: true, }, }, + news: { + apiKey: "", // An API key from https://newsdata.io for accessing news + countries: ["current"], // A list of country codes or "current" for the current location + categories: [] as string[], // A list of news categories to filter by + languages: ["en"], // A list of languages codes to filter by + domains: [] as string[], // A list of news domains to pull from + timezone: "", // A timezone to filter by, e.g. "America/New_York" + }, }; diff --git a/src/config/index.ts b/src/config/index.ts index abdb094..80b4dc4 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -21,5 +21,6 @@ export const { wallpapers, calendar, thumbnailer, + news, } = config; export default config; diff --git a/src/config/types.ts b/src/config/types.ts index 68f1824..736548a 100644 --- a/src/config/types.ts +++ b/src/config/types.ts @@ -94,4 +94,10 @@ export default { "thumbnailer.defaults.width": NUM, "thumbnailer.defaults.height": NUM, "thumbnailer.defaults.exact": BOOL, + "news.apiKey": STR, + "news.countries": ARR(STR), + "news.categories": ARR(STR), + "news.languages": ARR(STR), + "news.domains": ARR(STR), + "news.timezone": STR, } as { [k: string]: string | string[] | number[] }; diff --git a/src/services/news.ts b/src/services/news.ts new file mode 100644 index 0000000..a08f19c --- /dev/null +++ b/src/services/news.ts @@ -0,0 +1,109 @@ +import { notify } from "@/utils/system"; +import { execAsync, GLib, GObject, property, readFileAsync, register, writeFileAsync } from "astal"; +import { news as config } from "config"; + +export interface Article { + title: string; + link: string; + keywords: string; + creator: string; + description: string; + pubDate: string; +} + +@register({ GTypeName: "News" }) +export default class News extends GObject.Object { + static instance: News; + static get_default() { + if (!this.instance) this.instance = new News(); + + return this.instance; + } + + readonly #cachePath = `${CACHE}/news.json`; + + #loading: boolean = false; + #articles: Article[] = []; + + @property(Boolean) + get loading() { + return this.#loading; + } + + @property(Object) + get articles() { + return this.#articles; + } + + async getNews() { + if (!config.apiKey.get()) { + notify({ + summary: "A newsdata.io API key is required", + body: "You can get one by creating an account at https://newsdata.io", + icon: "dialog-error-symbolic", + urgency: "critical", + actions: { + "Get API key": () => execAsync("app2unit -O -- https://newsdata.io").catch(console.error), + }, + }); + return; + } + + this.#loading = true; + this.notify("loading"); + + let countries = config.countries.get().join(","); + const categories = config.categories.get().join(","); + const languages = config.languages.get().join(","); + const domains = config.domains.get().join(","); + const timezone = config.timezone.get(); + + if (countries.includes("current")) { + const out = JSON.parse(await execAsync("curl ipinfo.io")).country.toLowerCase(); + countries = countries.replace("current", out); + } + + let args = "removeduplicate=1&prioritydomain=top"; + if (countries) args += `&country=${countries}`; + if (categories) args += `&category=${categories}`; + if (languages) args += `&language=${languages}`; + if (domains) args += `&domain=${domains}`; + if (timezone) args += `&timezone=${timezone}`; + + const url = `https://newsdata.io/api/1/latest?apikey=${config.apiKey.get()}&${args}`; + try { + const res = JSON.parse(await execAsync(["curl", url])); + this.#articles = res.results; + + let page = res.nextPage; + for (let i = 0; i < 3; i++) { + const res = JSON.parse(await execAsync(["curl", `${url}&page=${page}`])); + this.#articles.push(...res.results); + page = res.nextPage; + } + + writeFileAsync(this.#cachePath, JSON.stringify(this.#articles)).catch(console.error); + } catch (e) { + console.error(e); + + if (GLib.file_test(this.#cachePath, GLib.FileTest.EXISTS)) + this.#articles = JSON.parse(await readFileAsync(this.#cachePath)); + } + this.notify("articles"); + + this.#loading = false; + this.notify("loading"); + } + + constructor() { + super(); + + this.getNews().catch(console.error); + config.apiKey.subscribe(() => this.getNews().catch(console.error)); + config.countries.subscribe(() => this.getNews().catch(console.error)); + config.categories.subscribe(() => this.getNews().catch(console.error)); + config.languages.subscribe(() => this.getNews().catch(console.error)); + config.domains.subscribe(() => this.getNews().catch(console.error)); + config.timezone.subscribe(() => this.getNews().catch(console.error)); + } +} |