Skip to content

Instantly share code, notes, and snippets.

@yuki2021
Last active March 6, 2024 12:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yuki2021/7c387f6f4f7fd3442f38e36983b48cd5 to your computer and use it in GitHub Desktop.
Save yuki2021/7c387f6f4f7fd3442f38e36983b48cd5 to your computer and use it in GitHub Desktop.
TampermonkeyでScrapboxからはてなブログに投稿するスクリプト
// ==UserScript==
// @name Scrapbox to Hatena Blog Post
// @namespace http://tampermonkey.net/
// @version 0.4
// @description Scrapboxのページデータを取得してはてなブログに投稿するスクリプト
// @author yuki2021
// @match https://scrapbox.io/*
// @grant GM_xmlhttpRequest
// ==/UserScript==
// ユーザーIDとAPIキー
const userId = '{{はてなのuser id}}';
const apiKey = '{{はてなブログのAPI Key}}';
const basicAuthUrl = 'https://blog.hatena.ne.jp/{{はてなのuser id}}/{{はてなのベースURL}}/atom/entry';
// Scrapboxのページデータを取得
async function fetchScrapboxPage(project, title) {
try {
const url = `https://scrapbox.io/api/pages/${project}/${title}/text`;
const response = await fetch(url);
return response.ok ? response.text() : null;
} catch (error) {
console.error('Error fetching Scrapbox page:', error);
return null;
}
}
// GM_xmlhttpRequestではてなブログに投稿する
function postToHatenaBlog(postData) {
// Basic認証のためのユーザーIDとAPIキーをBase64エンコード
const authHeader = `Basic ${btoa(`${userId}:${apiKey}`)}`;
// GM_xmlhttpRequestでPOSTリクエストを送信
GM_xmlhttpRequest({
method: 'POST',
url: basicAuthUrl,
headers: {
"Authorization": authHeader,
"Content-type": "application/xml",
},
data: postData,
onload: function(response) {
console.log(response.responseText);
alert('投稿に成功しました');
},
onerror: function(response) {
console.log(response.responseText);
alert('投稿に失敗しました');
}
});
}
// ポストデータを整形する処理
function createBlogPostXml(title, pageData, isDraft) {
const updatedTime = new Date().toISOString();
// isDraftがtrueの場合は下書きとして投稿
const draft = isDraft === '1' ? 'yes' : 'no';
console.log('draft:', draft);
// pageDataから'[と]'を削除
pageData = pageData.replace(/\[|\]/g, '');
// はてなブログの投稿データを整形
return `<?xml version="1.0" encoding="utf-8"?>
<entry xmlns="http://www.w3.org/2005/Atom"
xmlns:app="http://www.w3.org/2007/app">
<title>${title}</title>
<author><name>${userId}</name></author>
<content type="text/plain">${pageData}</content>
<updated>${updatedTime}</updated>
<app:control>
<app:draft>${draft}</app:draft>
</app:control>
</entry>`;
}
// contentの一行目をタイトルとして取得
function getFirstLine(content) {
// contentの一行目をタイトルとして取得
const firstLine = content.split('\n')[0];
// contentの一行目はタイトルとして使うため削除
const updatedContent = content.replace(firstLine + '\n', '');
return [firstLine, updatedContent];
}
// optoinキーとQキーが押されたときの処理
async function handleKeyPress(event) {
if (event.altKey && event.code === 'KeyQ') {
event.preventDefault(); // イベントのデフォルトの動作をキャンセル
// 現在のページのURLを取得
const urlMatch = window.location.href.match(/^https:\/\/scrapbox.io\/([^\/]+)\/(.+)$/);
if (urlMatch) {
const [_, project, title] = urlMatch;
// pageDataGet関数でページデータを取得
const content = await fetchScrapboxPage(project, title);
// 取得したデータが存在する場合はデータを整形して投稿
if (content) {
// contentの一行目をタイトルとして取得
const [firstLine, updatedContent] = getFirstLine(content);
// titleを尋ねるアラートを表示
const postTitle = prompt('タイトルを入力してください', firstLine);
// promptがキャンセルされた場合は処理を中断
if (!postTitle) return;
// 下書きかどうかを尋ねるアラートを表示
const isDraft = prompt('下書き保存しますか? Yes:1, No:0', '1');
// promptがキャンセルされた場合は処理を中断
if (!isDraft) return;
const postXml = createBlogPostXml(postTitle, updatedContent, isDraft);
postToHatenaBlog(postXml);
} else {
console.log("Scrapboxのページデータが取得できませんでした。");
}
} else {
console.log("ScrapboxのページURLの形式が正しくありません。");
}
}
}
document.addEventListener('keydown', handleKeyPress, true);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment