RO

Tema

Culoarea de accent

Tema și culoarea te urmează pe satelit.md și în aplicație.

Panou Alege un plan

Blogger automat

În fiecare dimineață, o postare în Markdown cu știrile cele mai grave, cursul BNM și plafonul ANRE. O salvează într-un fișier și, dacă vrei, o trimite pe un canal Telegram.

Cum funcționează

  1. Ia știrile din ultimele 24 de ore cu gravitate de cel puțin 50.
  2. Ia cursul și plafonul zilei.
  3. Scrie postarea, cu linkuri spre originale și cu rândul de atribuire primit în meta.attribution.
  4. O salvează în posts/ și o trimite pe Telegram, dacă ai pus tokenul.

Cât costă: 2 credite pentru știri, 1 pentru curs, 1 pentru carburanți: 4 credite pe zi, 120 pe lună. Încape în planul Start. Pe orice plan știrile vin fără întârziere și cu rezumat.

Cum îl rulezi

bash
export SATELIT_KEY=sat_live_...
POST_LANG=ro MIN_GRAVITY=50 node blogger.mjs

Ce scoate

stdout
posts/2026-11-02-ro.md: 4 stories, 4 credits

Scriptul complet

blogger.mjs
#!/usr/bin/env node
// Automated blogger: one Markdown post a day from the Satelit API.
//   SATELIT_KEY=sat_live_... node blogger.mjs
// Optional: TELEGRAM_TOKEN and TELEGRAM_CHAT send the post to a channel.
// No dependencies, Node 18 or newer. Put it in cron: 0 8 * * * node /path/blogger.mjs
import { mkdirSync, writeFileSync } from "node:fs";

const BASE = process.env.SATELIT_BASE ?? "https://api.satelit.md/v1";
const KEY = process.env.SATELIT_KEY;
const LANG = process.env.POST_LANG ?? "ro";
const MIN_GRAVITY = Number(process.env.MIN_GRAVITY ?? 50);
const OUT_DIR = process.env.OUT_DIR ?? "posts";
if (!KEY) throw new Error("Set SATELIT_KEY");

const WORDS = {
  ro: { title: "Ziua în Moldova", news: "Știrile zilei", money: "Curs și carburanți", cap: "Plafon A-95", gravity: "gravitate" },
  ru: { title: "День в Молдове", news: "Новости дня", money: "Курс и топливо", cap: "Предел A-95", gravity: "серьёзность" },
  en: { title: "The day in Moldova", news: "Today's news", money: "Rates and fuel", cap: "A-95 cap", gravity: "gravity" },
  uk: { title: "День у Молдові", news: "Новини дня", money: "Курс і пальне", cap: "Межа A-95", gravity: "серйозність" },
};
const w = WORDS[LANG];

async function api(path, params = {}) {
  const url = new URL(BASE + path);
  for (const [k, v] of Object.entries({ lang: LANG, ...params })) url.searchParams.set(k, v);
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(url, { headers: { Authorization: `Bearer ${KEY}` } });
    const retry = Number(res.headers.get("retry-after"));
    if ((res.status === 429 || res.status === 503) && retry && attempt < 2) {
      await new Promise((ok) => setTimeout(ok, retry * 1000));
      continue;
    }
    const body = await res.json();
    if (!res.ok) throw new Error(`${path}: ${body.error.code}: ${body.error.message}`);
    return body;
  }
}

const today = new Date().toISOString().slice(0, 10);
const since = new Date(Date.now() - 86400e3).toISOString().slice(0, 10);

const [news, rates, fuel] = await Promise.all([
  api("/news", { min_gravity: MIN_GRAVITY, from: since, per: 10 }),
  api("/rates"),
  api("/fuel"),
]);

const lines = [`# ${w.title}: ${today}`, "", `## ${w.news}`, ""];
for (const n of [...news.data].sort((a, b) => b.gravity - a.gravity)) {
  const link = n.sources[0]?.url;
  lines.push(`- **${n.title}** (${w.gravity} ${n.gravity})${link ? ` [${n.sources[0].name}](${link})` : ""}`);
  if (n.summary ?? n.dek) lines.push(`  ${n.summary ?? n.dek}`);
}

const eur = rates.data.find((r) => r.code === "EUR");
const usd = rates.data.find((r) => r.code === "USD");
lines.push("", `## ${w.money}`, "", `- EUR: ${eur.value} (${eur.change_7d_pct}%, 7d)`, `- USD: ${usd.value} (${usd.change_7d_pct}%, 7d)`, `- ${w.cap}: ${fuel.data.cap.a95} lei/l`);

// The plan's attribution line, when it asks for one
const attribution = news.meta.attribution ?? rates.meta.attribution;
if (attribution) lines.push("", `*${attribution}*`);

const post = lines.join("\n") + "\n";
mkdirSync(OUT_DIR, { recursive: true });
const file = `${OUT_DIR}/${today}-${LANG}.md`;
writeFileSync(file, post);

if (process.env.TELEGRAM_TOKEN && process.env.TELEGRAM_CHAT) {
  const res = await fetch(`https://api.telegram.org/bot${process.env.TELEGRAM_TOKEN}/sendMessage`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ chat_id: process.env.TELEGRAM_CHAT, text: post.slice(0, 4000) }),
  });
  if (!res.ok) throw new Error(`Telegram: ${res.status}`);
}

const credits = news.meta.credits + rates.meta.credits + fuel.meta.credits;
console.log(`${file}: ${news.data.length} stories, ${credits} credits`);