20 KiB
Lua4Frozen 仕様
Lua4Frozen は、Iceshrimp/FrozenFriendsYume 上で Lua プログラムを投稿・検索・閲覧・実行するためのブラウザ内サンドボックス実行環境です。
この仕様書は、現行実装に存在する Lua4Frozen 本体と関連機能をまとめます。
1. 対象範囲
この文書に含める範囲は次の通りです。
- Lua4Frozen 投稿の判定条件
- Lua4Frozen 一覧・検索
- ノート本文からの Lua4Frozen 導線
- 投稿添付
.luaの実行画面 - ブラウザ内テストランナー
- Lua 実行環境とサンドボックス
ffyAPI- 添付ファイル、仮想ファイル、Drive 連携
- 仮想キーボードとキーボード配列 JSON
- エラー、ログ、セキュリティ制約
2. 画面とルート
Lua4Frozen には次のルートがあります。
| ルート | 機能 |
|---|---|
/lua4frozen |
Lua4Frozen 投稿一覧・検索 |
/lua4frozen/:noteId |
指定ノートの Lua プログラム実行 |
/lua4frozen/spec |
アプリ内仕様説明ページ |
/lua4frozen/test |
ブラウザ内テストランナー |
ナビゲーションには Lua4Frozen 項目があり、トップ画面からテストランナーと仕様ページへ移動できます。
3. Lua4Frozen 投稿
ノートが Lua4Frozen 投稿として扱われる条件は次の通りです。
- ノートの可視性が
publicまたはhome - ノートのタグに
lua4frozenが含まれる - 添付ファイルに
.lua拡張子のファイルが 1 つ以上ある
タグ判定は小文字化されたタグ配列に対して行われます。
本文中の #Lua4Frozen はタグ化され、内部的には lua4frozen として扱われます。
タイトルと説明
Lua4Frozen 一覧と実行画面では、ノート本文を次のように扱います。
- 1 行目: タイトル
- 2 行目以降: 説明
- 1 行目が空の場合:
(no title)
説明は実行画面では MFM として表示されます。
4. ノートからの関連導線
通常のノート表示でも、次の条件を満たすと Lua4Frozen への導線が出ます。
- ノートにファイルが 1 つ以上ある
- 可視性が
publicまたはhome - タグに
lua4frozenが含まれる .lua拡張子の添付ファイルがある
この場合、メディアサービス用リンクのラベルは Lua4Frozen になり、遷移先は /lua4frozen/:noteId です。
5. 一覧・検索 API
Lua4Frozen 一覧は notes/lua4frozen-search を使います。
エンドポイント
notes/lua4frozen-search
認証
- 通常時: 認証不要
- Private mode 時: 認証が必要
パラメータ
| 名前 | 型 | 既定値 | 制約 |
|---|---|---|---|
query |
string | "" |
前後空白は除去 |
limit |
integer | 20 |
1 以上 50 以下 |
offset |
integer | 0 |
0 以上 |
検索条件
検索対象は次の通りです。
- ノート本文の部分一致
- 投稿者の
usernameLowerの部分一致
抽出条件
API は以下の条件でノートを抽出します。
visibilityがpublicまたはhometagsにlua4frozenが含まれるfileIdsが空でない- pack 後に
.luaファイルを持つものだけ残す
並び順は createdAt DESC です。
6. 投稿実行画面
/lua4frozen/:noteId は指定ノートを取得し、Lua4Frozen として実行します。
読み込み時の検証
実行画面は次を検証します。
- ノートが存在すること
- タグに
lua4frozenが含まれること - 添付ファイルに
.luaファイルが 1 つ以上あること
条件を満たさない場合はエラー表示になります。
Lua ファイル選択
.lua ファイルが複数添付されている場合、実行画面の Lua file セレクトで実行対象を選べます。
選択された .lua の内容を取得して実行します。
添付ファイルの事前読み込み
実行画面では、添付ファイルのうち次の条件を満たすものをテキストとして事前読み込みします。
- サイズが
512 KiB以下 - 拡張子が
.lua,.txt,.json,.csv
読み込めない添付ファイルは無視されます。
7. テストランナー
/lua4frozen/test は投稿なしで Lua4Frozen を実行する開発・確認用画面です。
主な機能
- Lua ソースの編集
- サンプルコード読み込み
- 実行・停止
- 実行ログ表示
- Runtime error 表示
- Canvas 表示
- HTML 出力表示
- 仮想ファイルの追加、編集、削除
- 仮想キーボードの利用
- ユーザー定義キーボードの保存、削除
- プログラムからのキーボード配列登録
.luaとしてダウンロード- ブラウザ内下書き保存
サンプル
現在の実装では次のサンプルがあります。
Bouncing ballKeyboard and mouseFiles and audio
ローカル保存
テストランナーはブラウザの localStorage を使います。
| キー | 用途 |
|---|---|
lua4frozen:test:source |
テストランナーの下書き Lua ソース |
lua4frozen:userKeyboards |
ユーザー定義キーボード配列 |
8. Lua 実行環境
Lua 実行には fengari を使います。
ブラウザ内で Lua state を作成し、安全な標準ライブラリだけを開きます。
開かれる標準ライブラリ
_Gcoroutinetablestringutf8math
削除されるグローバル
安全性のため、次のグローバルは削除されます。
ioospackagerequiredebugdofileloadfile
Lua から外部ネットワークへ直接通信する API は公開されません。
9. 実行ライフサイクル
実行ボタンを押すと、次の順に処理します。
- 既存の実行を停止
- Runtime error とログを初期化
- Drive 読み取り許可状態を
unknownに戻す - Canvas を初期化
- Lua state を作成
- 安全な標準ライブラリを開く
- 禁止グローバルを削除
ffyAPI を登録- Lua ソースを読み込み
- 初期化コードを実行
on_updateとon_drawの登録済み関数を取得requestAnimationFrameでフレームループを開始
フレーム処理
各フレームでは次を実行します。
- 前フレームからの経過秒数
dtを計算 dtは最大0.1秒に丸めるon_update登録関数があればdtを渡して呼ぶon_draw登録関数があれば呼ぶ- エラーがあれば停止してログに出す
命令数制限
Lua の実行には hook による命令数制限があります。
| 処理 | 制限 |
|---|---|
| 初期化コード | 250000 命令相当 |
on_update |
40000 命令相当 |
on_draw |
40000 命令相当 |
制限を超えると Lua instruction limit exceeded で停止します。
10. グローバル関数
print(...)
Lua の print は Lua4Frozen のログへ出力されます。
複数引数はタブ区切りで結合されます。
同じ関数は ffy.print(...) としても利用できます。
11. ffy API
Lua4Frozen はグローバルに ffy テーブルを公開します。
ffy.on_update(fn)
毎フレームの更新関数を登録します。
関数でない値を渡した場合は無視されます。
function update(dt)
-- dt is seconds
end
ffy.on_update(update)
ffy.on_draw(fn)
毎フレームの描画関数を登録します。
関数でない値を渡した場合は無視されます。
function draw()
ffy.gfx.clear("#111827")
end
ffy.on_draw(draw)
12. ffy.gfx
Canvas 描画 API です。Canvas の内部サイズは 800 x 450 です。
ffy.gfx.size()
Canvas の幅と高さを返します。
w, h = ffy.gfx.size()
ffy.gfx.color(color)
現在の塗り色と線色を設定します。
color は CanvasRenderingContext2D の色指定として解釈されます。
ffy.gfx.color("#7dd3fc")
ffy.gfx.clear(color?)
Canvas を消去します。
color が指定されている場合は、消去後に全体をその色で塗ります。
ffy.gfx.clear("#111827")
ffy.gfx.clear()
ffy.gfx.rect(x, y, w, h, fill?)
矩形を描きます。
fill が省略または真なら塗り、偽なら線だけです。
ffy.gfx.rect(40, 40, 160, 80)
ffy.gfx.rect(40, 40, 160, 80, false)
ffy.gfx.circle(x, y, r, fill?)
円を描きます。
fill が省略または真なら塗り、偽なら線だけです。
ffy.gfx.circle(120, 90, 24)
ffy.gfx.circle(120, 90, 24, false)
ffy.gfx.line(x1, y1, x2, y2)
線を描きます。
ffy.gfx.line(10, 10, 300, 120)
ffy.gfx.text(text, x, y, size?)
テキストを描きます。
size が未指定の場合は 18px です。フォントは sans-serif です。
ffy.gfx.text("Hello", 40, 80, 24)
12.5. ffy.svg
SVG ベクタ表示 API です。SVG レイヤーは Canvas の 1 つ上に重なります。Canvas 描画機能はそのまま利用でき、SVG レイヤーだけを差し替えまたは消去できます。
ffy.svg.set(markup)
SVG 全体、または SVG 要素断片を表示します。断片を渡した場合は viewBox="0 0 800 450" の <svg> に包まれます。
危険なタグ、イベント属性、外部 URL 参照、javascript: 参照は除去されます。
ffy.svg.set('<circle cx="400" cy="225" r="80" fill="#f472b6" opacity="0.75" />')
ffy.svg.clear()
SVG レイヤーを消去します。
ffy.svg.clear()
13. ffy.input
キーボードとマウスの状態を取得します。
Canvas にフォーカスがある間、実キーボード入力が反映されます。テストランナーでは仮想キーボード入力も反映されます。
ffy.input.key(name)
指定キーが押されているかを返します。
キー名は小文字化して比較されます。
if ffy.input.key("arrowleft") then
x = x - 2
end
主なキー名例:
arrowleftarrowrightarrowuparrowdownenterescapeshiftcontrolaltaltgraphmetabackspacetabdelete
ffy.input.mouse()
マウス状態のテーブルを返します。
| フィールド | 型 | 内容 |
|---|---|---|
x |
number | Canvas 内 X 座標 |
y |
number | Canvas 内 Y 座標 |
down |
boolean | マウスボタンが押されているか |
button |
number | MouseEvent の button 値 |
座標は表示サイズではなく 800 x 450 の Canvas 座標へ変換されます。
14. ffy.html
Canvas 下の HTML 出力領域を操作します。
ffy.html.set(html)
HTML 文字列を表示します。
ffy.html.set("<b>Status</b> Ready")
表示前にサニタイズされます。
削除されるものは次の通りです。
scriptiframeobjectembedlinkmetaformon...イベント属性http://,https://,//で始まるsrcまたはhref
15. ffy.audio
ffy.audio.beep(frequency?, duration?)
ビープ音を鳴らします。
| 引数 | 既定値 | 内容 |
|---|---|---|
frequency |
440 |
周波数 Hz |
duration |
0.12 |
秒数 |
音量は固定で小さめに設定されます。
duration は最大 2 秒に丸められます。
ブラウザの自動再生制限により、ユーザー操作なしでは鳴らない場合があります。
16. ffy.fs
Lua4Frozen のファイル API です。
ffy.fs.read(name)
指定名のファイル内容を文字列で返します。
存在しない場合は空文字列を返します。
投稿実行画面では、添付ファイルと許可済み Drive ファイルが対象です。
テストランナーでは、仮想ファイルと許可済み Drive ファイルが対象です。
ffy.fs.write(name, content)
Drive への保存を試みます。
保存前に確認ダイアログが表示されます。未ログインの場合は保存されません。
保存ファイル名は次の処理を受けます。
\ / : * ? " < > |を_に置換- 最大 120 文字に切り詰め
- 空の場合は
lua4frozen.txt - MIME type は
text/plain
.jsondb, .ffdb, .db.json は Lua4Frozen データベースファイルとして扱われます。
データベースファイルは Drive ファイルとして保存されますが、Drive 容量ではなく Lua4Frozen データベース容量を使用します。
ffy.fs.list()
見えているファイル名を Lua 配列として返します。
投稿実行画面では添付ファイル名と Drive ファイル名です。
テストランナーでは仮想ファイル名と Drive ファイル名です。
17. Drive 連携
Drive 読み取りは初回 ffy.fs.read 時に必要に応じて確認されます。
実行 1 回ごとに許可状態は初期化されます。
読み取り対象
Drive 読み取り許可後、最大 100 件の Drive ファイル一覧を取得します。
読み取り対象は次の条件を満たすものです。
- サイズが
512 KiB以下 - 拡張子が
.lua,.txt,.json,.csv,.jsondb,.ffdb,.db.json
読み取りに失敗したファイルは無視されます。
書き込み対象
書き込みは Drive へアップロードされます。
ffy.fs.write はメモリ上の Drive ファイル map も更新するため、同じ実行中に ffy.fs.read で読み返せます。
データベース容量
Lua4Frozen データベースファイルは、管理者がコントロールパネルで設定した Lua4Frozen データベース容量を使用します。
容量を超えた場合、新規書き込みは拒否されます。Drive 上の古いデータベースファイルを削除して空きを作ると、再び書き込めます。
18. ffy.db / ffy.sql
Lua4Frozen データベースファイルは JSON 形式の単純な key-value データベースとして扱われます。
ffy.db.get(name, key)
指定した DB ファイルから値を取得します。存在しない場合は空文字列を返します。
ffy.db.set(name, key, value)
指定した DB ファイルへ値を書き込みます。name に DB 拡張子がない場合は .jsondb が補われます。
ffy.db.delete(name, key)
指定したキーを削除します。
ffy.db.list(name)
指定した DB ファイルのキー一覧を Lua 配列として返します。
ffy.sql.exec(name, sql)
簡易 SQL を実行します。対応する構文は次の通りです。
CREATE TABLE ...INSERT INTO table (key, value) VALUES ('key', 'value')SELECT value FROM table WHERE key = 'key'SELECT * FROM tableDELETE FROM table WHERE key = 'key'
19. 仮想キーボード
テストランナーには画面上の仮想キーボードがあります。
仮想キーボードは ffy.input.key の入力状態にも反映されます。
標準配列
現在の標準配列は次の通りです。
jisus-intlukazerty-frcustom-jiscustom-ansicustom-isocustom-azerty
custom-* は編集用の空配列テンプレートです。
修飾キー
次のキーは修飾キーとして扱われます。
shiftcontrolaltaltgraphmeta
修飾キーはタップ回数で状態を切り替えます。
| タップ回数 | 状態 |
|---|---|
| 0 | off |
| 1 | latched |
| 2 | locked |
通常キーを押すと、latched 状態の修飾キーは消費されます。
テキスト挿入
仮想キーに text があり、フォーカス中の要素がテストランナーの Lua エディタである場合、その文字列をエディタへ挿入します。
20. キーボード配列 JSON
キーボード配列は JSON で定義します。
レイアウト形式
{
"id": "my-layout",
"name": "My Layout",
"cols": 16,
"rows": [
["Esc", "1", "2", { "label": "Backspace", "w": 2 }],
[{ "label": "Tab", "w": 2 }, "Q", "W", "E"],
[{ "label": "Shift", "w": 2 }, "Z", "X", "C"]
]
}
レイアウトフィールド
| フィールド | 型 | 内容 |
|---|---|---|
id |
string | 配列 ID |
name |
string | 表示名 |
cols |
number | グリッド列数。1 から 24 に丸められる |
rows |
array | キー行の配列 |
keys |
array | 旧形式。rows がない場合に cols ごとに行へ変換 |
キーフィールド
キーは文字列またはオブジェクトで指定できます。
| フィールド | 型 | 内容 |
|---|---|---|
label |
string | 表示ラベル |
key |
string | ffy.input.key 用のキー名 |
text |
string | エディタへ挿入する文字 |
w |
number | 横幅。1 から 6 に丸められる |
h |
number | 高さ。0.5 から 4 に丸められる |
key が未指定の場合は、label からキー名が推定されます。
text が未指定かつ label が 1 文字の場合は、その文字が text になります。
20. ffy.keyboard
テストランナーでは、Lua プログラムから仮想キーボードを操作できます。
投稿実行画面には現在この API はありません。
ffy.keyboard.set_layout_json(name, json)
プログラム由来のキーボード配列を登録し、選択します。
layout = [[
{
"id": "gamepad",
"name": "Gamepad",
"cols": 8,
"rows": [
["ArrowUp"],
["ArrowLeft", "ArrowDown", "ArrowRight"]
]
}
]]
ffy.keyboard.set_layout_json("Gamepad", layout)
登録された配列の ID は内部的に program- が付与されます。
ffy.keyboard.select(id)
既存のキーボード配列 ID を選択します。
ffy.keyboard.select("jis")
存在しない ID を指定した場合は何もしません。
21. ログ
ログは画面上の Log に表示されます。
最大 200 行まで保持し、超えた分は古い行から削除されます。
ログに出る主な内容は次の通りです。
print/ffy.printの出力- Runtime error
- Drive 読み取り許可・拒否
- Drive 書き込み許可・拒否・失敗
- 下書き保存
- キーボード配列保存・読み込み結果
22. エラー処理
Lua の読み込み、初期実行、フレーム処理でエラーが起きると、Runtime error 表示とログ出力を行います。
フレーム処理中のエラーでは実行を停止します。
23. セキュリティ仕様
Lua4Frozen はブラウザ内実行であり、以下の制約により危険な操作を抑制します。
- ファイルシステムの直接アクセス不可
- 任意モジュール読み込み不可
debugAPI 不可- OS コマンド実行不可
- Lua からの直接ネットワーク不可
- Drive 読み取りはユーザー確認が必要
- Drive 書き込みは保存ごとにユーザー確認が必要
- HTML 出力はサニタイズされる
- Lua 命令数制限がある
24. 制限事項
現行仕様上の制限は次の通りです。
- Canvas は 2D のみ
- Canvas 内部サイズは
800 x 450 - 画像読み込み API はない
- 外部 URL 読み込み API はない
- 永続ストレージは Drive 書き込みまたはテストランナーの
localStorageに限られる - 投稿実行画面では仮想キーボード API は使えない
- 添付ファイルの事前読み込みはテキスト系拡張子かつ
512 KiB以下のみ - Drive 読み取りは同期 XHR を使うため、実行時に一時的に UI が止まる可能性がある
25. 実装ファイル
この仕様は主に以下の実装に対応しています。