以前にも紹介した、Twitterのユーザ名からイベント参加情報を抽出するツール「Circle Checker」ですが、ややあってRustで作り直しました。
以前の記事はこちらです
なので今回は新しく作ったCircle Checker2の使い方と、前バージョンとの違い、QAなどを書いていこうと思います。
続きを読むだいぶ記事を寝かしてしまったので、今が2022年1月の気分で読んで下さい
年が明けてから、毎朝液タブでのクロッキーを習慣にしているのですが、ある日、電源ボタンを押してもBIOSが起動しない自体が発生しました。
この日は「あ、マザボのリチウム電池が切れたな」と人生二度目のマザーボード電池交換をすることで復旧させることができたのですが、 明けて次の日、二度とBIOSが起動してくることはありませんでした。。。
2013年に組んだので、寿命は9年でした。
そろそろWin8.1じゃ駄目だろうと、Win10を入れた起動ドライブを作ったり、大枚叩いてM1MAXなMacを買ったりしていたのですが、 肝心の絵を描く環境の移行を全然していなかったのです。
Win10環境作ったら、「お前のPC、Win11駄目だから!」ってMSに言われたり、 「Macへのクリスタ設定の引き継ぎ、よくわからん」ってなったせいなのですが。
思い返せば初代自作PCはマザーボードの電池交換のあと、HDDがぶっとんで使えなくなったので、 マザーボード付属のリチウム電池が切れた場合は、新しいPCの準備を始めるのが良いのかもしれません。
というわけにはもちろんいかず、CPUとメモリは最低限買い替える必要があります。会社辞めるというのにこの出費よ。。。
気を取り直して、マザーボード選びからしました。
使っていたマザーボードがASUSのだったので、今回もASUSから探していったのですが、いいなと思ったProArt B660-CREATOR D4はまだ出てなかったので、
「じゃあなんかそれなりのでいいか」
とこれらをツクモで買ってきたわけです。
それなりとか言いながら、ちょっとだけ奮発しました
後はWin10のOS入っているSSD繋いでおしまいかと思っていたのですが、最新のマザーボードは起動ディスクとしてSATAスロットのディスクを認識しませんでした。。。
いろいろ調べていると、新し目のマザーボードはM2スロットだけが起動ディスクになるようで、SATAのディスクでも起動するにはCMS機能を有効にする必要があるようでした。
この機能を有効にするには、グラフィックボードがマザーボードに挿されてなければならず、そうするとCPUについているグラフィック機能は使えなくなり、 intelのF無し型番を買った意味は消失しました・・・
ともあれしょうがないので、前のマザーボードにも挿していたグラフィックボードをはめ込み、CMSを有効にしました。
とりあえず前も使っていたWin8.1のOSで起動し、クリスタ環境を移行しようとしたのですが、 そもそもマザーボードがWindows 8.1に対応していないようで、ドライバの類がインストールできず、Win8.1環境ではネットに繋ぐことができなくなってしまいました。
まあそれも致し方なしかと思い、設定のコピー作業を数分していたところ、CPUが100%で張り付きました。 タスクマネージャで見る限り、特に何かがパワーを食っているようでもなかったので、とりあえず一旦PCを終了させ、再度立ち上げましたが、 2分くらいするとやはり100%になるようだったので、これも旧OSとマザーボードとの相性問題なのかもしれません。
更にいうと今回買った今のマザーボードはSATAが4ポートで、他がM2とかいう固定するタイプのスロットになってたので、 過去環境と同じにするにはSATAポートが足りませんでした。
なので過去環境をそのまま移行するような場合は、最新のマザーボードを選んでもあまり利点はないのかもしれないですね。
大人しく全部買い換えましょう!
公式の説明どおりに実行すれば移行はできました。
ただキャンバス設定は移行されなかったので、CPUが張り付く前に画面キャプチャを撮っておき、手動で設定し直しました。
前の環境だとできなかった4K表示ができたので嬉しかったのですが、描画遅延が発生していました。
色々見てみたのですが、Win10だとディスプレイのリフレッシュレートも変更できるようで、そこが30Hzになっていました。
これは60Hz近くが設定できるまで解像度を落とすことで解消できました。
数日使っていると、Windows11のアップデートが配信されたので、面倒くさいことは早めに済ましたい思いから、そのままアップデートを実行しました。
新しいOSなので不具合出るかなとは思ったのですが、意外と問題なく環境が引き継がれていました。
ともあれWindows11に全く問題がないわけでもなく、Windows Updateのあとにサウンド設定が初期化されてしまったり、画面設定がおかしくなったりはします。 設定し直せば元に戻すことはできるので、苦汁をなめさせられてきたWindows Update問題の中ではマシな方だとは思います。
それでもWindows11は今までのWindowsで一番しっくり来ている感じなので、アップデートおすすめです。
TypeScriptで書いているNext.jsアプリのユーザ情報管理を、 Firebase AuthenticationとFirestoreで行ったのですが、 調べたときに見かけたコードサンプルは、呼び出し方がJavaScriptのままだったので、 TypeScriptっぽいサンプルもここに残して置きます。
というかWeb v9の書き方じゃない・・・と後から気付きました・・・
https://firebase.google.com/docs/firestore/query-data/get-data?hl=ja#web-v9
TSの書き方では(もといWeb v9の書き方では)firebase.auth
のような呼び出し方をすることはできないので、
それぞれgetAuthやgetFirestoreを使ってインスタンスを取得する必要があります。
import * as firebase from 'firebase/app'; import { Auth, getAuth } from 'firebase/auth'; import { Firestore, getFirestore } from 'firebase/firestore'; const firebaseConfig = { apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, databaseURL: process.env.NEXT_PUBLIC_FIREBASE_DATABASE_URL, projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID, }; if (typeof window !== 'undefined' && !firebase.getApps().length) { firebase.initializeApp(firebaseConfig); } export default firebase; export const getFirebaseAuth = (): Auth => getAuth(); export const getFirebaseDb = (): Firestore => getFirestore();
firebaseをexportしていますが、今のところこれを触ることはなかったので、いらないかもしれません。
次はAuthenticationとFirestoreへの接続です。
signInWithPopupの呼び出しは、パラメータにAuthインスタンスと対象のプロバイダを渡すことで行なえます。
サインインが成功するとUserCredentialが返却されるので、ユーザ情報が必要な場合は、そのuser属性を取得してください。
import { GoogleAuthProvider, User, UserCredential, signInWithPopup } from 'firebase/auth'; import { getFirebaseAuth } from 'libs/firebase'; 〜〜〜 const auth = getFirebaseAuth(); const provider = new GoogleAuthProvider(); const signInResult: UserCredential = await signInWithPopup(auth, provider); const crrentUser: User = signInResult.user;
AuthインターフェイスにはcurrentUserがあるのですが、これはonAuthStateChangedの処理後でなければ値がnullになっているので、利用する場合は気を付けてください。
FirestoreもAuthenticationと同じように、getFirestore(ここではgetFirebaseDb)を使って処理を行います。 対象のコレクションとドキュメントを指定し、collection関数とdoc関数を使って、入れ子にして呼び出します。
doc(collection(Firestoreインスタンス, コレクション名), ドキュメントID)
これで得られるDocumentDataオブジェクトに、getDocで取得、setDocで追加の操作をします。
getDocで取得したオブジェクトのdata()
を呼び出すと、コレクションの値が取得できます。
フィールドの値はDocumentData["フィールド名"]
の形で取得します。
import { DocumentData, collection, doc, getDoc } from 'firebase/firestore'; 〜〜〜 const db = getFirebaseDb(); const uid = "対象のドキュメントID"; const document = await getDoc(doc(collection(db, 'users'), uid)); if (document.exists()) { const documentId = document.id; // この場合、uidと同じ値 const documentData: DocumentData = document.data(); const user: UserType = { id: documentId, name: documentData['name'], email: documentData['email'], picture: documentData['picture'], token: documentData['token'], };
ドキュメントをコレクションに追加するには、DocumentDataにsetDocを行います。
登録するデータはオブジェクトで定義してください。
const data = { name: "名前", email: "メールアドレス", picture: "アバターURL", token: "アクセストークン", }; await setDoc(対象のDocumentData, data);
実際のコードだと以下のようになりました。
import { GoogleAuthProvider, UserCredential, signInWithPopup } from 'firebase/auth'; import { Firestore, collection, doc, getDoc, setDoc } from 'firebase/firestore'; import Router from 'next/router'; import { convertToUser } from './user'; import { getFirebaseAuth, getFirebaseDb } from 'libs/firebase'; import { UserType } from 'application'; export const googleSignin = () => { (async () => { const auth = getFirebaseAuth(); const provider = new GoogleAuthProvider(); try { const signInResult: UserCredential = await signInWithPopup(auth, provider); const db = getFirebaseDb(); const exist = await existFirebaseUser(db, signInResult.user?.uid); if (!exist) { // firebaseへのユーザ登録処理 const user: UserType = await convertToUser(signInResult.user); const isRegistered = await registerUser(db, user); if (isRegistered) { console.log('登録OK uid: ' + user.id); } else { console.log('登録NG uid: ' + user.id); Router.push('/error'); } } Router.push('/mypage'); } catch (error) { console.log(error); console.log(error.code); console.log(error.message); } })(); }; const existFirebaseUser = async (db: Firestore, uid: string): Promise<boolean> => { const result = (await getDoc(doc(collection(db, 'users'), uid))).exists(); return result; }; const registerUser = async (db: Firestore, user: UserType): Promise<boolean> => { const userData = { name: user.name, email: user.email, picture: user.picture, token: user.token, }; try { await setDoc(doc(collection(db, 'users'), user.id), userData); console.log('登録成功'); return true; } catch (error) { console.log('登録失敗:' + error); return false; } }; export const signout = () => { getFirebaseAuth() .signOut() .then(() => { window.location.reload(); }); };
import { User } from 'firebase/auth'; import { UserType } from 'application'; export const convertToUser = async (firebaseUser: User): Promise<UserType> => { if (firebaseUser) { const user: UserType = { id: firebaseUser.uid, name: firebaseUser.displayName, email: firebaseUser.email, picture: firebaseUser.photoURL, token: await firebaseUser.getIdToken(), }; return user; } return null; };
四苦八苦してしまって本当に辛かったので、他の人が同じような苦労をしないように・・・
個人アカウントのOAuth認証で良い場合はこのサイトの手順が参考になります。
How to Use the Box API with Google Apps Script - Digital Inspiration
ただ今回は、AppSheetからの呼び出しで使いたかったので、これだと用途に合わないんですよね。
ざっくりですが、以下の作業が必要です。
Boxの開発アプリをCustom AppのServer Authenticationで作成します。
※途中で設定を日本語にしたので、図ではサーバー認証(クライアント資格情報許可)
です
アプリの設定が終わったら承認リクエストを飛ばします。
そうするとBox Adminの権限があるメンバーにアプリ承認のリクエストが飛ぶので、承認してください。
GASのコードはclaspを使って作成しています。
ここは特に真似る必要はありませんが、TypeScriptでのdoPost関数の実装例として見て貰えればと。
結局nodeライブラリは使わなかったのですが、claspの使い方はこのサイトを参考にさせて貰いました。
{ "timeZone": "Asia/Tokyo", "dependencies": {}, "exceptionLogging": "STACKDRIVER", "oauthScopes": [ "https://www.googleapis.com/auth/drive.readonly", "https://www.googleapis.com/auth/drive", "https://www.googleapis.com/auth/script.external_request" ], "runtimeVersion": "V8" }
重要なのはアクセストークンを取得しているgetAccessTokenで、他は各自の処理に合わせて貰えればOKです。
const BOX_CLIENT_ID = 'クライアントID'; const BOX_CLIENT_SECRET = 'クライアントシークレット'; const BOX_ENTERPRISE_ID = '組織ID'; const BOX_TEMPLATE_FOLDER_ID = 'コピー元フォルダのID'; const BOX_PARENT_FOLDER_ID = 'コピー先(親)フォルダのID'; // https://developers.google.com/apps-script/guides/web#request_parameters export function doPost(e: GoogleAppsScript.Events.DoPost): GoogleAppsScript.Content.TextOutput { const contents = JSON.parse(e.postData.contents); const orderDate = contents["orderDate"]; const customerName = contents["customerName"]; const createFolderId = copyFolder(orderDate, customerName); const resultParams = {folderId: createFolderId}; const result = JSON.stringify(resultParams); return ContentService.createTextOutput(result); } const getAccessToken = (): string => { const endpoint = 'https://api.box.com/oauth2/token'; const payload = { client_id: BOX_CLIENT_ID, client_secret: BOX_CLIENT_SECRET, grant_type: 'client_credentials', box_subject_type: 'enterprise', box_subject_id: BOX_ENTERPRISE_ID }; const headers = { 'Content-Type': 'application/x-www-form-urlencoded' }; const response = UrlFetchApp.fetch(endpoint, { headers: headers, method: 'post', payload: payload }); const json = JSON.parse(response.getContentText()); return json.access_token; }; const copyFolder = (orderDate: string, customerName: string): string => { const endpoint = `https://api.box.com/2.0/folders/${BOX_TEMPLATE_FOLDER_ID}/copy`; const access_token = getAccessToken(); const headers = { 'Authorization': 'Bearer ' + access_token, "Content-type": "application/json", }; const payload = { name: `${orderDate}_${customerName}`, parent: { id: BOX_PARENT_FOLDER_ID } }; const response = UrlFetchApp.fetch(endpoint, { headers: headers, method: 'post', payload: JSON.stringify(payload) }); const json = JSON.parse(response.getContentText()); Logger.log(json.id); return json.id; };
以上です。
今回のやり方のベースになっている、以下のページに該当する日本語のページがありません。
Boxの開発ドキュメントは日本語のものが用意されており、 そちらが最初に表示されたので、別段気にしていなかったのですが、 クライアントクレデンシャルでのアクセストークン取得方法のページはなぜかガッツリ抜け落ちていました。
英語
日本語
ADMINは、他のアカウントへのスイッチもできるので、適当なタイミングで適切な人に権限を渡しましょう。
https://support.box.com/hc/ja/articles/360044194953/
本記事の例ではdoPostでレスポンスを返していますが、これを呼び出すAppSheetは現状レスポンスの中身を利用できないので、 あの部分はなくても問題ありません。
GASをデプロイし、外部からGASのスクリプトをcurlなどで叩いた場合、GCP連携していないとコード中に組み込んだログは表示されません。