feat: news service

This commit is contained in:
2 * r + 2 * t 2025-04-09 00:04:15 +10:00
parent b43a576615
commit ed25a8790c
4 changed files with 124 additions and 0 deletions

View file

@ -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"
},
};

View file

@ -21,5 +21,6 @@ export const {
wallpapers,
calendar,
thumbnailer,
news,
} = config;
export default config;

View file

@ -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[] };

109
src/services/news.ts Normal file
View file

@ -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));
}
}