各言語でRSSフィード
守矢諏訪子 <suwako@076.moe>、2026-11-11 11:23
コミット 8d4ccf4456963450c1da7bb83d892538ca1ec048
・親 14ae23c
・ファイルを見る
9個のファイル、+75 -19
変更
README-JP.md
| @@ -120,7 +120,7 @@ | ||
| 120 | 120 | location "/.well-known/webfinger" { |
| 121 | 121 | request rewrite "/index.php" |
| 122 | 122 | } |
| 123 | - location "/blog.atom" { | |
| 123 | + location match "*.atom" { | |
| 124 | 124 | request rewrite "/index.php" |
| 125 | 125 | } |
| 126 | 126 | } |
| @@ -189,6 +189,10 @@ | ||
| 189 | 189 | forward to <httpd> port 8080 check tcp |
| 190 | 190 | } |
| 191 | 191 | ``` |
| 192 | + | |
| 193 | +### ブログと Atom フィード | |
| 194 | +1つのサイトに複数のブログを置けます。`config/config.php` の `BLOGS` の各ディレクトリ(例:`blog`、`enblog`)がブログになり、それぞれ `/ディレクトリ.atom` の Atom フィードがあります(一覧と検索結果のページの上にリンクがあります)。\ | |
| 195 | +`BLOGS` が無ければ、今まで通り `blog` だけです。 | |
| 192 | 196 | |
| 193 | 197 | ### Cron |
| 194 | 198 | 定期的なタスク(例えば、新しいブログ投稿を ActivityPub のフォロワーに送る `apfederate`)はシステムの cron で実行します。\ |
変更
README.md
| @@ -118,7 +118,7 @@ | ||
| 118 | 118 | location "/.well-known/webfinger" { |
| 119 | 119 | request rewrite "/index.php" |
| 120 | 120 | } |
| 121 | - location "/blog.atom" { | |
| 121 | + location match "*.atom" { | |
| 122 | 122 | request rewrite "/index.php" |
| 123 | 123 | } |
| 124 | 124 | } |
| @@ -187,6 +187,10 @@ | ||
| 187 | 187 | forward to <httpd> port 8080 check tcp |
| 188 | 188 | } |
| 189 | 189 | ``` |
| 190 | + | |
| 191 | +### Blogs and Atom feeds | |
| 192 | +A site can have more than one blog: every directory in `BLOGS` in `config/config.php` (for example `blog`, `enblog`) is a blog with its own Atom feed at `/directory.atom`, linked from the top of its index and search result pages.\ | |
| 193 | +Without `BLOGS`, only `blog` exists, as before. | |
| 190 | 194 | |
| 191 | 195 | ### Cron |
| 192 | 196 | Scheduled tasks (for example `apfederate`, which sends new blog posts to ActivityPub followers) are run by the system cron.\ |
| @@ -43,6 +43,11 @@ | ||
| 43 | 43 | 'tags' => 'game,tech,developer', |
| 44 | 44 | 'url' => '', |
| 45 | 45 | ]); |
| 46 | +// ブログ(URLのディレクトリ => 設定)。ブログ毎に「/ディレクトリ.atom」のフィードが出来る | |
| 47 | +// title:フィードの名前(省略するとSITEINFOのtitle) | |
| 48 | +define('BLOGS', [ | |
| 49 | + 'blog' => [], | |
| 50 | +]); | |
| 46 | 51 | define('MAILINFO', [ |
| 47 | 52 | 'from' => 'hogehoge@ho.ge', |
| 48 | 53 | 'host' => 'smtp.ho.ge', |
| @@ -115,3 +115,14 @@ | ||
| 115 | 115 | text-align: center; |
| 116 | 116 | } |
| 117 | 117 | } |
| 118 | + | |
| 119 | +.feed-link { | |
| 120 | + text-align: right; | |
| 121 | + margin: 0 0 10px; | |
| 122 | +} | |
| 123 | + | |
| 124 | +.feed-link a { | |
| 125 | + display: inline-flex; | |
| 126 | + align-items: center; | |
| 127 | + gap: 6px; | |
| 128 | +} |
変更
route.php
| @@ -131,7 +131,9 @@ | ||
| 131 | 131 | } |
| 132 | 132 | |
| 133 | 133 | if (ATOM_ENABLED) { |
| 134 | - $routes[] = Route::add('GET', 'blog.atom', Atom::class.'@feed'); | |
| 134 | + foreach (array_keys(blogs()) as $blog) { | |
| 135 | + $routes[] = Route::add('GET', $blog.'.atom', Atom::class.'@feed', ['section' => $blog]); | |
| 136 | + } | |
| 135 | 137 | } |
| 136 | 138 | |
| 137 | 139 | if (OPENPROVIDER_ENABLED) { |
| @@ -65,40 +65,51 @@ | ||
| 65 | 65 | */ |
| 66 | 66 | public function feed(array $params): void { |
| 67 | 67 | try { |
| 68 | + $section = (string)($params['section'] ?? 'blog'); | |
| 69 | + $blogs = blogs(); | |
| 70 | + if (!isset($blogs[$section])) { | |
| 71 | + header('HTTP/1.1 404 Not Found'); | |
| 72 | + exit; | |
| 73 | + } | |
| 74 | + | |
| 68 | 75 | // 最新の投稿を取得 |
| 69 | - $posts = $this->getPosts('/blog/', null); | |
| 76 | + $posts = $this->getPosts("/{$section}/", null); | |
| 77 | + | |
| 70 | 78 | // 最新の5件に制限 |
| 71 | 79 | $posts = array_slice($this->publicOnly($posts), 0, 5); |
| 72 | - | |
| 80 | + | |
| 73 | 81 | // サイトのドメインを取得 |
| 74 | 82 | $domain = $_SERVER['HTTP_HOST']; |
| 75 | 83 | $baseUrl = 'https://'.$domain; |
| 76 | - | |
| 84 | + | |
| 85 | + // 「blog」のフィードのIDとリンクは今まで通り(IDが変わると購読者のリーダーで全部新しい記事になる) | |
| 86 | + $home = $section === 'blog' ? $baseUrl : "{$baseUrl}/{$section}"; | |
| 87 | + | |
| 77 | 88 | // 現在の日時(RFC3339形式) |
| 78 | 89 | $published = date('c'); |
| 79 | - | |
| 90 | + | |
| 80 | 91 | // XMLヘッダーとコンテンツタイプを設定 |
| 81 | 92 | header('Content-Type: application/atom+xml; charset=utf-8'); |
| 82 | - | |
| 93 | + | |
| 83 | 94 | // Atomフィードの開始部分 |
| 84 | 95 | echo '<?xml version="1.0" encoding="utf-8"?>'."\n"; |
| 85 | 96 | echo '<feed xmlns="http://www.w3.org/2005/Atom">'."\n"; |
| 86 | - | |
| 97 | + | |
| 87 | 98 | // フィードの基本情報 |
| 88 | - echo ' <title>'.SITEINFO['title'].'</title>'."\n"; | |
| 89 | - echo ' <link href="'.$baseUrl.'" />'."\n"; | |
| 90 | - echo ' <link href="'.$baseUrl.'/blog.atom" rel="self" />'."\n"; | |
| 91 | - echo ' <id>'.$baseUrl.'/</id>'."\n"; | |
| 99 | + echo ' <title>'.htmlspecialchars($blogs[$section]['title'], ENT_XML1, 'UTF-8').'</title>'."\n"; | |
| 100 | + echo ' <link href="'.$home.'" />'."\n"; | |
| 101 | + echo ' <link href="'.$baseUrl.'/'.$section.'.atom" rel="self" />'."\n"; | |
| 102 | + echo ' <id>'.$home.'/</id>'."\n"; | |
| 92 | 103 | echo ' <published>'.$published.'</published>'."\n"; |
| 93 | 104 | echo ' <updated>'.$published.'</updated>'."\n"; |
| 94 | 105 | echo ' <author>'."\n"; |
| 95 | 106 | echo ' <name>'.SITEINFO['title'].'</name>'."\n"; |
| 96 | 107 | echo ' </author>'."\n"; |
| 97 | - | |
| 108 | + | |
| 98 | 109 | // 各エントリー(記事) |
| 99 | 110 | foreach ($posts as $post) { |
| 100 | 111 | // 記事の本文を取得(プレーンテキスト) |
| 101 | - $path = ROOT.'/blog/'.$post['slug'].'.md'; | |
| 112 | + $path = ROOT."/{$section}/".$post['slug'].'.md'; | |
| 102 | 113 | $content = ''; |
| 103 | 114 | $postPublished = date('c', strtotime($post['date'])); |
| 104 | 115 | |
| @@ -107,7 +118,7 @@ | ||
| 107 | 118 | $parts = explode('----', $fileContent, 2); |
| 108 | 119 | if (count($parts) > 1) { |
| 109 | 120 | // 本文をHTMLとして準備 |
| 110 | - $md = new Markdown($post['slug'], '/blog/'); | |
| 121 | + $md = new Markdown($post['slug'], "/{$section}/"); | |
| 111 | 122 | $content = $md->parse(); |
| 112 | 123 | // HTMLタグを取り除かないようにCDATAで囲む |
| 113 | 124 | $content = '<![CDATA['.$content.']]>'; |
| @@ -116,8 +127,8 @@ | ||
| 116 | 127 | |
| 117 | 128 | echo ' <entry>'."\n"; |
| 118 | 129 | echo ' <title>'.htmlspecialchars($post['title']).'</title>'."\n"; |
| 119 | - echo ' <link href="'.$baseUrl.'/blog/'.$post['slug'].'" />'."\n"; | |
| 120 | - echo ' <id>'.$baseUrl.'/blog/'.$post['slug'].'</id>'."\n"; | |
| 130 | + echo ' <link href="'.$baseUrl.'/'.$section.'/'.$post['slug'].'" />'."\n"; | |
| 131 | + echo ' <id>'.$baseUrl.'/'.$section.'/'.$post['slug'].'</id>'."\n"; | |
| 121 | 132 | echo ' <published>'.$postPublished.'</published>'."\n"; |
| 122 | 133 | |
| 123 | 134 | // カテゴリ(タグ) |
変更
util.php
| @@ -369,6 +369,17 @@ | ||
| 369 | 369 | return false; |
| 370 | 370 | } |
| 371 | 371 | |
| 372 | +/** | |
| 373 | + * ブログの一覧(config.phpのBLOGS、無ければ「blog」だけ) | |
| 374 | + * | |
| 375 | + * @return array ディレクトリ名 => ['title' => フィードの名前] | |
| 376 | + */ | |
| 377 | +function blogs(): array { | |
| 378 | + $blogs = defined('BLOGS') ? BLOGS : ['blog' => []]; | |
| 379 | + foreach ($blogs as $name => $b) $blogs[$name] = $b + ['title' => SITEINFO['title']]; | |
| 380 | + return $blogs; | |
| 381 | +} | |
| 382 | + | |
| 372 | 383 | function getcookie(string $name): string|null { |
| 373 | 384 | return $_COOKIE[$name] ?? null; |
| 374 | 385 | } |
| @@ -50,7 +50,8 @@ | ||
| 50 | 50 | {@ endif @} |
| 51 | 51 | {@ endif @} |
| 52 | 52 | {@ if (ATOM_ENABLED) @} |
| 53 | - <link rel="alternate" type="application/atom+xml" title="{{ SITEINFO['title'] }} feed" href="/blog.atom" /> | |
| 53 | +{$ $feed = isset($section) && array_key_exists($section, blogs()) ? $section : array_key_first(blogs()) $} | |
| 54 | + <link rel="alternate" type="application/atom+xml" title="{{ blogs()[$feed]['title'] }} feed" href="/{{ $feed }}.atom" /> | |
| 54 | 55 | {@ endif @} |
| 55 | 56 | </head> |
| 56 | 57 | <body> |
| @@ -1,5 +1,12 @@ | ||
| 1 | 1 | {@ include(common/header) @} |
| 2 | 2 | {@ include(common/search) @} |
| 3 | +{@ if (ATOM_ENABLED && array_key_exists($section, blogs())) @} | |
| 4 | + <div class="feed-link"> | |
| 5 | + <a href="/{{ $section }}.atom" title="{{ blogs()[$section]['title'] }}"> | |
| 6 | + <svg width="16" height="16" viewBox="0 0 16 16" aria-hidden="true"><circle cx="2.5" cy="13.5" r="2" fill="currentColor" /><path d="M0.5 6.5a9 9 0 0 1 9 9h-2.5a6.5 6.5 0 0 0-6.5-6.5zM0.5 1a14.5 14.5 0 0 1 14.5 14.5h-2.5a12 12 0 0 0-12-12z" fill="currentColor" /></svg> フィード | |
| 7 | + </a> | |
| 8 | + </div> | |
| 9 | +{@ endif @} | |
| 3 | 10 | |
| 4 | 11 | <div class="news-grid"> |
| 5 | 12 | {@ foreach ($posts as $post) @} |