🍮

ねこでもわかるかもしれないAiScript(Misskey Play)

2023/11/27に公開

AiScript is 何

ActivityPub対応SNSの「Misskey」を開発したsyuiloさんが開発したプログラミング言語で、Misskeyの一部機能(Play, プラグイン, ウィジェット)にも埋め込まれている。やつです。
今回はMisskey PlayのAiScriptの話だけします。

標準プリセットから見るAiScript

Misskey Playには標準でいくつかプリセットがあります。

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}{Str:lf}{THIS_URL}`
        }
      })
    ]
  })
])

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

// 選択肢

/// @ 0.16.0←これはAiScriptのバージョンです。
//←これで始まる行はただのコメントです。


let choices = [
  "ギガ吉"
  "大吉"
  "吉"
  "中吉"
  "小吉"
  "末吉"
  "凶"
  "大凶"
]

ここでは、choices変数を配列で初期化しています。
変数というのは名前をつけて覚えるためのもので、配列というのは要はリストのことです。
let なんちゃら = かんちゃらとすることで、かんちゃらを「なんちゃら」という名前で覚えることができます。
ここでは["ギガ吉" "大吉" "吉" ...]というリストを「choices」という名前で覚えたわけです。

なぜ""で囲っているの?

AiScriptをはじめとするプログラミング言語では、型というシステムがあります。
例えばコンピューターに「1」と言った時、文字列としての「いち」なのか、数字としての「1」なのか理解できません。AiScriptでは、"1"とダブルクォーテーション記号で囲った場合は文字列の「1」、囲わずに1と言った場合には数字の1を指します。
また、'1'"1"と同じように文字列として解釈されます。


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

ここでは、AiScriptにあるMath:gen_rng()という関数を使って、ランダムな数を生成する関数を用意し、randomという名前で覚えています。
関数というのは、何らかのデータを受け取ったり受け取らなかったりして、何か操作を行ったり加工したりするものです。
Math:gen_rng()は、シード(種)の値を受け取って、ランダムな数生成器(関数)を返す関数です。
ここで注意すべきは、ここで作ったのはランダムな数生成器であって、ランダムな数そのものではないということです。実際にランダムな数を発生させるには、作ったもの(関数)にさらに「いくつからいくつまでの値を作るのか」という範囲を入力する必要がありますが、この行ではまだやりません。

コードでは、Math:gen_rng()のシードになにやら「`」記号で囲まれたデータを入れています。「`」という記号は、AiScriptではデータを埋め込める文字列に使う記号です。{}記号でデータを埋め込めます。たとえば、

let name = "たかし"
say(`Hello, {name}!!`)

とすると、say関数[1]には、実際には"Hello, たかし!!"というデータが渡されます。nameとして覚えたデータを埋め込んでいるわけです。
ここで、もう一度コードを見てみます。

`{USER_ID}{Date:year()}{Date:month()}{Date:day()}`

USER_IDというのは、何も言わなくても最初からユーザーIDが入っている変数です。[2]
Date:year()というのは、現在の年を返す関数です。
Date:month()というのは、現在の月を返す関数です。
Date:day()というのは、現在の日を返す関数です。
つまり、2023/12/25にこのPlayを実行したユーザーIDAbCdEfGhIjの場合は、"AbCdEfGhIj20231225"と言っているのとおなじになるわけです。
さて、この値をMath:gen_rng()に入力します。すると乱数生成器が作られます。この乱数生成器はユーザーIDと日付によって変化する、唯一無二のものです。この生成器を、randomという名前で覚えておきます。


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

この行では、choicesrandom(0 choices.len - 1)番目の値をchosenとして覚えています。
choices[~]となっているのは、choices配列の~番目という意味です。
randomはさっき覚えた乱数生成器です。乱数生成器は関数なので、()に入力を入れられます。この入力に、0からchoices.len - 1という範囲を指定しています。choices.lenというのは、choices配列の長さのことです。そこから1を引いています。

これで、0からchoices.len - 1の範囲のランダムが生成できました。
この値をchoices[~]に入れると、choicesのランダムな要素を選び出すことができるというわけです。
選びだした結果をchosenとして覚えています。


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

前述のとおり、今日のあなたの運勢は**{chosen}**です。{chosen}は具体的な値に置き換えられます。chosenがギガ吉なら"今日のあなたの運勢は**ギガ吉**です。"となります。
それをresultとして覚えます。


// 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}`
        }
      })
    ]
  })
])

Ui:render()はUIを作成する関数です。UIパーツの配列を入力することで、UIを表示することができます。
Ui:C:container()はUIパーツをまとめるためのUIパーツを返す関数です。
Ui:C:mfm()はMFMを表示するためのUIパーツを返す関数です。
Ui:C:postFormButton()は投稿ボタンを表示するためのUIパーツを返す関数です。
UIパーツを返す関数にはオブジェクトと呼ばれるデータ構造のモノを入れます。オブジェクトは辞書オブジェクトと言われることもあり、モノとモノの詳細な特徴を記述するときに利用します。

オブジェクトの例

例えば、

{
  apple: "red"
  banana: "yellow"
}

みたいな感じです。
ちなみに、ここで言うappleのことを「キー」、"red"のことを「バリュー」と呼んだりもします。
また、オブジェクトは入れ子にしたり配列を入れたりすることもできます。

{
  apple: {
    color: [
      {
        red: 100
        green: 0
        blue: 0
      }
      {
        red: 0
	green: 100
	blue: 0
      }
    ]
  }
  grape: {
    color: [
      {
        red: 100
        green: 0
        blue: 100
      }
      {
        red: 0
	green: 100
	blue: 0
      }
    ]
  }
}

ちなみに、オブジェクトを変数で覚えると、

let fruits = {
  apple: "red"
  banana: "yellow"
}
fruits.apple // "red"
fruits["apple"] // "red"

のようにfruits.applefruits["apple"]も変数として覚えたみたいな感じになります。
また、オブジェクトも型の一種です。

Ui:C:container()にはこんなオブジェクトを渡します。

{
  align: 'center' // 文字揃え、'left'とかも可能
  children: [
    // 内側に入れるUIパーツいろいろ~~~~~
  ]
}

Ui:C:mfm()にはこんなオブジェクトを渡します。

{
  text: "表示したいMFM!!"
}

Ui:C:postFormButton()にはこんなオブジェクトを渡します。

{
  text: "投稿する" // ボタンに乗せるテキスト~~~
  rounded: true // 角丸めるか丸めないか 丸めないならfalse
  primary: true // 色つけるかつけないか つけないならfalse
  form: {
    text: "投稿するテキスト~~~~~"
  }
}

こんなかんじ。

ちなみに

UIパーツには以下のようにIDをセットすることで、表示する文字を後から変更するような用途にも対応可能です。

Ui:C:mfm({ text: "abc" } "ididid")
Ui:get("ididid").update({text: "xyz"}) // こうやって変更する

長くなりましたが、この行では全要素中央揃えで、MFMでresultとして覚えた値を表示して、「投稿する」というテキストの色付き角丸投稿ボタンを表示して、投稿ボタンを押すと{result}{Str:lf}{THIS_URL}で表されるテキストを投稿する画面になるように設定されています。(Str:lfは何も言わなくても最初から改行が入っている変数で、THIS_URLは何も言わなくても最初からこのPlayのURLが入っている変数です。)


Omikujiはこんな感じです。
簡単ですね。

Shuffle

巻き戻し機能がやや複雑なのでパス。

Quiz

/// @ 0.16.0
let title = '地理クイズ'

let qas = [{
  q: 'オーストラリアの首都は?'
  choices: ['シドニー' 'キャンベラ' 'メルボルン']
  a: 'キャンベラ'
  aDescription: '最大の都市はシドニーですが首都はキャンベラです。'
} {
  q: '国土面積2番目の国は?'
  choices: ['カナダ' 'アメリカ' '中国']
  a: 'カナダ'
  aDescription: '大きい順にロシア、カナダ、アメリカ、中国です。'
} {
  q: '二重内陸国ではないのは?'
  choices: ['リヒテンシュタイン' 'ウズベキスタン' 'レソト']
  a: 'レソト'
  aDescription: 'レソトは(一重)内陸国です。'
} {
  q: '閘門がない運河は?'
  choices: ['キール運河' 'スエズ運河' 'パナマ運河']
  a: 'スエズ運河'
  aDescription: 'スエズ運河は高低差がないので閘門はありません。'
}]

let qaEls = [Ui:C:container({
  align: 'center'
  children: [
    Ui:C:text({
      size: 1.5
      bold: true
      text: title
    })
  ]
})]

var qn = 0
each (let qa, qas) {
  qn += 1
  qa.id = Util:uuid()
  qaEls.push(Ui:C:container({
    align: 'center'
    bgColor: '#000'
    fgColor: '#fff'
    padding: 16
    rounded: true
    children: [
      Ui:C:text({
        text: `Q{qn} {qa.q}`
      })
      Ui:C:select({
        items: qa.choices.map(@(c) {{ text: c, value: c }})
        onChange: @(v) { qa.userAnswer = v }
      })
      Ui:C:container({
        children: []
      } `{qa.id}:a`)
    ]
  } qa.id))
}

@finish() {
  var score = 0

  each (let qa, qas) {
    let correct = qa.userAnswer == qa.a
    if (correct) score += 1
    let el = Ui:get(`{qa.id}:a`)
    el.update({
      children: [
        Ui:C:text({
          size: 1.2
          bold: true
          color: if (correct) '#f00' else '#00f'
          text: if (correct) '🎉正解' else '不正解'
        })
        Ui:C:text({
          text: qa.aDescription
        })
      ]
    })
  }

  let result = `{title}の結果は{qas.len}問中{score}問正解でした。`
  Ui:get('footer').update({
    children: [
      Ui:C:postFormButton({
        text: '結果を共有'
        rounded: true
        primary: true
        form: {
          text: `{result}{Str:lf}{THIS_URL}`
        }
      })
    ]
  })
}

qaEls.push(Ui:C:container({
  align: 'center'
  children: [
    Ui:C:button({
      text: '答え合わせ'
      primary: true
      rounded: true
      onClick: finish
    })
  ]
} 'footer'))

Ui:render(qaEls)

let title = '地理クイズ'

「地理クイズ」という文字列をtitleとして覚えています。


let qas = [{
  q: 'オーストラリアの首都は?'
  choices: ['シドニー' 'キャンベラ' 'メルボルン']
  a: 'キャンベラ'
  aDescription: '最大の都市はシドニーですが首都はキャンベラです。'
} {
  q: '国土面積2番目の国は?'
  choices: ['カナダ' 'アメリカ' '中国']
  a: 'カナダ'
  aDescription: '大きい順にロシア、カナダ、アメリカ、中国です。'
} {
  q: '二重内陸国ではないのは?'
  choices: ['リヒテンシュタイン' 'ウズベキスタン' 'レソト']
  a: 'レソト'
  aDescription: 'レソトは(一重)内陸国です。'
} {
  q: '閘門がない運河は?'
  choices: ['キール運河' 'スエズ運河' 'パナマ運河']
  a: 'スエズ運河'
  aDescription: 'スエズ運河は高低差がないので閘門はありません。'
}]

質問と選択肢と解答と説明を「オブジェクトの配列」として表現し、qasとして覚えています。


let qaEls = [Ui:C:container({
  align: 'center'
  children: [
    Ui:C:text({
      size: 1.5
      bold: true
      text: title
    })
  ]
})]

中央揃えにしてtitleを表示するUIパーツの配列をqaElsとして覚えます。


var qn = 0

0という数字をqnとして覚えています。letではなくvarとなっていますが、この2つの違いは覚えたものをあとから上書きできるかどうかです。上書きできない場合はlet、上書きできる場合はvarを使います。ただし、配列に値を追加したりオブジェクトにキーを追加したりする操作はletでも可能です。


each (let qa, qas) {
  // ~~~
}

each (let qa, qas)とは、「qas配列から順番に要素を取り出して、それをqaとして覚えながら後ろの関数を繰り返す」という意味です。
例えば、

let words = ["しりとり" "りんご" "ゴリラ" "ラッパ" "パンダ"]
each (let word, words) {
  say(word)
}

とすると、wordsの内容を順にsay関数に入力することになるというわけです。


qn += 1

qnの内容に1を足すという意味です。eachの中なので、ループの中で順々にカウントアップされます。この変数は問題番号(Q1、Q2、...)を数えるのに使われます。


qa.id = Util:uuid()

qaidキーをUtil:uuid()関数の出力にするという意味です。idキーは元々無いので、新しく作成されます。Util:uuid()関数は、被る可能性の非常に低いランダム文字列を出力します。(40abde63-6c16-4fb1-a32c-a5efff44ccc2←こんなかんじ) 被ることを心配するくらいなら隕石が落ちてくるのを心配したほうがいいほどなので、UUIDが被ることは考えなくてもいいです。
つまり、qa.id = Util:uuid()をeachで全要素に行った場合、qasは以下のようになります。

[{
  q: 'オーストラリアの首都は?'
  choices: ['シドニー' 'キャンベラ' 'メルボルン']
  a: 'キャンベラ'
  aDescription: '最大の都市はシドニーですが首都はキャンベラです。'
+  id: "b0e31617-ad76-4a4b-bd8b-2ef83b084028"
} {
  q: '国土面積2番目の国は?'
  choices: ['カナダ' 'アメリカ' '中国']
  a: 'カナダ'
  aDescription: '大きい順にロシア、カナダ、アメリカ、中国です。'
+  id: "2e10b24b-38ff-476d-b08d-6836e403bdb2"
} {
  q: '二重内陸国ではないのは?'
  choices: ['リヒテンシュタイン' 'ウズベキスタン' 'レソト']
  a: 'レソト'
  aDescription: 'レソトは(一重)内陸国です。'
+  id: "39008605-881b-4bd5-9ebc-1cde8ba4f006"
} {
  q: '閘門がない運河は?'
  choices: ['キール運河' 'スエズ運河' 'パナマ運河']
  a: 'スエズ運河'
  aDescription: 'スエズ運河は高低差がないので閘門はありません。'
+  id: "09580b9f-2ba2-440f-8427-86f850211b44"
}]

  qaEls.push(Ui:C:container({
    align: 'center'
    bgColor: '#000'
    fgColor: '#fff'
    padding: 16
    rounded: true
    children: [
      Ui:C:text({
        text: `Q{qn} {qa.q}`
      })
      Ui:C:select({
        items: qa.choices.map(@(c) {{ text: c, value: c }})
        onChange: @(v) { qa.userAnswer = v }
      })
      Ui:C:container({
        children: []
      } `{qa.id}:a`)
    ]
  } qa.id))

qaEls.push()関数は、qaEls配列の最後に要素を追加します。追加する要素はカッコ内の入力で指定します。このコードでは、

Ui:C:container({
  align: 'center'
  bgColor: '#000'
  fgColor: '#fff'
  padding: 16
  rounded: true
  children: [
    Ui:C:text({
      text: `Q{qn} {qa.q}`
    })
    Ui:C:select({ // これはセレクトボックスのUIパーツ
      items: qa.choices.map(@(c) {{ text: c, value: c }})
      onChange: @(v) { qa.userAnswer = v }
    })
    Ui:C:container({
      children: []
    } `{qa.id}:a`)
  ]
} qa.id)

という要素を追加するようになっています。これは、全要素を中央揃えした黒背景・白文字・余白16・角丸のコンテナの中に「`Q{qn} {qa.q}`」という文字列(置き換えられて「Q(問題番号) (質問)」の形になる)、セレクトボックス、空のコンテナ(あとで正解/不正解を表示するのに使われるので、IDが`{qa.id}:a`として指定されています)があるようなUIパーツです。
セレクトボックスに渡しているオブジェクトのitemsキーには、qa.choicesの内容をmap()関数で加工して渡しています。Ui:C:select()の仕様に合うようにいじっているだけです。

map関数とは?

配列の要素それぞれに対して、入力で渡された関数を実行して帰ってくる値を集めてできる配列を返します(驚きのややこしさ)
たとえば、

let array = [0 1 2 3 4 5 6 7 8 9 10]
let array2 = array.map(@(v) {v * 10}) // [0 10 20 30 40 50 60 70 80 90 100]

という感じになります。
じゃあmap関数に渡しているその謎のアットマークはなんなんだと言う話ですが、これは関数を自分でその場で作るための記法です。上の例では「v」として値を受け取って、vの10倍の値を返す関数を作っています。

qa.choices.map(@(c) {{ text: c, value: c }})

この例では、qa.choices配列に対してmap関数を実行することで、

['シドニー' 'キャンベラ' 'メルボルン']

これが

[
  { text: 'シドニー', value: 'シドニー' }
  { text: 'キャンベラ', value: 'キャンベラ' }
  { text: 'メルボルン', value: 'メルボルン' }
]

こうなります。

onChangeキーには、セレクトボックスの値が変化した際の処理が関数として書かれています。
@(v) { qa.userAnswer = v }
変化した後の選択肢を「v」として受け取り、qa.userAnswervとして設定する関数です。
userAnswerというキーがない場合、新しく作成されます。
つまり、「オーストラリアの首都は?」という問いに「シドニー」と答えると、qasの値はこうなります。

[{
  q: 'オーストラリアの首都は?'
  choices: ['シドニー' 'キャンベラ' 'メルボルン']
  a: 'キャンベラ'
  aDescription: '最大の都市はシドニーですが首都はキャンベラです。'
  id: "b0e31617-ad76-4a4b-bd8b-2ef83b084028"
+  userAnswer: 'シドニー'
}
︙

「キャンベラ」と答え直すとこうです。

[{
  q: 'オーストラリアの首都は?'
  choices: ['シドニー' 'キャンベラ' 'メルボルン']
  a: 'キャンベラ'
  aDescription: '最大の都市はシドニーですが首都はキャンベラです。'
  id: "b0e31617-ad76-4a4b-bd8b-2ef83b084028"
+  userAnswer: 'キャンベラ'
}
︙

@finish() {
  var score = 0

  each (let qa, qas) {
    let correct = qa.userAnswer == qa.a
    if (correct) score += 1
    let el = Ui:get(`{qa.id}:a`)
    el.update({
      children: [
        Ui:C:text({
          size: 1.2
          bold: true
          color: if (correct) '#f00' else '#00f'
          text: if (correct) '🎉正解' else '不正解'
        })
        Ui:C:text({
          text: qa.aDescription
        })
      ]
    })
  }

  let result = `{title}の結果は{qas.len}問中{score}問正解でした。`
  Ui:get('footer').update({
    children: [
      Ui:C:postFormButton({
        text: '結果を共有'
        rounded: true
        primary: true
        form: {
          text: `{result}{Str:lf}{THIS_URL}`
        }
      })
    ]
  })
}

答え合わせの処理やUI表示などをfinish()関数として定義しています。細かい解説は一旦飛ばします。


qaEls.push(Ui:C:container({
  align: 'center'
  children: [
    Ui:C:button({
      text: '答え合わせ'
      primary: true
      rounded: true
      onClick: finish
    })
  ]
} 'footer'))

Ui:render(qaEls)

ここでは、qaElsに答え合わせボタンを.push()しています。あとから書き換え可能にするためにcontainerに'footer'というIDを付与しています。答え合わせボタンをクリックしたときに、さっき定義したfinish()関数を呼び出しています。


ここでもういちどfinish()関数の中身を見てみます。

@finish() {
  var score = 0

  each (let qa, qas) {
    let correct = qa.userAnswer == qa.a
    if (correct) score += 1
    let el = Ui:get(`{qa.id}:a`)
    el.update({
      children: [
        Ui:C:text({
          size: 1.2
          bold: true
          color: if (correct) '#f00' else '#00f'
          text: if (correct) '🎉正解' else '不正解'
        })
        Ui:C:text({
          text: qa.aDescription
        })
      ]
    })
  }

  let result = `{title}の結果は{qas.len}問中{score}問正解でした。`
  Ui:get('footer').update({
    children: [
      Ui:C:postFormButton({
        text: '結果を共有'
        rounded: true
        primary: true
        form: {
          text: `{result}{Str:lf}{THIS_URL}`
        }
      })
    ]
  })
}

  var score = 0

0scoreという名前で覚えています(上書き可能)


  each (let qa, qas) {
    let correct = qa.userAnswer == qa.a
    if (correct) score += 1
    let el = Ui:get(`{qa.id}:a`)
    el.update({
      children: [
        Ui:C:text({
          size: 1.2
          bold: true
          color: if (correct) '#f00' else '#00f'
          text: if (correct) '🎉正解' else '不正解'
        })
        Ui:C:text({
          text: qa.aDescription
        })
      ]
    })
  }

ここでは、eachを使ってすべてのqas配列の要素に対してループしています。
let correct = qa.userAnswer == qa.aで、「qa.userAnswerqa.aが等しい」かどうかを調べ、等しければtrue、等しくなければfalseという値をcorrectとして覚えます。

true, falseとは?

true, falseは型の1種で、「はい」か「いいえ」の2択の値を表現するためのものです。trueかfalseのことをbool型と言います。
「true」は「はい」、「false」は「いいえ」という意味です。
ifを使った処理で「もし変数がtrueなら」「もし変数がfalseなら」「2つの変数が両方trueなら」「2つの変数が両方falseなら」...みたいな処理を実行することができます。

let bool1 = true
let bool2 = false
if (bool1) { // bool1がtrueなら実行
  say("bool1 is true!!")
}
if (!bool1) { // bool1がfalseなら実行(ビックリマークは否定を意味し、trueをfalseに、falseをtrueに変換する)
  say("bool1 is false!!")
}
if (bool1 || bool2) { // bool1, bool2のどちらかがtrueなら実行
  say("either bool1 or bool2 is true!!")
}
if (bool1 && bool2) { // bool1, bool2のどちらもtrueなら実行
  say("bool1 and bool2 are true!!")
}

if (correct) score += 1で、correcttrueならscoreを+1しています。
let el = Ui:get(`{qa.id}:a`)で、UIからIDが`{qa.id}:a`の要素を探して、見つかった要素をelとして覚えています。(正解か不正解かを表示するための空のcontainerが見つかります)
el.update()のくだりで正解か不正解かという文字列と解説に置き換えています。


  let result = `{title}の結果は{qas.len}問中{score}問正解でした。`
  Ui:get('footer').update({
    children: [
      Ui:C:postFormButton({
        text: '結果を共有'
        rounded: true
        primary: true
        form: {
          text: `{result}{Str:lf}{THIS_URL}`
        }
      })
    ]
  })

結果を説明する文字列をresultとして覚えて、idfooterの要素に`{result}{Str:lf}{THIS_URL}`の投稿ボタンを追加しています。


Quizはこんなかんじ。

Timeline Viewer

書き疲れてきたのでふんわりあっさり解説。

/// @ 0.16.0
// 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) {
    // 表示名を設定していないアカウントはidを表示
    let userName = if Core:type(note.user.name) == "str" note.user.name else note.user.username
    // リノートもしくはメディア・投票のみで本文が無いノートに代替表示文を設定
    let noteText = if Core:type(note.text) == "str" note.text else "(リノートもしくはメディア・投票のみのノート)"

    let el = Ui:C:container({
      bgColor: "#444"
      fgColor: "#fff"
      padding: 10
      rounded: true
      children: [
        Ui:C:mfm({
          text: userName
          bold: true
        })
        Ui:C:mfm({
          text: noteText
        })
      ]
    })
    noteEls.push(el)
  }

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

fetch()

Mk:api()でMisskeyのAPIを叩けます。APIの一覧はここにあります。
受け取った配列をeachでよしなに処理しています。

おわり

おわり

参考

https://qiita.com/saki-lere/items/e7f0eb6b7dc0af659a67

脚注
  1. say関数は説明を簡単にするために考えたもので、AiScript組み込みで存在するものではありません ↩︎

  2. ユーザーIDは、アットマークで始まるIDではなく、設定>その他>アカウント情報から見られるIDのことです。 ↩︎

Discussion