summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-04-09 00:04:15 +1000
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-04-09 00:04:15 +1000
commited25a8790c43555d58c3debfcc2ec951fd4516fb (patch)
treecdf4ee5213d61b4324558e4f9018ef1b9933daaa
parentplayers: fix duplicate players (diff)
downloadcaelestia-shell-ed25a8790c43555d58c3debfcc2ec951fd4516fb.tar.gz
caelestia-shell-ed25a8790c43555d58c3debfcc2ec951fd4516fb.tar.bz2
caelestia-shell-ed25a8790c43555d58c3debfcc2ec951fd4516fb.zip
feat: news service
-rw-r--r--src/config/defaults.ts8
-rw-r--r--src/config/index.ts1
-rw-r--r--src/config/types.ts6
-rw-r--r--src/services/news.ts109
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));
+ }
+}