scrapbox-userscript-std/browser/websocket
server-sideからwebsocketを通じてscrapboxへ書き込む
使い方を整理したnishio.icon
https://github.com/nishio/write_cosense/blob/main/README.md
しごとがはやいtakker.icon
ワタシCosenseチョットデキル
挿入位置計算間違えてた()takker.icon
直そう
直した
TEST HERE
ワタシCosenseチョットデキル:2025-01-23T13:25:00.610Z
ワタシCosenseチョットデキル1737638503707
ワタシCosenseチョットデキル
code:sample.ts
import { insertLines } from "./insertLines.ts";
await insertLines(
"villagepump",
"scrapbox-userscript-std/browser/websocket",
"TEST HERE",
" ワタシCosenseチョットデキル:" + new Date().toISOString(),
{ sid: Deno.env.get("CONNECT_SID")! },
);
nishio.icon
若干カオスになってるけどw とりあえず
% export CONNECT_SID="<your sid>"
% deno run -A -r=https://scrapbox.io https://scrapbox.io/api/code/villagepump/scrapbox-userscript-std%2Fbrowser%2Fwebsocket/sample.ts
でscrapboxに書き込まれることまでは確認された
これはダメnishio.icon % deno run -A -r=https://scrapbox.io https://scrapbox.io/api/code/villagepump/scrapbox-userscript-std/browser/websocket/sample.ts
scrapbox-userscript-std/browser/websocketがページ名ではなく、3つの階層として認識されてしまうためtakker.icon
/を階層の区切りとして認識されないよう、%2Fに変えておく必要がある
/emoji/tada.icontakker.icon
正しい位置に表示された!/emoji/tada.icon/emoji/tada.iconnishio.icon
やったー、Botが日記に書き込んだぞ!
/nishio/日記2025-01-23#679245be6a7a3f00000087dd86
わーいわーいtakker.icon
Cosense Code Cloneのpush機能をこの方式で実装できないかなbsahd.icon
helper関数として、insertLinesやupdateLineあたりを作っておくと便利そうかなtakker.icon
箇条書きで
A
B
C
のA,BがあるときにAを指示してCの位置に追加するものがあるとCosense的には自然な気がしたnishio.icon
「Aの下に追加」だとA,Cがある状態でBに書いちゃうので
やってみようtakker.icon
% git clone https://github.com/takker99/scrapbox-userscript-std
Roo-Clineに質問するnishio.icon
特定のページの特定の内容の行の下に行を追加したいnishio.icon
(Roo)
/websocketディレクトリ内で、行を追加するための操作に関連するコードを見つけました。特に、applyCommit.tsやupdateCodeFile.tsなどのファイルに、行を挿入するためのロジックが含まれています。これらのファイルを詳しく調べることで、特定のページに行を追加する方法を理解できるかもしれません。
次に、applyCommit.tsファイルを詳しく調べて、行を追加する具体的な方法を確認します。
applyCommit.tsファイルには、Scrapboxページの行に対してコミット(変更)を適用するための関数applyCommitが定義されています。この関数を使用することで、特定のページの特定の内容の行の下に新しい行を追加することが可能です。具体的には、changes配列に_insertプロパティを持つオブジェクトを追加し、挿入したい位置または_endを指定することで、新しい行を追加できます。
(takker)
行を追加するための操作には、jsr:@cosense/std/websocketからexportされている関数patchが使えます。
これは、指定したページの全文を指定したものに一括で書き換える関数です。scrapboxページの行に対してのコミットを直接作成するのは煩雑なため、代わりに変更後のページの内容を指定するだけで変更できるよう設計されています。
この設計は宣言的UIの考え方を下敷きにしています
React Componentが描画してほしい内容を返すと同様、patchには書き換え後のページの内容を渡すのです
実際の書き換えは、Reactと同様、内部で差分だけを抽出し、最小限のcommitで更新されるよう処理しています
続いて、このpatchを使って、特定のページの特定の行の下に指定した文字列を挿入する関数insertLinesを実装します。
code:insertLines.ts
// Supress errors related to XMLHttpRequest
import type {} from "jsr:/@kitsonk/xhr@0.5/globals";
import { patch, type PatchOptions } from "jsr:@cosense/std@0.29/websocket";
export const insertLines = (
project: string,
title: string,
targetLineText: string,
text: string,
options?: PatchOptions,
) => patch(
project,
title,
(lines_) => {
let index = lines_.findIndex(
(line) => line.text === targetLineText
);
index = index < 0 ? lines_.length : index;
const lines = lines_.map((line) => line.text);
return [
...lines.slice(0,index + 1),
...text.split("\n"),
...lines.slice(index + 1),
];
},
options,
);
この関数は、/project/title内で、targetLineTextに完全一致する行の下にtextを挿入します。targetLineTextが見つからなかったときは、末尾に挿入します。
patchにはproject,title,更新関数, optionsの順に引数を渡します
更新関数では、書き換え後のページの内容をstring[]で返します
string[]を直接渡すのではなく、関数を介して渡しているのは次の理由からです
patchを実行時したときのページの内容をもとに新しいページの中身を作るという操作をしやすくするため
これはinsertLinesの実装からも推測できるでしょう
書き換えに失敗したとき再試行できるようにするため
単純なネットワークエラーの他、patchを実行中に他のユーザが新しいコミットを加えたとき、すでに存在するページとタイトルが被ってしまったときなどに再度実行されます
第2引数に再試行の理由が渡されるので、それをもとに書き換え処理を変えることができます。
この関数の利用例として、scrapbox-userscript-std/browser/websocketのTEST HEREという行の下にワタシCosenseチョットデキルをインデントを下げて挿入するコードを紹介します
code:main-takker.ts
import { insertLines } from "./insertLines.ts";
await insertLines(
"villagepump",
"scrapbox-userscript-std%2Fbrowser%2Fwebsocket",
"scrapbox-userscript-std%2Fbrowser%2Fwebsocket"じゃなくて"scrapbox-userscript-std/browser/websocket"で問題なかったの忘れてたtakker.icon
percent encodingはpatchがよしなに処理してくれる
"scrapbox-userscript-std/browser/websocket"に変えれば、このページにちゃんと書き込まれます
(変に書き換えるとまずいかと思ってそのままにしてた)
code:main-takker.ts
"TEST HERE",
" ワタシCosenseチョットデキル",
{ sid: Deno.env.get("CONNECT_SID")! },
);
このコードは次のコマンドで実行できるかもしれません
$ deno run -A -r=https://scrapbox.io https://scrapbox.io/api/code/villagepump/scrapbox-userscript-std%2Fbrowser%2Fwebsocket/main-takker.ts
実行環境がないため、試すことができません。
静的検査は
$ deno check --remote -r=https://scrapbox.io https://scrapbox.io/api/code/villagepump/scrapbox-userscript-std%2Fbrowser%2Fwebsocket/main-takker.ts
でできます。
code:error
error: TS2559 ERROR: Type '"{ sid: Deno.env.get(\"CONNECT_SID\") }"' has no properties in common with type 'PushOptions'.
{ sid: Deno.env.get("CONNECT_SID") },
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
at https://scrapbox.io/api/code/villagepump/scrapbox-userscript-std%2Fbrowser%2Fwebsocket/main-takker.ts:8:3
ふーむnishio.icon
sid?: string;か
sid: Deno.env.get("CONNECT_SID")!にかえてみた
そっか!が必要だったtakker.icon
code:error
% deno check --remote -r=https://scrapbox.io https://scrapbox.io/api/code/villagepump/scrapbox-userscript-std%2Fbrowser%2Fwebsocket/main-takker.ts
error: Integrity check failed for remote specifier. The source code is invalid, as it does not match the expected hash in the lock file.
Specifier: https://scrapbox.io/api/code/villagepump/scrapbox-userscript-std%2Fbrowser%2Fwebsocket/main-takker.ts
Actual: a528530e94f8b0e7576759c33e9105e044c0415658cb5869186e4f120be43b74
Expected: c2e6a834a5d567fd94c0c0bfbbf6edc0a2a9d88073d3936e8e432fc527968ddf
This could be caused by:
* the lock file may be corrupt
* the source itself may be corrupt
Investigate the lockfile; delete it to regenerate the lockfile or --reload to reload the source code from the server.
それが出たかーtakker.icon
lock fileめんどい
--no-lockとかいうoptionがあったはず。それ渡せば回避できるはずです
もしくはdeno.lockを削除してください
code:error
Warning Workspace member '@cosense/std@0.0.0' was not used because it did not match '@cosense/std@0.29'
at https://scrapbox.io/api/code/villagepump/scrapbox-userscript-std%2Fbrowser%2Fwebsocket/insertLines.ts:1:42
Check https://scrapbox.io/api/code/villagepump/scrapbox-userscript-std/browser/websocket/main-takker.ts
error: TS2559 ERROR: Type '"{ sid: Deno.env.get(\"CONNECT_SID\")! }"' has no properties in common with type 'PushOptions'.
{ sid: Deno.env.get("CONNECT_SID")! },
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
at https://scrapbox.io/api/code/villagepump/scrapbox-userscript-std%2Fbrowser%2Fwebsocket/main-takker.ts:8:3
!をつけてもダメみたいnishio.icon
repoのdeno.jsoncの設定と衝突してますねtakker.icon
cloneしたrepoの配下でない、適当なdirectoryに移動して、再度実行してください
あー、なるほどnishio.icon
repoの外に出たらXMLHttpRequestが見つからなくなった
code:error
Check https://scrapbox.io/api/code/villagepump/scrapbox-userscript-std/browser/websocket/main-takker.ts
error: TS2304 ERROR: Cannot find name 'XMLHttpRequest'.
constructor(createRequest: (opts: RequestOptions) => XMLHttpRequest, uri: string, opts: RequestOptions);
~~~~~~~~~~~~~~
at file:///Users/nishio/Library/Caches/deno/npm/registry.npmjs.org/engine.io-client/6.6.3/build/esm/transports/polling-xhr.d.ts:65:58
えっなんでそのエラーがでるのtakker.icon
これはtakker.iconのコードミスの可能性高いな……
socket.io-clientのoptionsをミスってる
いや、型エラー出てるだけかも
XMLHttpRequestは使わないはずなので、無視して実行しても問題ないはずですtakker.icon
書き込みに成功しているのはさっき確認しましたし
もちろんエラーが出るのは気持ち悪いので、後日対策考えます
scrapbox-userscript-std側で対処するのは難しそうtakker.icon
XMLHttpRequestの型エラーが出るのは、server-sideでコードを動かしたときのみ
もしlibrary側で一律にXMLHttpRequestの型定義を入れてしまうと、browser向けコードを書くとき、二重定義エラーとなってしまう
ユーザー側で対処するしかない
幸い、 https://jsr.io/@kitsonk/xhr/doc/globals を入れればエラーを抑制できそう
insertLines.tsに入れておきます
2025-01-24 09:30:55 だめだった。効果ない
type errorはtakker.iconのコードのミスっぽいtakker.icon
調べます
いや、まちがってないな……sid?: stringだし
code:ts
export interface BaseOptions {
sid?: string;
ってなってるけど...
Deno.env.get()の型定義をみてみますtakker.icon
いや問題ないな。Deno.env.get()の戻り値はstring | undefinedだからあってる
そこ自信ないんだけど interface {x?: string} と {x: string | undefined}は別じゃない?nishio.icon
ていうかTS2559 [ERROR]: Type '"{ sid: Deno.env.get(\"CONNECT_SID\")! }"' has no properties in common with type 'PushOptions'.
これstring literal型では
back quote外したら直ったw
間違えて\`\`で囲んでたのか〜takker.icon
気づかなかった
code::
% deno run -A -r=https://scrapbox.io https://scrapbox.io/api/code/villagepump/scrapbox-userscript-std%2Fbrowser%2Fwebsocket/main-takker.ts
Warning: Not implemented: ClientRequest.options.createConnection
エラーなく完了したが書き込まれてはいなそうnishio.icon
scrapbox-userscript-std/browser/websocket#679240b11280f00000480377takker.icon
{ sid: Deno.env.get("CONNECT_SID")! },
注意点として、connect.sidを環境変数CONNECT_SIDに与える必要があります
与え方は一般的なshellscriptと同じです
.envファイルも使えます
方法は公式docsを参照ください
TEST HERE
Roo Clineが生成したサンプル
(deleted)
これはどうやって実行したらいい?nishio.icon
(Roo)
websocket/scrapboxApiSample.tsを実行するには、Denoを使用します。以下の手順で実行できます。
Denoがインストールされていない場合は、Denoの公式サイトからインストールしてください。
ターミナルを開き、websocket/scrapboxApiSample.tsがあるディレクトリに移動します。
次のコマンドを実行してスクリプトを実行します:
$ deno run --allow-net=api.scrapbox.io websocket/scrapboxApiSample.ts
このコマンドは、ネットワークアクセスを許可し、Scrapbox APIにアクセスしてページを更新します。
エラーになった
Response status: 403
Response text: {"name":"SessionError","message":"Your session has been expired. Please reload. (CSRF Token does not match)"}
Error: Error: Failed to update the page
コードを読んだらapplyCommitを使ってないじゃん??
そこを突っ込んだら書き直してくれた
Error: SyntaxError: "newLine" is an invalid id.
validなIDをつけないとダメそう
一ポモドーロでできるようにならなかったから保留nishio.icon
validなline idを生成する方法がコードのどこかにあるはずですnishio.icon
(Roo)websocket/id.tsファイルに、createNewLineIdという関数が定義されています。この関数を使用して、有効な行IDを生成できます。
なるほど!
Error: SyntaxError: "67922949user0000009671aa" is an invalid id.
ダメじゃん...
createNewLineIdに渡すべきuserIdの取得方法があるはずですnishio.icon
(Roo)websocket/pull.tsファイルにgetUserId関数が定義されています。この関数を使用して、現在のユーザーIDを取得できます。この関数をwebsocket/scrapboxApiSample.tsに組み込んで、ユーザーIDを取得するようにコードを修正します。
error: Uncaught SyntaxError: The requested module './pull.ts' does not provide an export named 'getUserId'
泥沼だ!nishio.icon
わかりやすいdocs用意してなくてマジごめんなさい……takker.icon
takker.icon
jsr: specifierでimportしなければならない
ん?scrapbox-userscript-stdをlocalにcloneして書いているのか
それならパスは通るか
applyCommitやcreateNewLineIdはprivate functionsで、外部から使うことは想定していない
だからjsr経由ではimportできないようにしてある
今回はrepoをcloneして使ってしまっているため、制限を突破して使えてしまっている
APIの使い方が誤っている
api/pages/:projectname/:pagetitleにPOSTしても書き換えられない
上記の内容を再度Roo Clineに与えてみるnishio.icon