Misskeyのウィジェット(AiScript App)やプラグインなどで使う定数はページに保存すると便利

# まえがき
今回の記事は"[Pageのデータを取得@AiScriptで困ったときに見るメモ - Qiita](https://qiita.com/saki-lere/items/851c4500d56659d15c9c#page%E3%81%AE%E3%83%87%E3%83%BC%E3%82%BF%E3%82%92%E5%8F%96%E5%BE%97)"で説明されているページのデータを取得できるという記述に対して「それができると何が嬉しいのか」を説明する記事です。

:::note warn
全てのケースで「定数はページに保存すると便利」と主張するものではありません。
:::

# 主張
## 前提
AiScriptには`Mk:api()`という関数があります[^mkapi_is_not_a_funtion_of_aiscript]。この関数はMisskey APIにリクエストする関数です。そして、`Mk:api("pages/show" {pageId: pageId})`あるいは`Mk:api("pages/show", {name: pageTitle, username: userName})`のように書くことでページのデータを取得できます。
例えば、`pageId`とコンテント名からデータを取るようなページの中のデータを取り出す関数はこのようになるでしょう。

:::note warn
ただし、以下のコードはページのコンテントの配置に強く依存します。
コンテンツの配置にあった関数を定義する方が良いでしょう。
:::

```aiscript
@getPageContentData(pageId, contentName){
  let contents = Mk:api("pages/show" {pageId: pageId}).content.filter(@(content){content.title==contentName})
  if ((contents.len < 1) || (contents[0].type!="section")) {return null}
  let childrens = contents[0].children
  if ((childrens.len < 1) || (childrens[0].type!="text")) {return null}
  return childrens[0].text
}
```
このようにすることで、`pageId`と`contentName`からデータの取得が可能なことがわかりました。
また、これはユーザーに関わらず利用可能だと考えられます[^not_tested]。

## どのように使うか
前提として、ページの内容は一般的には読み込みしかできません。以上を踏まえて、定数データなどに対して利用しましょう。
ページには`str`型のデータしか置くことはできません。そのため、数字やオブジェクトも文字列で表現しなければなりません。そこで、JSONというデータ形式の出番です。JSONという形式は`error`や`fn`などの一部を除くあらゆる形式のデータを`str`型で表現できる形式です。この形式を利用して、ページにデータを保存することができそうです。

## どのようなケースに有効か
ここまでで、ページのこのような手法は以下のようなケースで有効です
* 変更が想定される定数データ
* 形式が統一されている定数データ

## 有効ではないケース
一方で、これが有効ではないケースもあります。
* 明らかに変更が行われないであろう定数(円周率など)
* 変数

## 何が嬉しいのか
これができて嬉しいケースは、主にウェジェットとプラグインを利用している場合です。
もし、定数の変更のたびにコードを書き換えると、その都度ユーザーにソースコードの更新をお願いする必要があります。つまり、ユーザーに手間をかけたり、ユーザー間での得られるサービスに差が発生する要因にもなります。
しかし、このページでデータを管理するモデルでは、そのような問題を起こさずにデータの更新を行えます。そのため、ユーザー側の負担減少や、ユーザー間での得られるサービス格差などを無くすことができます。

## 実際に使うシナリオ
以下では実例を提示します。
### おみくじPlay
<details>
    <summary>公式プリセットからの変更点</summary>
    <ul>
        <li><code>{result}{Str:lf}{THIS_URL}</code>となっていたところを<code>{result}</code>に変更(ウェジェットとして扱うため)</li>
    </ul>
</details>

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

// 選択肢
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}`
				}
			})
		]
	})
])
```
さて、ここで`"歴史に遺る超吉"`を追加するケースを考えましょう。
ここで、`choices`を直接編集してしまうと、このウェジェットを利用している人全員にウェジェットのスクリプトの書き換えを要求しなければなりません。
それでは面倒なので、`choices`に関するコードをあらかじめ以下のようにしておきましょう。
```diff
- let choices = [
- 	"ｷﾞｶﾞ吉"
- 	"大吉"
- 	"吉"
- 	"中吉"
- 	"小吉"
- 	"末吉"
- 	"凶"
- 	"大凶"
- ]
+ let choices = Json:parse(getPageContentData(pageId, contentName))
```
そしてページ側のテキストをこのように指定します。
```diff_json
- ["ｷﾞｶﾞ吉","大吉","吉","中吉","小吉","末吉","凶","大凶"]
+ ["歴史に遺る超吉","ｷﾞｶﾞ吉","大吉","吉","中吉","小吉","末吉","凶","大凶"]
```
こうすることで、ウェジェット側のコードを書き換えずに新しいパターンを容易に追加できます。

# 続きのようなもの
https://qiita.com/the_quick_fox/items/f765659d00f6eefe8b50

# 出典

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

https://misskey-hub.net/docs/advanced/plugin-api-reference.html

https://misskey-hub.net/docs/api/endpoints.html

https://misskey.io/@saranann/pages/aiscript_function_collection

[^mkapi_is_not_a_funtion_of_aiscript]: 正確にはAiScriptの関数ではないですが、Misskeyコンソールなどから呼び出せるため、このような表現をしました。
[^not_tested]: ただしブロックなどの場合で未検証