Misskey Playで使うAiScriptのリファレンス

# 前書き
Misskey Playを作る時の参考にどうぞ。

- 公式ドキュメント

https://misskey-hub.net/ja/docs/for-developers/plugin/plugin-api-reference/

- 初めてPlayを作る人はこちらの解説が参考になります

https://zenn.dev/1step621/articles/a8eb3324ffad6a

- 細かい困り事やAiScript全般のことはこちらの記事をどうぞ

https://qiita.com/saki-lere/items/851c4500d56659d15c9c

- この記事は以下のソースコードの情報を元に作成しています

https://github.com/misskey-dev/misskey/blob/master/packages/frontend/src/scripts/aiscript/ui.ts

https://github.com/misskey-dev/misskey/blob/master/packages/frontend/src/scripts/aiscript/api.ts

# 実例
おおまかな作り方はPlay作成画面で使える公式プリセットや、他の人が作ったPlayのソースを参考にしてください。
## 公式プリセット
### Omikuji
ユーザーごとに日替わりのおみくじのプリセット
<details><summary>ソースコード</summary><div>

```typescript:Omikuji
/// @ 0.12.4
// ユーザーごとに日替わりのおみくじのプリセット

// 選択肢
let choices = [
	"ｷﾞｶﾞ吉"
	"大吉"
	"吉"
	"中吉"
	"小吉"
	"末吉"
	"凶"
	"大凶"
]

// シードが「ユーザーID+今日の日付」である乱数生成器を用意
let random = Math:gen_rng(`{USER_ID}{Date:year()}{Date:month()}{Date:day()}`)

// ランダムに選択肢を選ぶ
let chosen = choices[random(0 (choices.len - 1))]

// 結果のテキスト
let result = `今日のあなたの運勢は **{chosen}** です。`

// UIを表示
Ui:render([
	Ui:C:container({
		align: 'center'
		children: [
			Ui:C:mfm({ text: result })
			Ui:C:postFormButton({
				text: "投稿する"
				rounded: true
				primary: true
				form: {
					text: `{result}{Str:lf}{THIS_URL}`
				}
			})
		]
	})
])

```
</div></details>

### Shuffle
巻き戻し可能な文字シャッフルのプリセット
<details><summary>ソースコード</summary><div>

```typescript:Shuffle
/// @ 0.12.4
// 巻き戻し可能な文字シャッフルのプリセット

let string = "ペペロンチーノ"
let length = string.len

// 過去の結果を保存しておくやつ
var results = []

// どれだけ巻き戻しているか
var cursor = 0

@do() {
	if (cursor != 0) {
		results = results.slice(0 (cursor + 1))
		cursor = 0
	}

	let chars = []
	for (let i, length) {
		let r = Math:rnd(0 (length - 1))
		chars.push(string.pick(r))
	}
	let result = chars.join("")

	results.push(result)

	// UIを表示
	render(result)
}

@back() {
	cursor = cursor + 1
	let result = results[results.len - (cursor + 1)]
	render(result)
}

@forward() {
	cursor = cursor - 1
	let result = results[results.len - (cursor + 1)]
	render(result)
}

@render(result) {
	Ui:render([
		Ui:C:container({
			align: 'center'
			children: [
				Ui:C:mfm({ text: result })
				Ui:C:buttons({
					buttons: [{
						text: "←"
						disabled: !(results.len > 1 && (results.len - cursor) > 1)
						onClick: back
					} {
						text: "→"
						disabled: !(results.len > 1 && cursor > 0)
						onClick: forward
					} {
						text: "引き直す"
						onClick: do
					}]
				})
				Ui:C:postFormButton({
					text: "投稿する"
					rounded: true
					primary: true
					form: {
						text: `{result}{Str:lf}{THIS_URL}`
					}
				})
			]
		})
	])
}

do()
```
</div></details>

### Timeline viewer
APIリクエストを行いローカルタイムラインを表示するプリセット
<details><summary>ソースコード</summary><div>

```typescript:Timeline viewer
/// @ 0.12.4
// APIリクエストを行いローカルタイムラインを表示するプリセット

@fetch() {
	Ui:render([
		Ui:C:container({
			align: 'center'
			children: [
				Ui:C:text({ text: "読み込み中..." })
			]
		})
	])

	// タイムライン取得
	let notes = Mk:api("notes/local-timeline" {})

	// それぞれのノートごとにUI要素作成
	let noteEls = []
	each (let note, notes) {
		let el = Ui:C:container({
			bgColor: "#444"
			fgColor: "#fff"
			padding: 10
			rounded: true
			children: [
				Ui:C:mfm({
					text: note.user.name
					bold: true
				})
				Ui:C:mfm({
					text: note.text
				})
			]
		})
		noteEls.push(el)
	}

	// UIを表示
	Ui:render([
		Ui:C:text({ text: "ローカル タイムライン" })
		Ui:C:button({
			text: "更新"
			onClick: @() {
				fetch()
			}
		})
		Ui:C:container({
			children: noteEls
		})
	])
}

fetch()
```
</div></details>

## 応用
### 地理クイズ (by [@syuilo@misskey.io](https://misskey.io/@syuilo))

https://misskey.io/play/9bah2webe4

### 2048 (by [@madorama_vrc@misskey.io](https://misskey.io/@madorama_vrc))
パズルゲーム

https://misskey.io/play/9babihhe1j

### FlappyUsachan (by [@gozaru@misskey.io](https://misskey.io/@gozaru))
アクション要素のあるゲームも作れる。[制作者による解説](https://misskey.io/notes/9bja7nuhq1)

https://misskey.io/play/9bdogbc786

### サバイバルゲーム (by [@mugcAp@misskey.io](https://misskey.io/@mugcAp))
シミュレーション

https://misskey.io/play/9b3ug9hsty

### ガチャシミュレーター (by [@salano_ym@misskey.io](https://misskey.io/@salano_ym))
排出対象のデータはPageから取得します。

https://misskey.io/play/9e87xd4z9t

参考記事

https://qiita.com/the_quick_fox/items/17d03f2bfc5a56cf73f9

### ゲームのイベント予定表 (by [@salano_ym@misskey.io](https://misskey.io/@salano_ym))
Pageに保存したデータを取得して表示します。ウィジェット利用向けにコードのアップデートを通知することもできます。(fetch関数参照)

https://misskey.io/play/9oln16j25ruq043o

他にも参考になるものがあれば教えてください！

# UI操作

### `Ui:render`
`Ui:root.update({ children: [...] })`の糖衣構文
UIを1から表示するときはこれを使う
```typescript:UI全体を更新
Ui:render([
  Ui:C:text({text: "A"})
  Ui:C:button({text: "B", onClick: @(){}})
])
```
### `Ui:get`
コンポーネントをIDで取得。特定のコンポーネントのみ更新するときに使う
```typescript:text1の内容を更新
Ui:C:text({text: "A"}, "text1")
Ui:get("text1").update({text: "B"})
```

### `Ui:root`
UIのルート。あまり使わないかも
```typescript
Ui:root.update({
  children: [ // 中身のコンポーネントの配列
    Ui:C:text({text: "A"})
  ]
})
```
### `Ui:patch`
未実装っぽい

# UIコンポーネント
- コンポーネント定義の各要素は省略可能
- `component.id`でコンポーネントのIDを取得
- `component.update({...})`でコンポーネントを更新

**UIデモ**

https://misskey.io/play/9aki0ex85d

## 表示系
### `Ui:C:text`
MFMではない通常のテキスト。
```typescript
Ui:C:text({
  text: "内容" // 表示するテキスト
  size: 1 // 文字サイズ
  bold: false // ボールド
  color: '#000' // 色
  font: 'monospace' // フォント serif,sans-serif,monospace
})
```

### `Ui:C:mfm`
MFMテキスト。
```typescript
Ui:C:mfm({
  text: "内容" // 表示するテキスト
  size: 1 // 文字サイズ
  bold: false // ボールド
  color: '#000' // 色
  font: 'monospace' // フォント serif,sans-serif,monospace
  onClickEv: @(id) {
    <: `{id} clicked` // $[clickable.ev=SOMEID TEXT]
  }
})
```

## 入力系

### `Ui:C:button`
ボタン。
```typescript
var counter = 0

Ui:C:button({
  text: "ボタン" // 表示するテキスト
  onClick: @(){ counter += 1 } // 押したときのイベント
  primary: false // 色を付けて強調
  rounded: false // 角を丸く
  disabled: false // 無効化
})
```

### `Ui:C:buttons`
横並びの複数のボタン。
```typescript
Ui:C:buttons({
  buttons: [ // ボタン定義の配列 Ui:C:buttonと同じ
    {text: "a", onClick: @(){...}}
    {text: "b", onClick: @(){...}}
  ]
})
```

### `Ui:C:switch`
ON/OFFのスイッチ
```typescript
var func1_enabled = false

Ui:C:switch({
  onChange: @(enabled) { // 変更された時のイベント
    func1_enabled = enabled
  }
  default: false // デフォルト
  label: "ラベル"
  caption: "キャプション"
})
```

### `Ui:C:textarea`
複数行のテキスト入力。
```typescript
var the_text = ""

Ui:C:textarea({
  onInput: @(text){ the_text = text } // 入力された時のイベント
  default: "デフォルト"
  label: "ラベル"
  caption: "キャプション"
})
```

### `Ui:C:textInput`
1行のテキスト入力。
```typescript
var the_text = ""

Ui:C:textInput({
  onInput: @(text){ the_text = text } // 入力された時のイベント
  default: "デフォルト"
  label: "ラベル"
  caption: "キャプション"
})
```

### `Ui:C:numberInput`
数値入力。
```typescript
var the_number = 0

Ui:C:numberInput({
  onInput: @(number){ the_number = number } // 入力された時のイベント
  default: 0 // デフォルト
  label: "ラベル"
  caption: "キャプション"
})
```

### `Ui:C:select`
複数の選択肢から1つ選ぶ。
```typescript
var the_value = ""

Ui:C:select({
  items: [ // 選択肢の配列
    {text: "A", value: "v1"}
    {text: "B", value: "v2"}
  ]
  onChange: @(value){ the_value = value } // 変更された時のイベント
  default: "v1" // デフォルト
  label: "ラベル"
  caption: "キャプション"
})
```

## コンテナ系

### `Ui:C:container`
幅寄せしたり色を付けたりする
```typescript
Ui:C:container({
  children: [Ui:C:text({text: "A"})] // 中身のコンポーネントの配列
  align: 'center' // 幅寄せ left,center,right
  bgColor: '#000' // 背景色
  fgColor: '#00f' // 文字色
  font: 'serif' // フォント serif,sans-serif,monospace
  borderWidth: 1 // 枠幅
  borderColor: '#f00' // 枠の色
  padding: 1
  rounded: false // 角を丸く
  hidden: false // 隠す
})
```

### `Ui:C:folder`
折りたためるフォルダ
```typescript
Ui:C:folder({
  children: [Ui:C:text({text: "A"})] // 中身のコンポーネントの配列
  title: "タイトル"
  opened: true // 開いているか
})
```

## ノート投稿

### `Ui:C:postForm`
Misskeyの投稿フォーム。(`Misskey2023.9.0`から)
```typescript
Ui:C:postForm({
  form: {text: "投稿内容"} // 投稿フォームのデフォルト文字列
})
```

### `Ui:C:postFormButton`
Misskeyの投稿フォームをポップアップ表示するボタン。
```typescript
Ui:C:postFormButton({
  text: "投稿！" // 表示するテキスト
  primary: true // 色を付けて強調
  rounded: true // 角を丸く
  form: {text: "投稿内容"} // 投稿フォームのデフォルト文字列
})
```

# ダイアログ
### `Mk:dialog`
第3引数は`info`,`success`,`warning`,`error`,`question`が指定可能。省略すると`info`
```typescript
Mk:dialog("エラー！", "エラーが発生しました", "info")
```
### `Mk:confirm`
ユーザーが`OK`を押すと`true`、`キャンセル`を押すと`false`が返る。
```typescript
let ok = Mk:confirm("確認", "実行しますか？", "info")
```
### `readline`
Misskey上では文字列入力のダイアログが表示される。
```typescript
let name = readline('名前を入力してください')
```

# セーブ・ロード
ローカルに保存される。
### `Mk:save`
```typescript: cookie_countに9999を保存
Mk:save("cookie_count", 9999)
```
### `Mk:load`
```typescript: cookie_countの値を取得
let cookie_count = Mk:load("cookie_count")
```

# Misskey API
### `Mk:api`
`Mk:api(ENDPOINT, PARAMETER, TOKEN)`
[APIドキュメント](https://misskey-hub.net/docs/api/endpoints.html)
```typescript:ローカルTLを取得
Mk:api("notes/local-timeline", {})
```