Hexoで複数RSSに対応する

Placeholder of missing images.

Caution

この記事はHexo(2023年4月17日以前)、またはGatsby(2024年4月13日以前)時代の記事だよ❗ 現在のブログとは見た目や機能が異なる可能性があるよ❗

2021/09/12追記

今は違う方法を使ってる。↓を参照。

Hexoで複数RSSに対応する その2

Hexoで複数RSSに対応する方法のその2。

フィギュア記事に興味がある人向けに専用のRSSを用意したい❗

ということで、HexoでRSSフィードを複数用意できるようにした。

  • tagに フィギュア がついてる記事のRSS: https://tenpamk2-blog.netlify.app/atom_figure.xml
  • 2023-04-30追記: Gatsbyでは https://tenpamk2-blog.netlify.app/tags/フィギュア/rss.xml
  • tagに フィギュアレビュー がついてる記事のRSS: https://tenpamk2-blog.netlify.app/atom_figure_review.xml
  • 2023-04-30追記: Gatsbyでは廃止
画面右上のアイコンからもどうぞ

フィギュア全般用とフィギュアレビュー専用の2つを追加した。 お好みのRSSリーダーに突っ込めば、フィギュア記事だけを追えるぞ❗

例によって、hexoの修正手順などは長くなったので、記事は折りたたみ。

方針としては、既存の hexo-generator-feed のスクリプトを丸パクリして、ちょびっとだけ変更を加える。

解説は記事末尾。

手順

前の記事↓とだいたい一緒。scriptを置いて、設定なりをいじる。

Hexoでnative-lazy-loadingに対応する

Hexoでnative lazy-loadingに対応する。

↓のscriptをthemes/***/scripts/add_custom_rss.jsとして設置。 scriptsフォルダに置いたjsファイルはhexoのgenerate時に自動で実行される。

// Original code from ↓
//
// - <https://github.com/hexojs/hexo-generator-feed/blob/master/index.js>
// - <https://github.com/hexojs/hexo-generator-feed/blob/master/lib/generator.js>
const feedFn = require("./add_custom_rss_lib/generator");

hexo.config.feed = Object.assign(
  {
    type: "atom",
    limit: 20,
    hub: "",
    content: true,
    content_limit: 140,
    content_limit_delim: "",
    order_by: "-date",
    autodiscovery: true,
    template: "",
  },
  hexo.config.feed,
);

hexo.config.feed = Object.assign(
  {
    figure_atom_path: "",
    figure_review_atom_path: "",
  },
  hexo.config.add_custom_rss,
);

if (!hexo.config.feed.figure_atom_path) {
  // error
  return;
}

if (!hexo.config.feed.figure_review_atom_path) {
  // error
  return;
}

const filterNonFigure = (post) => {
  const figRevTag = post.tags.toArray().filter((tag) => {
    return tag.name === "フィギュア";
  });

  return 1 <= figRevTag.length;
};

const filterNonFigureReview = (post) => {
  const figRevTag = post.tags.toArray().filter((tag) => {
    return tag.name === "フィギュア" || tag.name === "レビュー";
  });

  return 2 <= figRevTag.length;
};

hexo.extend.generator.register("atom_figure", (locals) => {
  return feedFn.call(
    hexo,
    locals,
    "atom",
    hexo.config.feed.figure_atom_path,
    filterNonFigure,
  );
});

hexo.extend.generator.register("atom_figure_review", (locals) => {
  return feedFn.call(
    hexo,
    locals,
    "atom",
    hexo.config.feed.figure_review_atom_path,
    filterNonFigureReview,
  );
});

追加で、↓のscriptをthemes/***/scripts/add_custom_rss_lib/generator.jsとして設置。

// Original code from ↓
//
// - <https://github.com/hexojs/hexo-generator-feed/blob/master/index.js>
// - <https://github.com/hexojs/hexo-generator-feed/blob/master/lib/generator.js>

"use strict";

const nunjucks = require("nunjucks");
const { join } = require("path");
const { readFileSync } = require("fs");
const { encodeURL, gravatar, full_url_for } = require("hexo-util");

const env = new nunjucks.Environment();

env.addFilter("uriencode", (str) => {
  return encodeURL(str);
});

env.addFilter("noControlChars", (str) => {
  return str.replace(/[\x00-\x1F\x7F]/g, ""); // eslint-disable-line no-control-regex
});

module.exports = function (locals, type, path, filterFunc) {
  const { config } = this;
  const { email, feed, url: urlCfg } = config;
  const {
    icon: iconCfg,
    limit,
    order_by,
    template: templateCfg,
    type: typeCfg,
  } = feed;

  env.addFilter("formatUrl", (str) => {
    return full_url_for.call(this, str);
  });

  let tmplSrc = join(
    __dirname,
    `../../../../node_modules/hexo-generator-feed/atom.xml`,
  );

  if (templateCfg) {
    if (typeof templateCfg === "string") tmplSrc = templateCfg;
    else tmplSrc = templateCfg[typeCfg.indexOf(type)];
  }

  const template = nunjucks.compile(readFileSync(tmplSrc, "utf8"), env);

  let posts = locals.posts.sort(order_by || "-date");

  posts = posts.filter((post) => {
    return post.draft !== true;
  });

  // filter by custom-filter
  posts = posts.filter(filterFunc);

  if (posts.length <= 0) {
    feed.autodiscovery = false;
    return;
  }

  if (limit) posts = posts.limit(limit);

  let url = urlCfg;
  if (url[url.length - 1] !== "/") url += "/";

  let icon = "";
  if (iconCfg) icon = full_url_for.call(this, iconCfg);
  else if (email) icon = gravatar(email);

  const feed_url = full_url_for.call(this, path);

  const data = template.render({
    config,
    url,
    icon,
    posts,
    feed_url,
  });

  return {
    path,
    data,
  };
};

/_config.ymlもいじる。↓の設定を追加。

add_custom_rss:
  figure_atom_path: "atom_figure.xml"
  figure_review_atom_path: "atom_figure_review.xml"

/themes/landscape/_config.ymlもいじる。 ↓を追加。/_config.ymlの記述とズレないように注意。

# Custom RSS
## See `/_config.yml`.
rss_figure: /atom_figure.xml
rss_figure_review: /atom_figure_review.xml

head.ejsを↓のようにする。 theme.rssが既存部分。theme.rss_figuretheme.rss_figure_reviewが追加部分ね。

  <% if (theme.rss){ %>
    <link rel="alternate" href="<%= url_for(theme.rss) %>" title="<%= config.title %>" type="application/atom+xml">
  <% } %>
  <% if (theme.rss_figure){ %>
    <link rel="alternate" href="<%= url_for(theme.rss_figure) %>" title="<%= config.title + ' tag_figure' %>" type="application/atom+xml">
  <% } %>
  <% if (theme.rss_figure_review){ %>
    <link rel="alternate" href="<%= url_for(theme.rss_figure_review) %>" title="<%= config.title + ' tag_figure-review' %>" type="application/atom+xml">
  <% } %>

同じノリでheader.ejsも↓のようにする。

<% if (theme.rss){ %>
  <a id="nav-rss-link" class="nav-icon" href="<%- url_for(theme.rss) %>" title="<%= __('rss_feed') %>"></a>
<% } %>
<% if (theme.rss_figure){ %>
  <a id="nav-rss-figure-link" class="nav-icon" href="<%- url_for(theme.rss_figure) %>" title="<%= __('rss_figure_feed') %>"></a>
<% } %>
<% if (theme.rss_figure_review){ %>
  <a id="nav-rss-figure-review-link" class="nav-icon" href="<%- url_for(theme.rss_figure_review) %>" title="<%= __('rss_figure_review_feed') %>"></a>
<% } %>

header.stylを↓のようにする。

#nav-rss-figure-link
  &:before
    content: "\f143"

#nav-rss-figure-review-link
  &:before
    content: "\f143"

各言語用のキーワード名定義ファイルもいじる。 ja.ymlに↓を追加する。他の言語用のymlファイルはお好みで。

rss_figure_feed: RSSフィード(フィギュア)
rss_figure_review_feed: RSSフィード(フィギュアレビュー)

最後に、clean&generateすればOK。 stylをいじったので、cleanが必須。注意。

解説と蛇足

add_custom_rss.jsgenerator.jsのざっくり設計解説

  • add_custom_rss.jsは本家様と結構変えている
    • 設定ファイルからRSSのファイル名を取り出せるように。
    • フィルタリング用の関数はここで定義
      • 定義だけして、実際のフィルタリングはgenerator.jsで実施
  • generator.jsは本家様とほぼ同じ
    • 好きにフィルタリングできるよう、フィルタリング関数を受け取れるようにした
    • テンプレート用atom.xmlnode_module/hexo-generator-feed/の直下を直接見るように

header.stylのざっくり解説

いじる理由は、RSSフィードへのリンクのアイコンを設定するため。

なお、全記事用RSSとフィギュア用RSSとで区別できるようにした。 "_f09e"が全記事用、"_f143"がフィギュア用。 アイコンの絵面は Awesome Fontの公式ページ で調べられる。

その他

hexo.extend.generator.register("atom_figure" ..."atom_figure"の文字列は好きに決めて良いようだ。 てっきり、特定のキーワードに反応して関数実行するのかと思ったが、違うみたい。