脳に優しく、情報収集する方法【落ち着いた心から、世界を見る】
こんにちは、マナブです。
情報収集に関する話です。
基本的に、今の世の中の人は「脳がオーバーロード」している。SNSやLINE、夜でもディスプレイを見たり、スマホゲーなど。
そのまま生きても良いけど、結局は「非効率」です。疲れているのに筋トレをするのと同じで、休む時に筋肉が成長する事を忘れている。
じゃあ、どうするか? Kindleです。
テック界隈でも、増えている。
これは「当たり前の流れ」で、なぜなら共通ゴールは「生産性と気分を上げつつ、効率的に働く」だから。
なので最近は「1時間のオフィスワークをしたら5分の散歩」とか「スマホを白黒画面にして、脳の疲れを減らす人」が増えてる。
この文脈でKindleが最適で、解説します。
届いたメルマガを、自動転送。
論より証拠で、僕のワークフローは「読みたいメルマガをKindleに自動転送 → Kindleで、ゆっくり読書」です。
Kindle画面だと脳が疲れづらく、かつ僕は長文の英語記事を読む事が多く、Kindle読書で英語力も大幅に伸びた。方法は下記です。
const CONFIG = {
// あなたのSend to Kindleアドレス(Amazonの「コンテンツと端末の管理」で確認)
KINDLE_EMAIL: '___YOUR_KINDLE_EMAIL_HERE___',
// Kindleに送りたいメルマガの差出人アドレス(ここに追加・削除するだけで管理できる)
SENDERS: [
'____NEWSLETTER’S_SENDER_EMAIL___',
'____NEWSLETTER’S_SENDER_EMAIL___',
// '____NEWSLETTER’S_SENDER_EMAIL___',
],
// 何日前までのメールを検索対象にするか(トリガー間隔より十分長ければOK)
SEARCH_WINDOW_DAYS: 2,
// 処理済みメールIDを何日分記憶しておくか(SEARCH_WINDOW_DAYSより長くすること)
PROCESSED_RETENTION_DAYS: 7,
// 自動実行の間隔(分)。1, 5, 10, 15, 30 のいずれか
TRIGGER_INTERVAL_MINUTES: 15,
};
/**
* メイン処理。時間トリガーから定期実行される。
*/
function main() {
if (CONFIG.SENDERS.length === 0) {
console.warn('CONFIG.SENDERS が空です。対象メルマガの差出人を登録してください。');
return;
}
if (CONFIG.KINDLE_EMAIL.indexOf('@kindle.com') === -1) {
console.warn('CONFIG.KINDLE_EMAIL がSend to Kindleアドレスではありません: ' + CONFIG.KINDLE_EMAIL);
return;
}
const processed = loadProcessedIds_();
const query = buildSearchQuery_();
const threads = GmailApp.search(query);
let sentCount = 0;
threads.forEach(function (thread) {
thread.getMessages().forEach(function (message) {
const id = message.getId();
if (processed[id]) return; // 送信済み
if (!isTargetSender_(message.getFrom())) return; // スレッド内の無関係なメールを除外
try {
sendToKindle_(message);
processed[id] = Date.now();
sentCount++;
console.log('Kindleへ送信: ' + message.getSubject());
} catch (e) {
// 失敗したメールは処理済みにせず、次回の実行で再試行される
console.error('送信失敗: ' + message.getSubject() + ' — ' + e);
}
});
});
saveProcessedIds_(processed);
console.log('チェック完了。新規送信: ' + sentCount + '件');
}
/**
* 1通のメールを .html 添付ファイルにしてKindleアドレスへ送信する。
* Amazonの変換エンジンはHTML内のリモート画像を取得してくれないため、
* 画像をこちらでダウンロードし、base64データURIとしてHTMLに直接埋め込む。
* (ZIP同梱方式は、Amazonが中の各ファイルを別々のドキュメントとして
* 変換してしまうため使えない)
*/
function sendToKindle_(message) {
const subject = message.getSubject() || '(無題)';
let html = buildDocumentHtml_(message, subject);
const filename = sanitizeFilename_(subject) + '.html';
// リモート画像をダウンロードしてHTMLに埋め込む。
// 取得できない画像(非対応形式・エラー)はKindle上でリンク切れアイコンに
// なるだけなので、タグごと取り除く。
let embeddedCount = 0;
let embeddedBytes = 0;
html = html.replace(/<img src=("|')(https?:\/\/[^"']+)\1([^>]*)>/g, function (match, quote, url, rest) {
if (embeddedCount >= 30 || embeddedBytes > 20 * 1024 * 1024) return ''; // 安全上限
const dataUri = fetchImageAsDataUri_(url);
if (!dataUri) return '';
embeddedCount++;
embeddedBytes += dataUri.length;
return '<img src="' + dataUri + '"' + rest + '>';
});
const blob = Utilities.newBlob('', 'text/html', filename)
.setDataFromString(html, 'UTF-8');
GmailApp.sendEmail(CONFIG.KINDLE_EMAIL, subject, 'Sent by auto-send-newsletter-to-kindle', {
attachments: [blob],
name: 'Newsletter to Kindle',
});
}
/**
* 画像URLをダウンロードして data:image/...;base64,... 形式の文字列にする。
* Kindleが扱えない形式(webp/svg等)や取得失敗時はnullを返す。
*/
function fetchImageAsDataUri_(url) {
const SUPPORTED_TYPES = {
'image/jpeg': true,
'image/jpg': true,
'image/png': true,
'image/gif': true,
'image/bmp': true,
};
try {
const resp = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
if (resp.getResponseCode() !== 200) return null;
const blob = resp.getBlob();
const type = (blob.getContentType() || '').toLowerCase().split(';')[0];
if (!SUPPORTED_TYPES[type]) return null;
const bytes = blob.getBytes();
if (bytes.length > 5 * 1024 * 1024) return null; // 1枚5MBまで
return 'data:' + type + ';base64,' + Utilities.base64Encode(bytes);
} catch (e) {
return null;
}
}
/**
* メール本文からKindle変換用のHTMLドキュメントを組み立てる。
* プレーンテキストのメール(まぐまぐ等のテキストメルマガ)は、
* 改行を<br>に、空行を段落区切りに変換してからHTML化する。
*/
function buildDocumentHtml_(message, subject) {
let body = message.getBody(); // HTML本文
if (!body || !looksLikeHtml_(body)) {
body = plainTextToHtml_(message.getPlainBody() || '');
} else {
body = cleanEmailHtml_(body);
}
const from = escapeHtml_(message.getFrom());
const date = Utilities.formatDate(message.getDate(), Session.getScriptTimeZone(), 'yyyy-MM-dd HH:mm');
return (
'<!DOCTYPE html>' +
'<html lang="ja">' +
'<head>' +
'<meta charset="UTF-8">' +
'<title>' + escapeHtml_(subject) + '</title>' +
'</head>' +
'<body>' +
'<p style="color:#666;font-size:0.85em;">' + from + '<br>' + date + '</p>' +
'<hr>' +
body +
'</body>' +
'</html>'
);
}
/**
* メールのHTMLをKindle変換用にクリーンアップする。
* SubstackなどのメルマガHTMLは、深い入れ子のテーブル・大量のインラインスタイル・
* 条件付きコメントを含み、Amazonの変換エンジンがこれらを誤解釈して
* 本文の大部分を落とすことがある(冒頭とフッターだけ残る症状)。
* 対策として、本文の意味を持つタグ(段落・見出し・リンク・画像など)だけを
* 白リストで残し、レイアウト用のタグと属性をすべて取り除いた
* シンプルなHTMLに再構築する。
*/
const KEEP_TAGS_ = /^(?:p|br|hr|h1|h2|h3|h4|h5|h6|a|img|ul|ol|li|blockquote|strong|em|b|i|u|s|small|sub|sup|pre|code)$/;
function cleanEmailHtml_(html) {
let cleaned = html;
// HTMLコメントを除去(Outlook向け条件付きコメント <!--[if mso]> ... を含む)
cleaned = cleaned.replace(/<!--[\s\S]*?-->/g, '');
// DOCTYPE宣言を除去(後段の白リスト処理はタグのみ対象のため個別に消す)
cleaned = cleaned.replace(/<!DOCTYPE[^>]*>/gi, '');
// <head>(<meta>や<style>入り)、独立した<style>/<script>を除去
cleaned = cleaned.replace(/<head(\s[^>]*)?>[\s\S]*?<\/head\s*>/gi, '');
cleaned = cleaned.replace(/<style(\s[^>]*)?>[\s\S]*?<\/style\s*>/gi, '');
cleaned = cleaned.replace(/<script(\s[^>]*)?>[\s\S]*?<\/script\s*>/gi, '');
// SubstackのCDN変換URLを元画像の直接URLに戻す(Amazonが取得できず画像が壊れる対策)
cleaned = cleaned.replace(
/https:\/\/substackcdn\.com\/image\/fetch\/[^"'\s>]*\/(https%3A%2F%2F[^"'\s>]+)/gi,
function (match, encodedUrl) {
try {
return decodeURIComponent(encodedUrl);
} catch (e) {
return match;
}
}
);
// 開封トラッキングピクセルを除去(1px画像、Substackの /o/ ビーコン)
cleaned = cleaned.replace(/<img[^>]*\s(?:width|height)=["']?1["']?[^>]*>/gi, '');
cleaned = cleaned.replace(/<img[^>]*substack\.com\/o\/[^>]*>/gi, '');
// タグを白リスト化: 本文系タグ以外(table/div/span等)は取り除き、中身のテキストだけ残す。
// 残すタグも属性を落とす(<a>のhrefと<img>のsrc/altだけ保持)。
cleaned = cleaned.replace(
/<(\/?)([a-zA-Z][a-zA-Z0-9]*)((?:[^>"']|"[^"]*"|'[^']*')*)>/g,
function (match, slash, tag, attrs) {
tag = tag.toLowerCase();
if (!KEEP_TAGS_.test(tag)) return ' ';
if (slash) return '</' + tag + '>';
if (tag === 'a') {
const href = (attrs.match(/\shref\s*=\s*("[^"]*"|'[^']*')/i) || [])[1];
return href ? '<a href=' + href + '>' : '<a>';
}
if (tag === 'img') {
const src = (attrs.match(/\ssrc\s*=\s*("[^"]*"|'[^']*')/i) || [])[1];
if (!src) return '';
const alt = (attrs.match(/\salt\s*=\s*("[^"]*"|'[^']*')/i) || [])[1] || '""';
return '<img src=' + src + ' alt=' + alt + ' style="max-width:100%">';
}
return '<' + tag + '>';
}
);
// メール冒頭のプレビュー用不可視文字(Substackが大量に入れる)を除去。
// 生の文字と数値文字参照(͏ 等)の両方の形で入っているため、両方消す。
cleaned = cleaned.replace(/[\u034F\u00AD\u200B-\u200D\uFEFF\u2007]/g, '');
cleaned = cleaned.replace(/&#(?:847|173|8199|820[3-5]|65279);/g, '');
cleaned = cleaned.replace(/&#x(?:0*34f|0*ad|200[b-d]|feff|2007);/gi, '');
// 不可視文字除去後に残る空白の連続を1つにまとめる(Kindleでの空白ページ化を防ぐ)
cleaned = cleaned.replace(/(?: |[ \t]){3,}/g, ' ');
return cleaned;
}
/** 差出人リストからGmail検索クエリを組み立てる。 */
function buildSearchQuery_() {
const fromClause = CONFIG.SENDERS.map(function (s) {
return '"' + s + '"';
}).join(' OR ');
return 'from:(' + fromClause + ') newer_than:' + CONFIG.SEARCH_WINDOW_DAYS + 'd';
}
/** Fromヘッダー(例: "Foo News <news@example.com>")が差出人リストに合致するか。 */
function isTargetSender_(fromHeader) {
const lower = fromHeader.toLowerCase();
return CONFIG.SENDERS.some(function (s) {
return lower.indexOf(s.toLowerCase()) !== -1;
});
}
/** 処理済みメールIDのマップ {messageId: 送信時刻(ms)} を読み込む。 */
function loadProcessedIds_() {
const raw = PropertiesService.getScriptProperties().getProperty('processedIds');
return raw ? JSON.parse(raw) : {};
}
/** 処理済みIDを保存する。保持期間を過ぎた古いエントリは削除する。 */
function saveProcessedIds_(processed) {
const cutoff = Date.now() - CONFIG.PROCESSED_RETENTION_DAYS * 24 * 60 * 60 * 1000;
const pruned = {};
Object.keys(processed).forEach(function (id) {
if (processed[id] >= cutoff) pruned[id] = processed[id];
});
PropertiesService.getScriptProperties().setProperty('processedIds', JSON.stringify(pruned));
}
/**
* 本文がHTMLメールかどうかの判定。
* プレーンテキストのメールでも getBody() は本文を返すため、
* 実際にHTMLの構造タグが含まれているかで見分ける。
*/
function looksLikeHtml_(body) {
return /<\s*(html|body|div|p|br|table|tr|td|img|span|a|blockquote|h[1-6])[\s>\/]/i.test(body);
}
/**
* プレーンテキストをKindle向けHTMLに変換する。
* 空行 → 段落区切り、行内の改行 → <br> として保持する。
*/
function plainTextToHtml_(text) {
const escaped = escapeHtml_(text);
return escaped
.split(/\r?\n\s*\r?\n+/) // 空行で段落に分割
.map(function (paragraph) {
return '<p>' + paragraph.replace(/\r?\n/g, '<br>') + '</p>';
})
.join('\n');
}
function sanitizeFilename_(name) {
return name.replace(/[\\\/:*?"<>|\r\n]+/g, ' ').trim().slice(0, 80) || 'newsletter';
}
function escapeHtml_(text) {
return text
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"');
}
// ─────────────────────────────────────────────
// 以下はセットアップ用の関数(手動で1回だけ実行する)
// ─────────────────────────────────────────────
/**
* 定期実行トリガーを設定する。エディタから1回だけ実行すればよい。
* 既存のトリガーがあれば作り直すので、間隔変更後の再実行も安全。
*/
function setupTrigger() {
ScriptApp.getProjectTriggers().forEach(function (t) {
if (t.getHandlerFunction() === 'main') ScriptApp.deleteTrigger(t);
});
ScriptApp.newTrigger('main')
.timeBased()
.everyMinutes(CONFIG.TRIGGER_INTERVAL_MINUTES)
.create();
console.log(CONFIG.TRIGGER_INTERVAL_MINUTES + '分ごとの自動実行トリガーを設定しました。');
}
/**
* 動作テスト用: 差出人リストに合致する直近のメール1通を実際にKindleへ送信する。
* 送信したメールは処理済みとして記録されるため、その後の自動実行で重複送信はされない。
*/
function testSendLatest() {
const threads = GmailApp.search(buildSearchQuery_());
for (let i = 0; i < threads.length; i++) {
const messages = threads[i].getMessages();
for (let j = messages.length - 1; j >= 0; j--) {
const message = messages[j];
if (isTargetSender_(message.getFrom())) {
sendToKindle_(message);
const processed = loadProcessedIds_();
processed[message.getId()] = Date.now();
saveProcessedIds_(processed);
console.log('テスト送信しました: ' + message.getSubject());
return;
}
}
}
console.warn('差出人リストに合致するメールが見つかりませんでした(検索範囲: 直近' + CONFIG.SEARCH_WINDOW_DAYS + 'd)。');
}
/**
* 診断用: 差出人リストに合致する直近のメールについて、GASが取得できた本文の
* 長さと末尾をログに出す。Kindleで本文が途中で切れるときの切り分けに使う。
* 末尾にメルマガのフッター(配信停止リンク等)が見えていれば取得は完全で、
* 切れているのはAmazonの変換側と判断できる。
*/
function debugLatestBody() {
const threads = GmailApp.search(buildSearchQuery_());
for (let i = 0; i < threads.length; i++) {
const messages = threads[i].getMessages();
for (let j = messages.length - 1; j >= 0; j--) {
const message = messages[j];
if (!isTargetSender_(message.getFrom())) continue;
const html = message.getBody() || '';
const plain = message.getPlainBody() || '';
console.log('件名: ' + message.getSubject());
console.log('getBody() の長さ: ' + html.length + ' 文字');
console.log('getPlainBody() の長さ: ' + plain.length + ' 文字');
console.log('--- getBody() 末尾500文字 ---');
console.log(html.slice(-500));
return;
}
}
console.warn('差出人リストに合致するメールが見つかりませんでした。');
}
/** 処理済み記録をすべてリセットする(再送テストをしたいときのみ使用)。 */
function resetProcessedIds() {
PropertiesService.getScriptProperties().deleteProperty('processedIds');
console.log('処理済み記録をリセットしました。');
}これを「Google Apps Script」に貼りつけて、あとは「Your Kindle Email」と「購読リスト」を書くだけ。解説はこちら。
なお最近はメルマガが長過ぎた感じがあるので、本日はこれぐらいで終わります。あ、最後に1つだけ書かせて下さい🙇♂️
急がば回れ → 急がば休め。
昔なら「急がば回れ」が良かったけど、現代で必要なのは「急がば休め」だと思う。これを経験から書きます。
認知力が落ちると、気付かぬ内に崩れます。僕が鬱っぽくなった時期は、あとから見ると「悲惨」でした。
月収1000万円くらいの時にメンタルが崩れ、半年で「月収300万円」くらいに低下しました。オワコンな動きをしていた。
脳が回復してから当時の発信を見たら、それは「その場、その場の収益化」だけに動いていました。例えるなら、、
種まきをして、花咲く前に収穫
こういった感じで、信頼を貯めては金で回収、そして繰り返し、という感じ。だから、大きなものが育ちません。
もし仮に、当時の僕が「なんか、メンタルがおかしいけど、まぁいいだろう、、ちょっと頭も痛いけど頑張ろう」と判断していたら、
もしかしたら現時点でも、同じような行動を続けていたかも。休まなかった人生を想像すると、本当に恐いです。
これは、全員で共通です。
脳が疲れ、認知力が落ちた状態で頑張っても、エネルギーは出ません。毎日30%のパワーで頑張る感じ。
僕は3年くらい低迷したし、たぶん多くの人からオワコンと思われたし、オンラインでもオフラインでも、多くの人が離れていった。
でも、全く問題ないと思ってます。
なぜなら、自転車もあるから。
これはDJ社長の言葉ですが、自分がどんなに頑張って走っていても、頭の良いやつはチャリに乗ると。だから考えろ、と。
思考力は人によってレベルが異なるけど、結局のところ、これから生きるのは自分の人生だし、だから自分なりに生きるべき。
多くの人はインフルエンサーになれないし、億も稼げない。でも、その人なりの人生があり、そこを意識して生きるだけ。
疲れている脳は「あぁ、自分はインフルエンサーなんてなれないし、結局は頑張っても意味ないや。全て辞めよう」と考えます。
元気な脳は「今日も瞑想が気持ち良かった。メルマガも楽しいし、読者の方々に感謝しよう」と考えます。
という訳で、以上です。
情報を詰め込んだり、速読するのは、完全にAIの仕事です。だから任せつつ、自分は「人々の心」に着目する。
しかし「心」に着目するには、まずは自分の心を落ち着けないとだし、その「落ち着いた心」を通して、やっと世界が見える。
松下幸之助さん (パナソニック創業者) は、毎日仏壇の前で「世界を平等に見せてください」と祈っていたと言います。
30年くらい祈ったら、少し良くなったと、本人の書籍に書いていました。きっと、そういう事なのだと思います🧘
マナブ





今の自分、修正しなくては
マナブさん、いつも哲学的な問いをありがとうございます。
自分を見失うことが多い昨今、
マナブさんのメルマガを読むと素直な気持ちになり、自分に立ち返ることができます。
今日も素敵な一日をお過ごしください。