風柳メモ

ソフトウェア・プログラミング関連の覚書が中心

はてなブログの記事に更新日付を表示&古い記事に警告を出す試み

はてなブログが配信しているサイトマップの仕様が変わっていることに、今更ながら気が付きました
変化したのが2020/04/01からなので、何ヶ月気が付かなかったのか、という話ですが。
sitemap.xml の中身を覗いてみたら、クエリで年月を指定することにより、指定月に作成した記事の更新日付を取得できるようです。

これを利用して、フッタなどに貼り付けることで

  • 個別記事に更新日付を表示
  • 更新日付が古い場合には、警告を表示

するようなスクリプトを試作してみました。

f:id:furyu-tei:20200818053148p:plain
はてなブログの記事に更新日付を入れてみる&古い記事には警告を表示する



更新日付の取得方法について

はてなブログの記事には作成日付は最初から入っており、例えば

var entry_date = new Date( document.querySelector( 'time[datetime][pubdate].updated' ).getAttribute( 'datetime' ) );

のようにすることで、簡単に取得することができます。
これを用いて記事が古いかどうかを判断して表示するスクリプトは以前から存在します。
sprint-life.hatenablog.com

しかし、実際には記事は更新されることもよくあり、その記事の情報が古いかどうかは更新日付を元に判断したいところです。

2020/04/01より前のサイトマップ仕様

2020/03/31まで、サイトマップの仕様は

/sitemap.xml?page=<ページ番号>

のような形式で、1ページ毎に作成日付の新しいものから順に100件ずつ、記事のURLと更新日付が含まれる仕様になっていました。
なお、当概仕様のサイトマップには現在でもアクセス可能です。
これだと、特定記事の更新日付を取得するためには、当該記事のURLが現れるまでひたすらページ番号を増やしてアクセスする必要があります。
上記の記事やコメント中でも、サイトマップから更新日を取得することは可能ではあるものの、記事と一致させることが手間なので実用的ではない旨書かれています。

このサイトマップを使った更新日付表示の実装例は
www.tsubasa-note.blog

等があるようです。
また、サイトマップの場合には何度もアクセスが発生してしまう問題を解消するため、AMP(更新日付が埋め込まれている)を利用した実装もあるようです。
psn.hatenablog.jp

2020/04/01 以降のサイトマップ仕様

2020/04/01から配信されるようになったサイトマップの仕様では、

/sitemap_periodical.xml?year=<作成年>&month=<作成月>

のようにすると、

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url>
        <loc>https://memo.furyutei.work/entry/20161015/1476495230</loc>
        <lastmod>2016-12-06T09:08:18+09:00</lastmod>
    </url>
    <url>
        <loc>https://memo.furyutei.work/entry/20161008/1475863694</loc>
        <lastmod>2016-12-06T09:08:56+09:00</lastmod>
    </url>
    <url>
        <loc>https://memo.furyutei.work/entry/20161008/1475860031</loc>
        <lastmod>2017-11-14T07:08:24+09:00</lastmod>
    </url>
    <url>
        <loc>https://memo.furyutei.work/entry/20161007/1475839249</loc>
        <lastmod>2020-03-07T03:29:59+09:00</lastmod>
    </url>
</urlset>

のように、指定した年月に作成した記事のURLと更新日の一覧を取得することが出来るようになっていました。
これならアクセスが1回だけで済むので、実用的だと思われます。

個別記事に更新日付を表示&古い記事に警告を表示するスクリプトとCSSのサンプル

注意事項
  • スクリプトをブログのフッタなどに貼り付ける場合、<script>~</script>で囲むのを忘れないこと
  • サンプルソースコードはGist上にあります。なお、残念ながら Gist 上のコードは外部スクリプトとしては読み込めません(Content-Type: text/plain; charset=utf-8 のため)
  • async/await とか使っているので、モダンブラウザじゃないと動きません(笑)
    それじゃ嫌だというかたはこちらの記事等を参考にして、新しいサイトマップ仕様に対応させてみてください

余談

記事の更新時刻が例えば

2017-11-14T07:08:24+09:00

という文字列で与えられたとき、閲覧者のタイムゾーンによらずに日本標準時の年月日で

2017-11-14

という文字列に変換するためにはどうするのがスマートなんですかね……?
いや、上記サンプル中ではT以降の文字を消す、という手抜き実装ですが……

また、同様に

2017-11-13T22:08:24Z

で与えられた場合には?

var modified_date = new Date( '2017-11-13T22:08:24Z' );
modified_date.setHours( modified_date.getHours()+9 );
var jst_date_string = modified_date.toISOString().replace( /T.*$/, '' );

のようにすれば一応できるけれど、正攻法ではないですね……。

Git for WindowsのGit BASHでウィンドウタイトルだけが文字化けする現象(winptyが原因)と対処方法

[2020.10.27追記] Git for Windows 2.29.1-64-bit(mintty 3.4.0 / winpty 0.4.3 の組み合わせ)では、exec winpty bash した後も Git Bash のウィンドウタイトルが文字化けしなくなった模様。以下は古いバージョンについての記述であり、新バージョンでは環境変数 PS1 を書き換えるとかえって文字化けしてしまうので、注意。



Git BASHで、たまたま日本語を含むフォルダ下で作業していたら、ウィンドウのタイトルだけが文字化けしていることに気がついた(ウィンドウ内については正常に表示されている)。
Windows 10 Pro バージョン 1909 (OS ビルド 18363.959)、Git for Windows 2.28.0-64-bit、mintty 3.2.0 (x86_64-pc-msys) [Windows 18363]

f:id:furyu-tei:20200812030339p:plain
Git BASHでウィンドウタイトルだけが文字化けしている様子

この現象の原因と、対処方法についての覚え書きを残しておく。




原因

どうやら、~/.bash_profile にて

exec winpty bash

を記述していたためらしい。

試しにこの記述を外すと、ウィンドウタイトルは化けなくなる。
ただし当然ながら、対話型コマンドがそのままではうまく動作しなくなる。

f:id:furyu-tei:20200812040929p:plain
exec winpy bash の記述を外した場合


Python なんかの Windows ネイティブな対話型コマンドを呼び出すのに、いちいち

$ winpty python

としたり、あるいは ~/.bashrc で対話型コマンド毎に

alias python="winpty python.exe"

のように alias 定義するのが面倒だったので bash 自体を winpty で呼び出していたのだけれど、これが原因でウィンドウタイトルが化けてしまうのは想定外……。

対処方法

A. 必要なコマンド毎に個別に winpty を付けて呼び出す alias を定義しておく

bash そのものを winpty 経由で呼び出しているのが原因なのでこれを止め、個別のコマンド毎に winpty 付き呼び出しを行うようにすれば問題はなくなる。
その場合、標準では /etc/profile.d/aliases.sh にて

    for name in node ipython php php5 psql python2.7
    do
        case "$(type -p "$name".exe 2>/dev/null)" in
        ''|/usr/bin/*) continue;;
        esac
        alias $name="winpty $name.exe"
    done

のように、いくつかのコマンドについて対応されているのを参考にして ~/.bashrc に必要なコマンドについて同様の記述を追記するというのが一つの方法。

B. 環境変数 PS1 を書き換える

A. のようにすると、対話型コマンドの種類が増えるごとにメンテナンスしなければならないため、面倒なのが難点。
そこで、bash を winpty で呼び出したときにウィンドウタイトルのみが化けている点に着目し、プロンプト定義用の環境変数(PS1)を書き換えてやれば良いのではないか? と考えた。
状況的に、bash を winpty 経由にした際には、mintty はウィンドウタイトル文字列としては Shift-JIS(CP932) で渡されることを期待しているのではないかと推測、$PS1 中に含まれる $PWD の文字コードを変換するように定義してみると、

export PS1=`echo "$PS1" | sed "s~\\$PWD~\\\`echo \\$PWD | iconv -f utf-8 -t cp932 2>/dev/null\\\`~"`

これでうまくいった。
ウィンドウタイトルに関してだけはなんとかなったということで、winpty bash したときに出る潜在的な問題は他にもあるかも知れない。

f:id:furyu-tei:20200812042525p:plain
ウィンドウタイトルも文字化けせず、対話型コマンドも正常に起動できている

覚え書きを兼ねて、現状の ~/.bash_profile および ~/.bashrc を残しておく。

~/.bash_profile

#【覚書】
# ※参考:[bashの.profileや.bashrc等を実行する動作仕様 - sgryjp.log](https://blog.sgry.jp/entry/2019/11/09/232927)
#===============================================================================
# ・ ~/.bash_profile はログインシェルで一度だけ実行される
#   このため、主に
#
#   - ログイン時に一度だけ実行したい処理
#   - 対話操作では必要のない(対話操作以外のコマンド実行に必要な)設定(環境変数 LANG 定義等)
#   - ログインシェル/サブシェルに関わらず基本変更不要な設定(環境変数 PATH 定義等)
#
#   を記述するのに向く
#
# ・ ~/.bashrc はサブシェルでは自動的に呼ばれる(ログインシェルでは自動では呼ばれない)
#   このため、~/.bash_profile からも呼び出されるよう設定の上で (^1)
#
#   - alias や関数等、暗黙的に引き継がれない設定
#   - 対話操作のみで必要となる設定(環境変数 PS1、EDITOR 等)
#
#   を記述するのに向く
#
#   (^1) ~/.bashrc があるのに ~/.bash_profile, ~/.bash_login or ~/.profile が無い環境は推奨されず、
#        例えば Git for Windows においては、その場合、/etc/profile.d/bash_profile.sh により
#        ~/.bashrc を呼び出す記述が入った ~/.bash_profile が生成される
#===============================================================================

# ■ ~/.bashrc 実行
#   ※ログインシェルでは ~/.bashrc は自動的には実行されないため、この設定が必要
#test -f ~/.profile && . ~/.profile
test -f ~/.bashrc && . ~/.bashrc

# ■ 各種定義
export PYTHONUTF8=1
export PYTHONSTARTUP=~/.pythonstartup

# ■ bash を winpty 経由で起動
#   ※ Windows ネイティブな対話型コマンド(Python 等の、interactive mode が mintty に対応していないもの)に対応
exec winpty bash
#   ※ exec によりシェルプロセス自体を置換しているため、これ以降に書いた内容は反映されないことに注意

~/.bashrc

if [[ $TERM_PROGRAM = "mintty" ]]; then
    # ■ mintty 専用設定
    case `tty` in
        /dev/cons*) # winpty
            # winpty 経由だと $PWD が日本語を含む場合ウィンドウタイトルが文字化けする(mintty 3.2.0)問題に対処
            # → mintty 3.4.0 / winpty 0.4.3 の組み合わせでは発生しなくなったため、必要がなくなった
            # export PS1=`echo "$PS1" | sed "s~\\$PWD~\\\`echo \\$PWD | iconv -f utf-8 -t cp932 2>/dev/null\\\`~"`
            ;;
        /dev/pty*) # (default)
            ;;
    esac
fi


# ■ 暗黙的にサブシェルに引き継がれない設定(alias・関数定義)
# ※サブシェルでは、
#   - profile系スクリプト(/etc/profile、/etc/profile.d/*、~/.bash_profile等)は実行されない
#   - ログインシェルで定義された alias や関数は引き継がれない
#   ことに注意
shopt -q login_shell || {
    # サブシェル専用設定
    # ※必要に応じて /etc/profile や /etc/profile.d/* (aliases.sh等)で定義されている alias 等をコピーしておく
    alias ls='ls -F --color=auto --show-control-chars'
    alias ll='ls -l'
}

# ~/.bash_profile 経由/サブシェル共用設定
alias ls='ls -aF --color=auto --show-control-chars'
alias tree='tree -N'
alias python2='py -2'
alias python3='py -3'
alias python='python3'
alias pip2='python2 -m pip'
alias pip3='python3 -m pip'
alias pip='pip3'


# ■ 非対話ログインで不要かつ対話ログインで使いたい設定(環境変数定義)
export EDITOR=vim

その他の覚え書き等

ユーザー独自のファイル(bashスクリプト)をログインシェル/サブシェルどちらでも呼び出す(Git BASH 独自仕様?)

/etc/profile.d/git-prompt.sh
の最後の方に、以下のような記述があることに気づいた。

# Evaluate all user-specific Bash completion scripts (if any)
if test -z "$WINELOADERNOEXEC"
then
        for c in "$HOME"/bash_completion.d/*.bash
        do
                # Handle absence of any scripts (or the folder) gracefully
                test ! -f "$c" ||
                . "$c"
        done
fi
  • /etc/profile.d 下のファイルは通常はログインシェルからしか呼ばれないものだが、git-prompt.sh に限り、サブシェルからも呼び出される(/etc/bash.bashrc 経由)
  • ~/bash_completion.d/ というディレクトリを作成し、この下に拡張子 .bash の付いた任意の bash スクリプトを置いておくと、/etc/profile.d/git-prompt.sh 経由で呼び出される(結果的にはログインシェル/サブシェルの区別なく呼ばれることになる)

参考

winpty・mintty 関連

mseeeen.msen.jp

bash 関連ファイル(~/.bash_profile・~/.bashrc 等)の動作仕様について

blog.sgry.jp
なんとなくで設定しがちだった bash の ~/.bash_profile や ~/.bashrc といったファイルの動作仕様が分かりやすくまとまっていて非常に参考になった。

Google ドライブの共用 PDF を画像データとしてダウンロードするユーザースクリプトを試作

Google ドライブで共用されている PDF ファイルを、画像データにして ZIP 化し、ダウンロードできるユーザースクリプトを作ってみました。
通常は PDF をそのままダウンロードすればよいのですが、まれにダウンロード権限が「閲覧者はダウンロードできません」となっている場合があるため、主にその対策用です。
画像として取得しても、PDF 化したり OCR にかけたりと結局は別途手間はかかってしまいますが……まぁ、手動で1ページずつ落としたりするよりは幾分マシかなと。

インストール・使い方など

github.com
をご参照下さい。

ユーザースクリプトのソースコード


はてなブログでは Gist 貼り付けしかできないみたいなので、gist-it.appspot.com - Embed files from a github repository like a gist を使って GitHub 上のソースコードを貼り付けてみました

chrome-extension-kickstart-typescript が Node.js v12 だとうまく動かない件とその対策

■ 追記
2020年02月11日にid:SWIMATH2さんが対応して下さりgenerator-chrome-extension-kickstart-typescript v4.4.1 以降では、Node.js 12 でも元の手順のままで正常動作するようになった模様です。
以下は古い情報となります。


勉強を兼ねてブラウザ拡張機能をTypeScriptで書いてみようと、swimath2.hatenablog.comgenerator-chrome-extension-kickstart-typescript を試してみたところ、Node.js v12 ではそのままでは動作しませんでした。
試行錯誤の末にとりあえず動くようになったので、覚え書きとして手順等を書いておきます。




対策(お急ぎの方はこちらだけお読みください)

試行環境
# Node.js v12.14.1 LTS および Yarn 1.21.1 は、それぞれインストーラをダウンロードしてインストール済
$ node -v && npm -v
v12.14.1
6.13.4

$ yarn -v
1.21.1

# yo および generator-chrome-extension-kickstart-typescript を npm もしくは yarn にて global インストール
#$ npm install -g yo generator-chrome-extension-kickstart-typescript
$ yarn global add yo generator-chrome-extension-kickstart-typescript

$ yo --version
3.1.1
手順

1. 拡張機能用のプロジェクト作成
拡張機能用のフォルダを作り(例では sample)chrome-extension-kickstart-typescript を --skip-install 付きで実行

$ mkdir -p sample
$ cd sample
# --skip-install を付けて、npm による自動インストールを抑制
$ yo chrome-extension-kickstart-typescript --skip-install

2. package.json 編集

$ vi ./package.json

※差分例

--- ./package.json.orig 2020-01-11 14:33:14.473509100 +0900
+++ ./package.json      2020-01-11 14:44:36.316563000 +0900
@@ -32,7 +32,7 @@
     "chromereload": "0.x.x",
     "debounce": "1.x.x",
     "del": "3.x.x",
-    "gulp": "3.x.x",
+    "gulp": "3.9.0",
     "gulp-bump": "2.x.x",
     "gulp-cache": "0.x.x",
     "gulp-clean-css": "^3.x.x",
@@ -57,6 +57,10 @@
     "vinyl-named": "1.x.x",
     "webpack": "3.x.x",
     "webpack-stream": "3.x.x",
-    "yargs": "^8.x.x"
+    "yargs": "^8.x.x",
+    "typescript": "^3.x.x"
+  },
+  "resolutions": {
+    "gulp/**/graceful-fs": "^4.x.x"
   }
 }

3. パッケージのインストール
yarn install で必要な全パッケージをインストール

$ yarn install

4. 動作確認
動作するかどうかを yarn run で確認

$ yarn run dev:chrome

※ 正常動作したときの例

yarn run v1.21.1
$ gulp --watch --vendor=chrome
[14:56:00] Requiring external module babel-core/register
[14:56:07] Using gulpfile D:\webext\sample\gulpfile.babel.js
[14:56:07] Starting 'build'...
[14:56:07] Starting 'clean'...
[14:56:07] Finished 'clean' after 3.37 ms
[14:56:07] Starting 'manifest'...
[14:56:07] Starting 'scripts'...
[14:56:07] Starting 'styles:css'...
[14:56:07] Starting 'styles:less'...
[14:56:07] Starting 'styles:sass'...
[14:56:07] Starting 'pages'...
[14:56:07] Starting 'locales'...
[14:56:07] Starting 'images'...
[14:56:07] Starting 'fonts'...
[14:56:07] Starting 'chromereload'...
[14:56:07] Starting 'livereload-server'
[14:56:07] Finished 'styles:css' after 32 ms
[14:56:07] Finished 'styles:less' after 26 ms
[14:56:07] Finished 'styles:sass' after 26 ms
[14:56:07] Starting 'styles'...
[14:56:07] Finished 'styles' after 20 μs
[14:56:07] Finished 'pages' after 27 ms
[14:56:07] Finished 'fonts' after 26 ms
[14:56:07] Finished 'manifest' after 264 ms
[14:56:07] Finished 'locales' after 237 ms
[14:56:07] Finished 'images' after 237 ms
ts-loader: Using typescript@3.7.4 and D:\webext\sample\tsconfig.json
[14:56:10] Finished 'scripts' Hash: 941f7e8c10f53a4b8956
Version: webpack 3.12.0
Time: 2076ms
        Asset     Size  Chunks             Chunk Names
background.js  6.83 kB       0  [emitted]  background
   [0] multi ./app/scripts/background.ts 28 bytes {0} [built]
   [1] ./app/scripts/background.ts 254 bytes {0} [built]
[14:56:10] webpack is watching for changes

経緯

発生した不具合

最初に、qiita.com を参考にして、

$ mkdir -p sample
$ cd sample
$ yo chrome-extension-kickstart-typescript
# 試行のためとりあえず問にはすべて[Enter]
$ npm install --save-dev typescript
$ npm install --save-dev gulp@3.9.0
$ npm run dev:chrome

のようにしたところ、

$ npm run dev:chrome

> sample@0.0.0 dev:chrome D:\webext\sample
> gulp --watch --vendor=chrome

[08:33:12] Requiring external module babel-core/register
fs.js:27
const { Math, Object } = primordials;
                         ^

ReferenceError: primordials is not defined
    at fs.js:27:26
    at req_ (D:\webext\sample\node_modules\natives\index.js:143:24)
    at Object.req [as require] (D:\webext\sample\node_modules\natives\index.js:55:10)
    at Object.<anonymous> (D:\webext\sample\node_modules\vinyl-fs\node_modules\graceful-fs\fs.js:1:37)
    at Module._compile (internal/modules/cjs/loader.js:955:30)
    at Module._extensions..js (internal/modules/cjs/loader.js:991:10)
    at Object.require.extensions.<computed> [as .js] (D:\webext\sample\node_modules\babel-register\lib\node.js:152:7)
    at Module.load (internal/modules/cjs/loader.js:811:32)
    at Function.Module._load (internal/modules/cjs/loader.js:723:14)
    at Module.require (internal/modules/cjs/loader.js:848:19)
npm ERR! code ELIFECYCLE
npm ERR! errno 1
npm ERR! sample@0.0.0 dev:chrome: `gulp --watch --vendor=chrome`
npm ERR! Exit status 1
npm ERR!
npm ERR! Failed at the sample@0.0.0 dev:chrome script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\testuser\AppData\Roaming\npm-cache\_logs\2020-01-11T06_33_12_337Z-debug.log
調査

Node.js v12 と gulp.js 3 は相性が悪いらしい?!
とりあえず「ReferenceError: primordials is not defined」で検索してみると
stackoverflow.com
に行き当たる。
こちらの回答によると、

  1. gulp.js を version 4 にする
  2. Node.js のバージョンを v12 未満へ下げる

のいずれかで対応できるらしい。

1. の gulp.js のバージョンを変える方は、3→4 の変更に対応してソースコードも変更が必要になると思われるので手間がかかりそうだったため、2. の方法を試してみる。
nvm-windows で Node.js のバージョンを 10.18.1 LTS にしてみると、確かに動作するようになった。

Node.js v12 & gulp.js 3 のまま使いたいな……
できれば Node.js や gulp.js のバージョンを変えずに使う方法はないか、と調べていると、
https://timonweb.com/posts/how-to-fix-referenceerror-primordials-is-not-defined-error/timonweb.com
に行き当たる。

要は、gulp.js が利用している graceful-fs のバージョンが古いのが悪いらしい。

$ npm list graceful-fs

で調べてみると、他はほとんどが "graceful-fs@4.2.3" を使っているのに対し、gulp(及び
gulp が依存しているパッケージ)については

+-- gulp@3.9.0
| `-- vinyl-fs@0.3.14
|   +-- glob-watcher@0.0.6
|   | `-- gaze@0.5.2
|   |   `-- globule@0.1.0
|   |     `-- glob@3.1.21
|   |       `-- graceful-fs@1.2.3
|   `-- graceful-fs@3.0.12

のように、確かに古いバージョン("graceful-fs@1.2.3"、"graceful-fs@3.0.12")を使っている。

そういえば、yo chrome-extension-kickstart-typescript 実行時にも

npm WARN deprecated graceful-fs@1.2.3: please upgrade to graceful-fs 4 for compatibility with current and future versions of Node.js

のような警告が出ていた。これによれば、graceful-fs 4 以降を使うようにすれば良さげ。

上記の記事を参考に、npm-shrinkwrap.json を

{
  "dependencies": {
    "graceful-fs": {
        "version": "^4.x.x"
     }
  }
}

の内容で作成した後、改めて npm install 後に npm run dev:chrome をすると、

# package-lock.json は npm-shrinkwrap.json と排他なため、消しておく
$ rm package-lock.json
# パッケージのインストール
$ npm install
# ※このとき、npm-shrinkwrap.json も更新されることに注意

# 開発環境を起動
$ npm run dev:chrome

すると、今度はエラーになると起動することができた。

ただし、この方法だと、

  • npm install --save-dev でパッケージを入れたりすると、最初に指定した "dependencies" が維持されないため、再度 npm-shrinkwrap.json を上記内容に書き換えた上で npm install を実行しなおす必要がある

ということで、やや保守性に欠ける。

特定のパッケージが依存関係にあるパッケージだけバージョンを指定したい
要望としては

  • gulp が依存している graceful-fs のみ、バージョンを指定したい
  • メンテナンスは最小限(意識するのは初回のみ)としたい(パッケージの追加時等に影響無いようにしたい)

となる。

調べてみると npm の代わりに Yarn を使用し、『選択的な依存関係の解決(Selective dependency resolutions)』機能を使えばできそう(package.json に "resolutions" フィールドを追加)。

  "resolutions": {
    "gulp/**/graceful-fs": "^4.x.x"
  }

この方針で調整したのが、こちらの手順になる

参考:npm で Yarn の "resolutions" っぽいことはできないか?
一応、npm 用にそういうパッケージも存在する模様。
github.com

ただ、試してみた限りでは

  • 階層指定("gulp/**/graceful-fs" 等といった書き方)はできない
  • "scripts"セクションに"preinstall"を追加するのは、一度 npm install を実行後にする必要あり
    いったん npm install してから(package-lock.json が作成されてから)でないと、npm-force-resolutions がエラーになる
  • パッケージの追加時等でpackage-lock.jsonが更新された際には、npm install をやり直す必要あり

のような感じで、今ひとつ使い勝手が良くないように感じた。
ざっくりとか見ていないため、誤解しているかもしれない

その他

generator-chrome-extension-kickstart 及び主な fork の状況

【覚書】他社で取得した独自ドメインのメールアカウントをさくらのレンタルサーバーで使う場合のDNS設定方法

さくらのレンタルサーバーのメールサーバー機能を利用して、(さくら外で取得した)独自ドメインのアカウントでメールを送信したら、Gmail で「未認証メール」扱いとなった(「?」マークがついた)。
調べてみると、独自ドメインの DNS 設定(SPFレコード)が誤っていた。
再発防止のため、メモ書きとして恥をさらしておく。





メール用の DNS 設定例

さくらのレンタルサーバーの設定が以下の様になっている場合、

項目 値(例) 備考
初期ドメイン myaccount.sakura.ne.jp さくらのアカウント名が頭に付くドメイン名
ホスト名 wwwNNN.sakura.ne.jp さくらのサーバーのホスト名(IPv4アドレス=YYY.YYY.YYY.YYY)
※サーバ情報の表示>サーバに関する情報の「ホスト名」
SPFチェック対象となる(メールヘッダのReceived: from ~に記載される)のはこちら
IPアドレス XXX.XXX.XXX.XXX 初期ドメインに紐づいたIPv4アドレス
※サーバ情報の表示>サーバに関する情報の「IPアドレス」
※共有アドレスか専用アドレスかはプランによって異なる
サーバーのホスト名に紐づいたもの(YYY.YYY.YYY.YYY)とは異なる場合があることに注意

以下の様に独自ドメインを取得し、

項目 値(例) 備考
独自ドメイン mydomain.com (さくら外で取得した)独自ドメイン名

"user@mydomain.com" のようなメールアドレス(アカウント)を、さくらのレンタルサーバーで使用することを想定する。
さくらのコントロールパネルから、ドメイン/SSL設定>[新しいドメインの追加] にて、「5. 他社で取得したドメインを移管せずに使う」により、mydomain.com を追加してあるものとする。

このとき、メール用として、(他社の)DNSサーバーには以下の様に MX レコードと TXT レコード(SPFレコード)を登録する。

mydomain.com.          3600    IN      MX      10 myaccount.sakura.ne.jp.
mydomain.com.          3600    IN      TXT     "v=spf1 a:wwwNNN.sakura.ne.jp mx ~all"

SPFレコードの記述は、さくらのドメイン詳細設定を使用した際に「□SPFレコードを利用する」にチェックを入れた場合に設定されるものに準拠している(SPFディレクトリ中、'mx' は無くても良い。また、'a:wwwNNN.sakura.ne.jp' の代わりに 'ip4:YYY.YYY.YYY.YYY' と直接 IP アドレスを記載しても動作はするが、IPv6アドレスが使用されることもあるため、ドメイン名にしておいた方が無難)。

失敗例

SPF レコードを

mydomain.com.          3600    IN      TXT     "v=spf1 ip4:XXX.XXX.XXX.XXX ~all"
mydomain.com.          3600    IN      TXT     "v=spf1 a:myaccount.sakura.ne.jp ~all"
mydomain.com.          3600    IN      TXT     "v=spf1 mx ~all"

といった様に、許容するIPアドレス/ドメインとして初期ドメインを設定してしまうと、認証に失敗してしまう(場合がある)。
ビジネスプラン等では、初期ドメイン(myaccount.sakura.ne.jp)とサーバーのホスト(wwwNNN.sakura.ne.jp)が示すIPアドレスが異なっているため、失敗(softfail)する。スタンダードプラン等では同じIPアドレスを示すので、結果的に通る(pass)場合もある。

例えば Gmail アドレス宛にメールを送信したとき、認証に失敗していると、メールヘッダで

ARC-Authentication-Results: i=1; mx.google.com;
       spf=softfail (google.com: domain of transitioning user@mydomain.com does not designate YYY.YYY.YYY.YYY as permitted sender) smtp.mailfrom=user@mydomain.com

のように SPF チェックの結果が "softfail" であったことが記載され、Gmail で当該メールを見たときに「未認証メール」として、「?」マークが付く。
メールを開き、「?」マークにマウスオーバ(スマートフォン等の場合にはタップ)すると、「Gmail では、このメールが(スパム発信者からではなく)本当に mydomain.com から送信されたメールであることを確認できませんでした。」といった文言が表示される。

SPF レコードの妥当性チェック方法

設定した SPF レコードが有効かどうかは、port25が提供しているサービスを使うのが手っ取り早い。

check-auth@verifier.port25.com

宛にメールを送信すると、「Authentication Report」という Subject がついて、

==========================================================
Summary of Results
==========================================================
SPF check:          pass
"iprev" check:      pass
DKIM check:         none
SpamAssassin check: ham

のように SPF チェックの結果などを含むメールが返信されてくる。