Cloud FunctionsでScrapboxのAPIを叩く
いきさつ:
KozanebaにScrapboxこざねを追加した。
各種パラメータを人間が設定するのは面倒
ページのURLを渡したらScrapboxのAPIを叩いて必要なデータを取ってくるようにしようと考えた。
しかしScrapboxのAPIのCORS制限により、Kozanebaを開いたブラウザからScrapboxのAPIは直接叩けない。
そこでCloud Functionsで叩く。
絵で解説
やったことメモ
料金プランのアップグレード
サーバサイドはPythonに慣れてるけど、ずっとPythonだけなのもなんだし、今回のは難しくないはずだからNode.jsでやるか
エミュレータにFunctionsを追加するのどうするんだ
:$ firebase init emulators
You're about to initialize a Firebase project in this directory:
/Users/nishio/kozaneba
Before we get started, keep in mind:
* You are initializing within an existing Firebase project directory
できた
設定を変更する方法が「init」なのおかしいだろ…
できてない
$ firebase emulators:start --import firebase_emulator_data
>⚠ functions: The functions emulator is configured but there is no functions source directory. Have you run firebase init functions?
$ firebase init functions
:? What language would you like to use to write Cloud Functions? TypeScript
? Do you want to use ESLint to catch probable bugs and enforce style? Yes
? Do you want to install dependencies with npm now? Yes
これでできたかな?
$ firebase emulators:start --import firebase_emulator_data
:⚠ Error: Cannot find module '/Users/nishio/kozaneba/functions/lib/index.js'. Please verify that the package.json has a valid "main" entry ...
⚠ We were unable to load your functions code. (see above)
- It appears your code is written in Typescript, which must be compiled before emulation.
- You may be able to run "npm run build" in your functions directory to resolve this.
あー、TypeScriptを選択したから先にビルドしろとのことらしい
サンプルコードがあったからコメントアウトを外してビルドしてみる
tsimport * as functions from "firebase-functions";
// // Start writing Firebase Functions
// // https://firebase.google.com/docs/functions/typescript
//
export const helloWorld = functions.https.onRequest((request, response) => {
functions.logger.info("Hello logs!", { structuredData: true });
response.send("Hello from Firebase!");
});
今度こそできた
http://localhost:5001/regroup-d4932/us-central1/helloWorld
にブラウザでアクセスしてレスポンスを見る
functions.logger.info("Hello logs!", { structuredData: true });
に関してはエミュレータが走ってるターミナルにこう表示されてた
:i functions: Beginning execution of "us-central1-helloWorld"
> {"structuredData":true,"severity":"INFO","message":"Hello logs!"}
i functions: Finished "us-central1-helloWorld" in ~1s
さて、ScrapboxAPIを作る
requestオブジェクトの仕様はどこにあるかな
なるほどExpressと同じか
tsexport const get_scrapbox_page = functions.https.onRequest(
(request, response) => {
console.log(request.body);
response.json(request.body);
// functions.logger.info("Hello logs!", { structuredData: true });
// response.send("Hello from Firebase!");
}
);
jsfetch("http://localhost:5001/regroup-d4932/us-central1/get_scrapbox_page", {
method: "post",
body: JSON.stringify({ foo: "bar" }),
})
.then((x) => x.json())
.then((x) => console.log(x));
よし、やりとり部分はできた。
ではScrapboxのAPIを叩こう。
tsimport fetch from "node-fetch";
export const get_scrapbox_page = functions.https.onRequest(
(request, response) => {
const body = JSON.parse(request.body);
const url = body.url;
const api_url = url.replace("scrapbox.io/", "scrapbox.io/api/pages/");
fetch(api_url).then((req) => {
console.log(req);
req.text().then((text) => {
console.log(text);
response.send(text);
});
});
}
);
jsfetch("http://localhost:5001/regroup-d4932/us-central1/get_scrapbox_page", {
method: "post",
body: JSON.stringify({
url: "https://scrapbox.io/nishio/2021-08-28Kozaneba%E9%96%8B%E7%99%BA%E6%97%A5%E8%A8%98",
}),
})
.then((x) => x.json())
.then((x) => console.log(x));
output
できた
じゃあアプリからこのAPIを叩こう→CORS
なるほど、こうか。
tsexport const get_scrapbox_page = functions.https.onRequest((req, res) => {
res.set("Access-Control-Allow-Origin", "*");
if (req.method === "OPTIONS") {
// Send response to OPTIONS requests
res.set("Access-Control-Allow-Methods", "GET");
res.set("Access-Control-Allow-Headers", "Content-Type");
res.set("Access-Control-Max-Age", "3600");
res.status(204).send("");
return;
}
const body = JSON.parse(req.body);
const url = body.url;
const api_url = url.replace("scrapbox.io/", "scrapbox.io/api/pages/");
fetch(api_url).then((req) => {
req.text().then((text) => {
res.send(text);
});
});
});
問題なくアプリから叩けるようになった。
できたできた
あとはfirebase deployして...
$ firebase deploy
ESLintが文句を言ってうるさいので無効にする
$ firebase init functions
:? Do you want to use ESLint to catch probable bugs and enforce style? No
再度デプロイ
$ firebase deploy
:i functions: creating Node.js 14 function get_scrapbox_page(us-central1)...
✔ functions[get_scrapbox_page(us-central1)]: Successful create operation.
i functions: cleaning up build files...
Function URL (get_scrapbox_page(us-central1)): https://us-central1-regroup-d4932.cloudfunctions.net/get_scrapbox_page
できた
叩くのを localhost:5001
から https://us-central1-regroup-d4932.cloudfunctions.net/get_scrapbox_page
にかえる
tsfetch("https://us-central1-regroup-d4932.cloudfunctions.net/get_scrapbox_page", {
method: "post",
body: JSON.stringify({
url: "https://scrapbox.io/nishio/2021-08-28Kozaneba%E9%96%8B%E7%99%BA%E6%97%A5%E8%A8%98",
}),
})
.then((x) => x.json())
.then((x) => console.log(x));
できた