Files
FrozenFriendsYume/Lua4Frozen.spec.md
T
2026-07-26 12:29:43 +09:00

20 KiB

Lua4Frozen 仕様

Lua4Frozen は、Iceshrimp/FrozenFriendsYume 上で Lua プログラムを投稿・検索・閲覧・実行するためのブラウザ内サンドボックス実行環境です。
この仕様書は、現行実装に存在する Lua4Frozen 本体と関連機能をまとめます。

1. 対象範囲

この文書に含める範囲は次の通りです。

  • Lua4Frozen 投稿の判定条件
  • Lua4Frozen 一覧・検索
  • ノート本文からの Lua4Frozen 導線
  • 投稿添付 .lua の実行画面
  • ブラウザ内テストランナー
  • Lua 実行環境とサンドボックス
  • ffy API
  • 添付ファイル、仮想ファイル、Drive 連携
  • 仮想キーボードとキーボード配列 JSON
  • エラー、ログ、セキュリティ制約

2. 画面とルート

Lua4Frozen には次のルートがあります。

ルート 機能
/lua4frozen Lua4Frozen 投稿一覧・検索
/lua4frozen/:noteId 指定ノートの Lua プログラム実行
/lua4frozen/spec アプリ内仕様説明ページ
/lua4frozen/test ブラウザ内テストランナー

ナビゲーションには Lua4Frozen 項目があり、トップ画面からテストランナーと仕様ページへ移動できます。

3. Lua4Frozen 投稿

ノートが Lua4Frozen 投稿として扱われる条件は次の通りです。

  1. ノートの可視性が public または home
  2. ノートのタグに lua4frozen が含まれる
  3. 添付ファイルに .lua 拡張子のファイルが 1 つ以上ある

タグ判定は小文字化されたタグ配列に対して行われます。
本文中の #Lua4Frozen はタグ化され、内部的には lua4frozen として扱われます。

タイトルと説明

Lua4Frozen 一覧と実行画面では、ノート本文を次のように扱います。

  • 1 行目: タイトル
  • 2 行目以降: 説明
  • 1 行目が空の場合: (no title)

説明は実行画面では MFM として表示されます。

4. ノートからの関連導線

通常のノート表示でも、次の条件を満たすと Lua4Frozen への導線が出ます。

  1. ノートにファイルが 1 つ以上ある
  2. 可視性が public または home
  3. タグに lua4frozen が含まれる
  4. .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 は以下の条件でノートを抽出します。

  • visibilitypublic または home
  • tagslua4frozen が含まれる
  • 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 ball
  • Keyboard and mouse
  • Files and audio

ローカル保存

テストランナーはブラウザの localStorage を使います。

キー 用途
lua4frozen:test:source テストランナーの下書き Lua ソース
lua4frozen:userKeyboards ユーザー定義キーボード配列

8. Lua 実行環境

Lua 実行には fengari を使います。
ブラウザ内で Lua state を作成し、安全な標準ライブラリだけを開きます。

開かれる標準ライブラリ

  • _G
  • coroutine
  • table
  • string
  • utf8
  • math

削除されるグローバル

安全性のため、次のグローバルは削除されます。

  • io
  • os
  • package
  • require
  • debug
  • dofile
  • loadfile

Lua から外部ネットワークへ直接通信する API は公開されません。

9. 実行ライフサイクル

実行ボタンを押すと、次の順に処理します。

  1. 既存の実行を停止
  2. Runtime error とログを初期化
  3. Drive 読み取り許可状態を unknown に戻す
  4. Canvas を初期化
  5. Lua state を作成
  6. 安全な標準ライブラリを開く
  7. 禁止グローバルを削除
  8. ffy API を登録
  9. Lua ソースを読み込み
  10. 初期化コードを実行
  11. on_updateon_draw の登録済み関数を取得
  12. requestAnimationFrame でフレームループを開始

フレーム処理

各フレームでは次を実行します。

  1. 前フレームからの経過秒数 dt を計算
  2. dt は最大 0.1 秒に丸める
  3. on_update 登録関数があれば dt を渡して呼ぶ
  4. on_draw 登録関数があれば呼ぶ
  5. エラーがあれば停止してログに出す

命令数制限

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

主なキー名例:

  • arrowleft
  • arrowright
  • arrowup
  • arrowdown
  • enter
  • escape
  • shift
  • control
  • alt
  • altgraph
  • meta
  • backspace
  • tab
  • delete

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")

表示前にサニタイズされます。
削除されるものは次の通りです。

  • script
  • iframe
  • object
  • embed
  • link
  • meta
  • form
  • on... イベント属性
  • 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 table
  • DELETE FROM table WHERE key = 'key'

19. 仮想キーボード

テストランナーには画面上の仮想キーボードがあります。
仮想キーボードは ffy.input.key の入力状態にも反映されます。

標準配列

現在の標準配列は次の通りです。

  • jis
  • us-intl
  • uk
  • azerty-fr
  • custom-jis
  • custom-ansi
  • custom-iso
  • custom-azerty

custom-* は編集用の空配列テンプレートです。

修飾キー

次のキーは修飾キーとして扱われます。

  • shift
  • control
  • alt
  • altgraph
  • meta

修飾キーはタップ回数で状態を切り替えます。

タップ回数 状態
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 はブラウザ内実行であり、以下の制約により危険な操作を抑制します。

  • ファイルシステムの直接アクセス不可
  • 任意モジュール読み込み不可
  • debug API 不可
  • 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. 実装ファイル

この仕様は主に以下の実装に対応しています。