commit 862ad8fbb699f834b52485d5e87b29425560c3f5
parent 76f7d9000b1a593062db7f8ec991303b13d5386b
Author: JayVii <jayvii[AT]posteo[DOT]de>
Date: Mon, 9 Jun 2025 20:18:41 +0200
feat: introduce basic generation of OPML files
Diffstat:
A | index.php | | | 405 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
1 file changed, 405 insertions(+), 0 deletions(-)
diff --git a/index.php b/index.php
@@ -0,0 +1,405 @@
+<!--
+SPDX-License-Identifier: AGPL-3.0-or-later
+SPDX-FileCopyrightText: 2025 JayVii <jayvii+kontra[AT]posteo[DOT]de>
+-->
+
+<!-- Loading configuration -->
+<?php
+ $config = json_decode(
+ file_get_contents("./news-sources.json"),
+ true
+ );
+?>
+
+<!-- OPML Generator -->
+<?php
+
+
+ /* Check for user input */
+ if (!is_null($_POST["action"])) {
+
+ /* Generate OPML */
+ if ($_POST["action"] === "gen_opml") {
+
+ /* Gather news sources from selection */
+ $input_keys = preg_grep(
+ "/^source-[0-9]+$/",
+ array_keys($_POST)
+ );
+ $sources = array();
+ foreach ($input_keys as $key) {
+ array_push(
+ $sources,
+ $config["sources"][$_POST[$key]]
+ );
+ }
+
+ /* Build OPML file */
+ header('Content-Type: application/xml; charset=UTF-8');
+ header('Content-Disposition: Attachment;filename=kontra.opml');
+?>
+<opml version="2.0">
+<head>
+ <title>Nachrichten-Export von Kontra - der linke Newsaggregator</title>
+</head>
+<body>
+<?php
+
+ /* Cycle through each category */
+ foreach ($config["categories"] as $category) {
+
+?>
+
+<outline
+ title="<?php echo $category["name"]; ?>"
+ text="<?php echo $category["name"]; ?>"
+>
+
+<?php
+ /* Within each category, find selected sources that are part of it */
+ foreach ($sources as $source) {
+
+ $match = preg_grep("/^" . $category["id"] . "$/", $source["categories"]);
+ if (count($match) > 0) {
+
+?>
+<outline
+ title="<?php echo $source["title"]; ?>"
+ text="<?php echo $source["title"]; ?>"
+ xmlUrl="<?php echo $source["rss"]; ?>"
+/>
+<?php
+
+ } // if-match
+ } // foreach-source
+
+?>
+
+</outline>
+
+<?php
+
+ } // foreach-category
+
+?>
+
+</body>
+</opml>
+
+<?php
+
+ die();
+ } // if-action
+ } // if-input
+
+?>
+
+<!doctype html>
+<html>
+
+ <!-- Head -->
+ <head>
+ <meta charset=utf-8>
+ <meta name=viewport content="width=device-width,initial-scale=1">
+ <link rel=icon href=assets/img/favicon.png type=image/png>
+ <link rel=icon href=assets/img/favicon.ico type=x-image/ico>
+ <link rel=stylesheet href=assets/css/simple.min.css media=all>
+ <link rel=stylesheet href=assets/css/custom.css>
+ <script async src=assets/js/filter_engines.js></script>
+ <title>Kontra - Der Linke News Aggregator</title>
+ </head>
+
+ <!-- Body -->
+ <body>
+
+ <!-- Header -->
+ <header>
+ <nav>
+ <a href="#sources">
+ Nachrichten
+ </a>
+ <a href="#faqs">
+ Was ist das?
+ </a>
+ <a href="https://src.jayvii.de/pub/kontra/" target="_blank">
+ Hilf mit!
+ </a>
+ </nav>
+ <h1>Kontra</h1>
+ <p>Der Linke News Aggregator</p>
+ </header>
+
+ <!-- Description -->
+ <div class="description">
+ <strong style="font-size: 133%;">
+ <em>Kontra</em> gibt Nutzer:innen die Möglichkeit,
+ unterschiedliche Nachrichtenquellen zu einem persönlichen
+ Nachrichten-Feed zusammen zu stellen. Ganz ohne Tracking,
+ Big-Tech und algorithmischem Framing.
+ </strong>
+ <p>
+ Auf dieser Seite befinden sich viele seriöse Medienquellen aus
+ dem <em>linken Spektrum¹</em> von Zeitungen, Magazinen und
+ sonstigen journalistischen Veröffentlichungen, aus denen sich
+ persönliche Nachrichten-Feeds erstellt werden lassen.
+ </p>
+ <a href="#sources" class="button">
+ Zu den Nachrichten!
+ </a>
+ <a href="#faqs" class="button">
+ Wie funktioniert das?
+ </a>
+ </div>
+
+ <!-- Search Engine Lists -->
+ <h3 id="sources">Eigenen Nachrichten-Feed generieren</h3>
+
+ <!--<p>
+ Aktuell stehen
+ <strong><?php echo count($config["sources"]); ?>
+ Nachrichtenquellen
+ </strong> in
+ <strong><?php echo count($config["categories"]); ?>
+ Kategorien
+ </strong>
+ zur verfügung.
+ </p>-->
+ <p>
+ Wähle beliebig viele Nachrichtenquellen aus der unteren Liste aus
+ und klicke auf den <strong>Nachrichten-Feed erzeugen</strong> Knopf.
+ Speichere die darauf angebotene OPML-Datei ab und importiere sie in
+ deinen RSS-Reader.
+ </p>
+
+ <!-- Filterbar -->
+ <input
+ id="sources_filterbar"
+ name="sources"
+ type="text"
+ placeholder="Suche nach Nachrichtenquellen oder Kategorien..."
+ oninput="filter_sources('sources_filterbar');"
+ >
+ <noscript>
+ <div class="notice">
+ <p>
+ Achtung: Die Nutzung der Suchfunktion benötigt aktiviertes
+ JavaScript. Entweder unterstützt dein Browser diese
+ Funktion nicht, oder du hast sie auf dieser Seite blockiert.
+ <strong>
+ Du kannst dennoch einen Nachrichten-Feed erzeugen, jedoch
+ ohne die Suchfunktion oben dafür nutzen zu können.
+ </strong>
+ </p>
+ </div>
+ </noscript>
+
+ <!-- Category Buttons -->
+ <details id="categories">
+ <summary>Kategorien</summary>
+
+ <div class="row">
+
+ <!-- List all configured categories -->
+ <?php
+ foreach ($config["categories"] as $category) {
+ ?>
+ <button
+ onclick="filter_category('<?php echo $category["name"]; ?>');"
+ title="<?php echo $category["description"]; ?>"
+ >
+ <?php echo "#" . $category["name"]; ?>
+ </button>
+ <?php
+ }
+ ?>
+
+ </div>
+
+ </details>
+
+ <!-- Sources List -->
+ <form id="sources-selection" method="post">
+
+ <!-- Submit button -->
+ <input type="submit" value="Nachrichten-Feed erzeugen!">
+ <input type="hidden" name="action" value="gen_opml">
+ <?php
+ $cnt = 0;
+ foreach ($config["sources"] as $id => $source) {
+ $cnt++; // increase counter for POST field ID
+ ?>
+ <article>
+ <!-- Entry-Head -->
+ <h3>
+ <a href="<?php echo $source["web"]; ?>" target="_blank">
+ <?php echo $source["title"]; ?>
+ </a>
+ </h3>
+
+ <!-- Description -->
+ <p><?php echo $source["desc"]; ?></p>
+
+ <!-- Choose this Button -->
+ <input
+ type="checkbox"
+ name="<?php echo "source-" . $cnt; ?>"
+ value="<?php echo $id; ?>"
+ >
+ <label for="<?php echo $id; ?>">
+ Diese Nachrichtenquelle auswählen
+ </label>
+
+ <!-- Entry-Categories -->
+ <div class="categories">
+
+ <?php
+ /* List all categories of current entry */
+ foreach ($source["categories"] as $category) {
+ $catentry = $config["categories"][$category]
+ ?>
+ <mark title="<?php echo $catentry["description"];?>">
+ <?php echo "#" . $catentry["name"]; ?>
+ </mark>
+ <?php
+ }
+ ?>
+
+ </div>
+
+ </article>
+ <?php
+ }
+ ?>
+ </form>
+
+ <!-- FAQs / About-Section -->
+ <section id="faqs">
+
+ <h3>Was ist <em>Kontra</em>?</h3>
+
+ <!-- meaning of name -->
+ <details>
+ <summary>What does serĉi mean?</summary>
+ <p>
+ It is the Esperanto term for "to search".This may not be the
+ most creative name for such a software project but to the
+ best of my knowledge, it is a unique one.
+ </p>
+ </details>
+
+ <!-- what are keywords -->
+ <details>
+ <summary>What are keywords?</summary>
+ <p>
+ An idea lent from the search Engine
+ <a href="https://duckduckgo.com/bangs" target="_blank">
+ DuckDuckGo
+ </a>, which has been utilized by many others since. They
+ call their keywords bangs, but it is the same concept and
+ usability. In essence, keywords are shortcuts for searching
+ on other websites.
+ </p>
+ <p>
+ For example, you can conduct a search on the website
+ <a
+ href="<?php echo $searches[$rsi]["website"]; ?>"
+ target="_blank"
+ >
+ <?php echo $searches[$rsi]["name"]; ?>
+ </a>
+ directly from <?php echo $_SERVER["SERVER_NAME"]; ?> by
+ adding the keyword
+ <code>!<?php echo $searches[$rsi]["keywords"][0]; ?></code>
+ to your search query.
+ </p>
+ <p>
+ You can find all keywords associated with each search engine
+ in the <a href="#engines">list above</a>.
+ </p>
+ </details>
+
+ <!-- how to use -->
+ <details>
+ <summary>How can I use serĉi?</summary>
+ <p>
+ You can try serĉi directly
+ <a href="#searchbar">on this site</a> or
+ <a
+ href="https://src.jayvii.de/pub/serci/#hosting"
+ target="_blank"
+ >
+ host it yourself
+ </a>!
+ It barely needs any server resources, no database or lots of
+ RAM. serĉi has minimal footprint (also on user data: there
+ are <strong>none</strong>).
+ </p>
+ <p>
+ Either type your search directly into the
+ <a href="#searchbar">search bar at the very top</a>
+ or add the search to your browser by rightclicking in the
+ URL-bar of your web browser and choose
+ <emph>add serĉi - Search with !keywords</emph>.
+ </p>
+ <p>
+ Some browsers (for example Firefox on Android / Fennec)
+ allow to configure custom search engines via a so-called
+ search URL. Simply list following URL as
+ <emph>search-URL</emph> there and choose it as your fallback
+ search option:
+ </p>
+ <pre>https://<?php echo $_SERVER["SERVER_NAME"]; ?>/?q=%s</pre>
+ <p>
+ You can also choose the fallback search engine, used by
+ serĉi as fallback, when no keyword is given, through the URL
+ instead of choosing it from the
+ <a href="#engines">list above</a>, in case you do not want
+ serĉi to store any cookie on your device. For example, if
+ you wanted to use
+ <a
+ href="<?php echo $searches[$rsi]["website"]; ?>"
+ target="_blank"
+ >
+ <?php echo $searches[$rsi]["name"]; ?>
+ </a>
+ as your fallback search, you could use following search-URL:
+ </p>
+ <pre><?php
+ echo "https://" . $_SERVER["SERVER_NAME"] .
+ "/?fallback=" . $rsi . "&q=%s";
+ ?></pre>
+ </details>
+
+ <!-- no js or cookies -->
+ <details>
+ <summary>
+ Choosing a fallback search without cookies or Javascript
+ </summary>
+ <p>
+ You can simply choose a fallback search by adding the
+ <code>fallback</code> URL parameter. For example, if you
+ wanted to use
+ <a
+ href="<?php echo $searches[$rsi]["website"]; ?>"
+ target="_blank"
+ >
+ <?php echo $searches[$rsi]["name"]; ?>
+ </a>
+ as your fallback search, you can simply add
+ <code>/?fallback=<?php echo $rsi; ?></code> to the serĉi
+ URL:
+ <br>
+ <a href="/?fallback=<?php echo $rsi; ?>">
+ <?php
+ echo "https://" . $_SERVER["SERVER_NAME"] .
+ "/?fallback=" . $rsi;
+ ?>
+ </a>
+ </p>
+ </details>
+
+ </section>
+
+ </body>
+
+</html>