和休は、いまご覧になっている「旅馬」というホームーページと、ブログ「旅馬の舞台裏」を持っています。
どちらにも、もっとお客さんを呼び込みたいと思っていまして、いい方法は無いだろうか?と考えていました。
新しい記事を作成したら、Xにそのことをお知らせするような記事を書くことで、お客さんを呼ぶことができます。この方法は以前から採用していました。
過去の記事をランダムにXに投稿する
「もっと旅馬と旅馬の舞台裏を知ってほしい。」という思いで、ネットの海をさまよっていると過去の記事をXに投稿する方法に出会いました。
「旅馬」はワードプレスで運営していまして、過去の記事をX(当時はTwitterでした。)に投稿するプラグイン(オプション機能)が存在します。それが、「Revive Old Posts」というプラグインです。
早速和休も導入して、しばらくの間は順調に過去記事が投稿されていたのです。
XのAPIの仕様変更(有料化)
「Revive Old Posts」がXにポストするために、XのAPI(異なるソフトウェア、プログラム、Webサービス同士をつなぐ「接点」や「仕組み」のこと)を取得する必要があります。
ところが、TwitterがXに変わったあたりから、XのAPIの仕様が変更され、無料プランが無くなってしまいました。
APIが使えなくなったので、過去記事の投稿もストップ。
AI(Gemini)の力を借りて代わりの方法を模索するも、XのAPI問題を突破する方法はありませんでした。
完全自動化はあきらめる
行き詰った和休、Geminiにこんな質問をしてみました。

こんな方法はどうだろう。
これから自作するプログラムが、両方のブログのrssリーダーからポストする文案を組み立てる。できたものを私のメールアドレスに送る。
私は受け取ったメールからxの予約投稿にコピペする。
この質問を受け取ったGeminiは、「非常に素晴らしいアイデアです!この方法なら、費用を1円もかけず、かつXの有料API制限やサービス終了のリスクを完全に回避できます。」と回答。なんとすぐにプログラムを書いてくれました。
使用したプログラムは、Google Apps Script (GAS)といい、Googleが提供するプログラミング環境です。Googleドライブから使用することができます。
予約投稿用の文案を自動的に作成するプログラム
Geminiと打ち合わせて決めたプログラムの概要は、次のとおり。
- 旅馬と旅馬の舞台裏のRSSを取得する
- 取得したRSSから過去の記事をランダムに7つ選ぶ
- 選んだ記事のタイトル、記事の概要を、あらかじめ設定した投稿用フォーマットに組み込んで投稿文を組み立てる
- 1日1ポスト、7ポスト分の投稿文を組み立てる
- 組み立てた7ポスト分の投稿文を和休のメールアドレスへ送る
- 和休は、受け取ったメールをXに貼り付け、予約投稿する
7ポスト分の投稿文には、予約投稿する日付を入れてもらうように指示しました。
何度かGeminiにコードを書き直してもらい、最終的に採用となったのが次のコードです。
generateAndSendWeeklyPosts(毎週のポストを生成して送る)
// ==========================================
// 設定項目(ここをご自身の情報に変更してください)
// ==========================================
const CONFIG = {
// メール送信先のアドレス
TO_EMAIL: 'your-email@gmail.com',
// 各ブログのRSSフィードURL
RSS_URLS: [
'https://your-wordpress-site.com/feed/', // WordPressのRSS
'https://your-hatena-id.hatenablog.com/rss' // はてなブログのRSS
],
// 1投稿あたりの定型文フォーマット
// {title} は自動で省略処理されたタイトルが入ります
POST_FORMAT: `(* ̄∇ ̄)ノ 過去の記事をランダムに投稿しています!
{title}
{link}
#ワードプレス #旅馬 #旅馬の舞台裏 #バイクツーリング #プログラミング`
};
// ==========================================
// メイン処理(メール生成・送信)
// ==========================================
function generateAndSendWeeklyPosts() {
// 1. 各RSSから全記事を取得
let allArticles = [];
CONFIG.RSS_URLS.forEach(url => {
const articles = fetchArticlesFromRss(url);
allArticles = allArticles.concat(articles);
});
if (allArticles.length === 0) {
Logger.log('記事が取得できませんでした。');
return;
}
// 2. 重複のないよう7件をランダム選出
const selectedArticles = getRandomArticles(allArticles, 7);
// 3. 今日を基準に「次の月曜日」から始まる7日間を計算
const startDate = getNextMonday(new Date());
// 4. メールの本文・件名を組み立て
let emailBody = "以下の7日分のテキストをコピーして、Xの予約投稿等にご活用ください。\n\n";
const dayNames = ["日", "月", "火", "水", "木", "金", "土"];
for (let i = 0; i < 7; i++) {
const targetDate = new Date(startDate);
targetDate.setDate(startDate.getDate() + i);
const m = targetDate.getMonth() + 1;
const d = targetDate.getDate();
const dayOfWeek = dayNames[targetDate.getDay()];
// 【m月d日aaa曜日のポスト】表記の生成
const dateLabel = `【${m}月${d}日 ${dayOfWeek}曜日のポスト】`;
const article = selectedArticles[i % selectedArticles.length];
// ★タイトルが長すぎる場合に自動で省略(例:35文字以上なら削って「…」にする)
// ※URLやハッシュタグを含めて140文字以内に収まる安全ラインです
const safeTitle = truncateTitle(article.title, 35);
const postText = CONFIG.POST_FORMAT
.replace('{title}', safeTitle)
.replace('{link}', article.link);
emailBody += `${dateLabel}\n${postText}\n\n--------------------------------------------------\n\n`;
}
const startDateStr = `${startDate.getMonth() + 1}月${startDate.getDate()}日`;
const subject = `【X予約投稿用】次週(${startDateStr}〜)のポスト文案 7日分`;
// 5. メール送信
GmailApp.sendEmail(CONFIG.TO_EMAIL, subject, emailBody);
Logger.log('メール送信完了: ' + subject);
}
// ==========================================
// 補助関数
// ==========================================
// RSSから記事(タイトル・リンク)を取得
function fetchArticlesFromRss(rssUrl) {
const articles = [];
try {
const response = UrlFetchApp.fetch(rssUrl);
const xml = XmlService.parse(response.getContentText());
const root = xml.getRootElement();
// RSS 2.0 (channel -> item) または Atom (entry) の判定
let items = [];
if (root.getChild('channel')) {
items = root.getChild('channel').getChildren('item');
items.forEach(item => {
const title = item.getChildText('title');
const link = item.getChildText('link');
if (title && link) articles.push({ title: title.trim(), link: link.trim() });
});
} else {
const atomNs = XmlService.getNamespace('http://www.w3.org/2005/Atom');
items = root.getChildren('entry', atomNs);
items.forEach(item => {
const title = item.getChildText('title', atomNs);
const linkElem = item.getChild('link', atomNs);
const link = linkElem ? linkElem.getAttribute('href').getValue() : '';
if (title && link) articles.push({ title: title.trim(), link: link.trim() });
});
}
} catch (e) {
Logger.log('RSS取得エラー (' + rssUrl + '): ' + e.message);
}
return articles;
}
// 配列から指定数をランダム抽出
function getRandomArticles(array, count) {
const shuffled = array.slice().sort(() => 0.5 - Math.random());
return shuffled.slice(0, count);
}
// 今日以降で「最も近い次の月曜日」を取得
function getNextMonday(currentDate) {
const result = new Date(currentDate);
const day = result.getDay();
const diff = (day === 0 ? 1 : 8 - day);
result.setDate(result.getDate() + diff);
result.setHours(0, 0, 0, 0);
return result;
}
// タイトルを指定文字数でカットし、超えたら「…」を付与する関数
function truncateTitle(title, maxLength) {
if (title.length > maxLength) {
return title.substring(0, maxLength) + "…";
}
return title;
}
設定手順
- GASの作成手順
- ブラウザで Google Drive を開き、左上の「+ 新規」>「その他」>「Google Apps Script」を選択します。
- 表示されたエディタ画面に、上のコードをそのまま上書き貼り付けします。
- コード内の CONFIG 部分(TO_EMAIL、RSS_URLS、POST_FORMAT)をご自身の情報に書き換えます。
- テスト実行
- 上部の関数選択ドロップダウンから generateAndSendWeeklyPosts を選び、「実行」 を押します。
- 初回のみ「アクセス許可(権限の承認)」を求められますので、画面の指示に従って許可してください。
- メール(Gmail)に【m月d日aaa曜日のポスト】という形式で1通7日分の文面が届いているか確認します。
- 自動タイマー(トリガー)の設定(毎週金曜日に自動化したい場合)
- 左側の時計マーク(トリガー)をクリック > 右下の「トリガーを追加」を選択。
- 実行する関数: generateAndSendWeeklyPosts
- イベントのソース: 時間主導型
- 時間ベースのトリガーのタイプ: 週ベースのタイマー
- 曜日を選択: 毎週金曜日
- 時間帯を選択: 午後8時〜9時(お好みの時間)







この設定をしておけば、基本は毎週金曜日の夜に届き、金曜日に時間が取れず後から手動で実行(エディタで「実行」ボタンを押す)しても、常にその時点からみた「来週の月曜〜日曜」の7日分が届くようになります。
プログラムから送られてきたメール
実際に和休のメールに届いた、7ポスト分の投稿文はこちら。
以下の7日分のテキストをコピーして、Xの予約投稿等にご活用ください。
【10月19日 月曜日のポスト】
(* ̄∇ ̄)ノ 過去の記事をランダムに投稿しています!
大学時代の友人と東京で忘年会と東京観光
https://waq3-travelog.hatenablog.com/entry/party-and-tokyo-sightseeing
#ワードプレス #旅馬 #旅馬の舞台裏 #バイクツーリング #プログラミング
【10月20日 火曜日のポスト】
(* ̄∇ ̄)ノ 過去の記事をランダムに投稿しています!
Amazonのファイヤースティックを導入しました
https://waq3-travelog.hatenablog.com/entry/introducing-the-fire-stick
#ワードプレス #旅馬 #旅馬の舞台裏 #バイクツーリング #プログラミング
【10月21日 水曜日のポスト】
(* ̄∇ ̄)ノ 過去の記事をランダムに投稿しています!
アルファードのタイヤをスタッドレスタイヤへ交換 2023
https://waq3-travelog.hatenablog.com/entry/preparation-for-winter-2023
#ワードプレス #旅馬 #旅馬の舞台裏 #バイクツーリング #プログラミング
【10月22日 木曜日のポスト】
(* ̄∇ ̄)ノ 過去の記事をランダムに投稿しています!
V-Strom650のミラーをタナックス・クロス2ミラーに交換
https://waq3-travelog.com/replace-with-cross2-mirror/
#ワードプレス #旅馬 #旅馬の舞台裏 #バイクツーリング #プログラミング
【10月23日 金曜日のポスト】
(* ̄∇ ̄)ノ 過去の記事をランダムに投稿しています!
峰山高原リゾート ホワイトピークに行きました
https://waq3-travelog.hatenablog.com/entry/white-peak-ski-20240102
#ワードプレス #旅馬 #旅馬の舞台裏 #バイクツーリング #プログラミング
【10月24日 土曜日のポスト】
(* ̄∇ ̄)ノ 過去の記事をランダムに投稿しています!
アルファードのエアコンフィルターを交換する【2024年2月】
https://waq3-travelog.hatenablog.com/entry/replacing-alphards-air-conditioner-filter-202402
#ワードプレス #旅馬 #旅馬の舞台裏 #バイクツーリング #プログラミング
【10月25日 日曜日のポスト】
(* ̄∇ ̄)ノ 過去の記事をランダムに投稿しています!
恵比寿様のお祭り「十日えびす」に行ってきました
https://waq3-travelog.hatenablog.com/entry/toka-ebisu-2026
#ワードプレス #旅馬 #旅馬の舞台裏 #バイクツーリング #プログラミング
送られてきたメールの「(* ̄∇ ̄)ノ 過去の記事をランダムに投稿しています!」から「#プログラミング」までを選択してコピー、Xのポストに貼り付けます。
後は、【10月25日 日曜日のポスト】とあれば、予約投稿に2026年10月25日の好きな時間を設定してポストするだけです。
こんな感じで和休は、XのAPIの壁を突破しました。
コメントをどうぞ!