Skip to content

Instantly share code, notes, and snippets.

@Neos21

Neos21/README.md

Created Oct 16, 2020
Embed
What would you like to do?
Cross Post

Cross Post

Netlify Functions で動かしていたソースコードの簡略版。

  • Do Well (オレオレマイクロブログ)
  • Misskey
  • Mastodon

に投稿を一括送信する。

  • ブックマークレットからの GET リクエスト
  • IFTTT Webhook からの POST リクエスト
  • Slack Incoming Webhook からの POST リクエスト

の3箇所から受信するための方法も記載。

コードそのままでは動かないので、適宜修正すること。

$ npm run dev
# npx netlify-lambda serve src だと chokidar でエラーが出るので npx は使えない

# ブックマークレットからの GET リクエストを再現する : 日本語は encodeURIComponent 後のモノを渡す
$ curl -X GET "http://localhost:9000/.netlify/functions/cross-post?credential=CREDENTIAL&text=$(node -p "encodeURIComponent('テスト')")"

# IFTTT Webhook からの POST リクエストを再現する
$ curl -X POST --data '{"credential":"FROM-IFTTT","text":"テスト"}' 'http://localhost:9000/.netlify/functions/cross-post'

# Slack Incoming Webhook からの POST リクエストを再現する
$ curl -X POST --data-urlencode 'token=TOKEN' --data-urlencode 'text=テスト' 'http://localhost:9000/.netlify/functions/cross-post'
// axios を使わず Node.js 組み込みの http・https で送信する場合
// ====================================================================================================
const http = require('http');
const https = require('https');
/**
* リクエストする
*
* @param {string} url URL
* @param {object} options オプション
* @return {Promise<*>} Promise
*/
function request(url, options) {
return new Promise((resolve, reject) => {
options = options || {};
const body = options.body || null;
if(options.body) {
delete options.body;
}
const agent = url.startsWith('https:') ? https : http;
const req = agent.request(url, options, (res) => {
res.setEncoding('utf8');
let data = '';
res.on('data', (chunk) => {
data += chunk;
})
.on('end', () => {
resolve(data);
});
})
.on('error', (error) => {
reject(error);
})
.on('timeout', () => {
req.abort();
reject('Request Timeout');
});
req.setTimeout(8000);
if(body) {
req.write(body);
}
req.end();
});
}
/**
* Do Well (オレオレマイクロブログ) に POST する
*
* @param text 投稿文字列
* @return {Promise} 通信結果
*/
function postDoWell(text) {
return request('http://example.com/index.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
},
body: new URLSearchParams({
credential: 'CREDENTIAL',
text : text
}).toString()
});
}
/**
* Misskey に POST する
*
* @param text 投稿文字列
* @return {Promise} 通信結果
*/
function postMisskey(text) {
return request('https://misskey.io/api/notes/create', {
method: 'POST',
body: JSON.stringify({
i : 'MISSKEY_API_KEY',
text: text
})
});
}
/**
* Mastodon に POST する
*
* @param text 投稿文字列
* @return {Promise} 通信結果
*/
function postMastodon(text) {
return request('https://mstdn.jp/api/v1/statuses', {
method: 'POST',
headers: {
'Accept' : 'application/json, text/plain, */*',
'Content-Type': 'application/json;charset=utf-8',
'User-Agent' : 'my-post-api' // 必須
},
body: JSON.stringify({
access_token: 'MASTODON_ACCESS_TOKEN',
status : text,
visibility : 'public'
})
});
}
// axios を使う例
// ====================================================================================================
const axios = require('axios');
/**
* Do Well (オレオレマイクロブログ) に POST する
*
* @param text 投稿文字列
* @return {Promise} 通信結果
*/
function postDoWell(text) {
const params = new URLSearchParams();
params.append('credential', 'CREDENTIAL');
params.append('text' , text);
return axios.post('http://example.com/index.php', params);
}
/**
* Misskey に POST する
*
* @param text 投稿文字列
* @return {Promise} 通信結果
*/
function postMisskey(text) {
return axios.post('https://misskey.io/api/notes/create', {
timeout: 8000,
i : 'MISSKEY_API_KEY',
text : text
});
}
/**
* Mastodon に POST する
*
* @param text 投稿文字列
* @return {Promise} 通信結果
*/
function postMastodon(text) {
return axios.post('https://mstdn.jp/api/v1/statuses', {
timeout : 8000,
access_token: 'MASTODON_ACCESS_TOKEN',
status : text,
visibility : 'public'
});
}
// Netlify Functions を想定したエントリポイント
// ====================================================================================================
/**
* エントリポイント
*
* @param event イベント
* @param _context 未使用
* @param callback レスポンスを行うコールバック
* @return statusCode と body を含む連想配列
*/
exports.handler = (event, _context, callback) => {
// レスポンスの雛形 (通常時は result・エラー時は error プロパティにメッセージを入れる)
const response = {
statusCode: 400,
headers: { 'Content-Type': 'application/json' }
};
// ブックマークレットから本エントリポイントを GET で呼び出した場合
const credential = event.queryStringParameters.credential;
const text = event.queryStringParameters.text;
// IFTTT の Webhook から本エントリポイントを POST で呼び出した場合
const params = JSON.parse(event.body);
const credential = params.credential; // IFTTT Webhook から送られてきたモノと判別するためのパラメータを用意しておく
const text = params.text;
// Slack Incoming Webhook から本エントリポイントを POST で呼び出した場合
const params = [...new URLSearchParams(event.body)].reduce((acc, pair) => ({...acc, [pair[0]]: pair[1]}), {});
const token = params.token; // Slack Incoming Webhook が送ってくるトークン
const text = params.text;
// 適宜パラメータの正当性チェック、例外ハンドリングなどを行うこと
return Promise.all([postDoWell(text), postMisskey(text), postMastodon(text)])
.then((results) => {
response.statusCode = 200;
response.body = JSON.stringify({ result: 'Success' });
return callback(null, response);
})
.catch((error) => {
response.body = JSON.stringify({ error: 'Failed' });
return callback(null, response);
});
};
[build]
command = "npm run build"
functions = "dist"
{
"name": "cross-post",
"private": true,
"scripts": {
"dev": "netlify-lambda serve src",
"build": "netlify-lambda build src"
},
"devDependencies": {
"netlify-lambda": "1.6.3"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment