yuru-kit

    Shared plumbing for yuru-poll and yuru-come: live chat readers (Twitch, YouTube, stdin), a TypeSafe Jev client, .env loading and QR codes

    live-chat
    twitch
    youtube
    jev
    typesafe
    streaming
    Download zip
    Version
    0.1.0
    License
    Apache-2.0
    Last updated
    7 hours ago
    Downloads
    7

    #yuru-kit

    Shared plumbing for yuru-poll and yuru-come: reading live chat from Twitch, YouTube and stdin, sending requests to a TypeSafe Jev compatible API, loading .env, and drawing QR codes. Written in MoonBit.

    Each app keeps its own domain logic (polls and fractional votes in yuru-poll; bundling, triage and mood in yuru-come) and its own Jev questions. yuru-kit only carries what both need.

    #Packages

    PackageTargetsWhat it has
    hiroyannnn/yuru-kit (root)allRe-exports the pure parts of lib
    hiroyannnn/yuru-kit/liballComment, the ChatSource trait, Twitch IRC and YouTube (innertube and Data API v3) parsers, stdin lines, reconnect backoff, .env parsing, QR codes
    hiroyannnn/yuru-kit/sourcesnativeTwitchSource, YouTubeSource, StdinSource, log, load_dotenv
    hiroyannnn/yuru-kit/jevnativeJevClient: POST /v1/systemone with a single retry on 429 and 5xx

    #Reading chat

    ///|
    async fn main {
    @sources.set_log_prefix("my-app")
    let twitch = @sources.TwitchSource::new("somechannel")
    twitch.run(comment => println("\{comment.author}: \{comment.text}"))
    }

    Every source implements one trait:

    ///|
    pub(open) trait ChatSource {
    async fn run(Self, emit : async (Comment) -> Unit) -> Unit
    }

    • Twitch connects anonymously (justinfan<digits>, no OAuth) over TLS 6697 and falls back to plain 6667. It reconnects with exponential backoff (1 s up to 60 s; back to 1 s after a connection that lasted 60 s or more).
    • YouTube reads chat without an API key by default, the way the browser does (youtube.com/live_chat and youtubei/v1/live_chat/get_live_chat). This is unofficial and can break when YouTube changes its pages. Pass api_key to use the official Data API v3 instead (it consumes quota).
    • stdin reads participant-id<TAB>text lines.

    Participant IDs are prefixed with the source name (twitch:<user-id>, youtube:<channel-id>, stdin:<id>) so they never collide across sources.

    #Talking to Jev

    ///|
    async fn judge(text : String) -> Json {
    let client = @jev.JevClient::from_env() // JEV_URL, JEV_API_KEY, JEV_MODEL
    client.systemone({
    "state": text,
    "model": client.model,
    "questions": {
    "q": { "type": "noul", "instructions": "Is this a question?" },
    },
    })
    }

    JevClient only sends the request and returns the JSON. Building questions and reading answers stays in each app.

    VariableDefault
    JEV_URLhttp://127.0.0.1:8000 (a local open-jev)
    JEV_API_KEYlocal
    JEV_MODELjev-latest (use typesafe-ai/jev with Vercel AI Gateway at https://ai-gateway.vercel.sh/typesafe)

    #Testing

    moon check --target all && moon test --target all

    The pure parts are tested on wasm, wasm-gc, js and native. jev is tested against a local HTTP server started inside the test (success, one retry on 5xx, no retry on 4xx, broken JSON). QR codes are verified by rasterizing the generated matrix and decoding it again.

    #Acknowledgments

    The code was moved here from yuru-poll and yuru-come (same author, Apache-2.0). See NOTICE for license details.

    ChatSource

    純粋な部分(全ターゲット)をルートから使えるように再エクスポートする。 接続(sources)と Jev の送信(jev)は native 専用なので、それぞれのパッケージを import する。

    Comment

    純粋な部分(全ターゲット)をルートから使えるように再エクスポートする。 接続(sources)と Jev の送信(jev)は native 専用なので、それぞれのパッケージを import する。

    InnertubeConfig

    純粋な部分(全ターゲット)をルートから使えるように再エクスポートする。 接続(sources)と Jev の送信(jev)は native 専用なので、それぞれのパッケージを import する。

    IrcMessage

    純粋な部分(全ターゲット)をルートから使えるように再エクスポートする。 接続(sources)と Jev の送信(jev)は native 専用なので、それぞれのパッケージを import する。

    LiveChatPage

    純粋な部分(全ターゲット)をルートから使えるように再エクスポートする。 接続(sources)と Jev の送信(jev)は native 専用なので、それぞれのパッケージを import する。

    YouTubeError

    純粋な部分(全ターゲット)をルートから使えるように再エクスポートする。 接続(sources)と Jev の送信(jev)は native 専用なので、それぞれのパッケージを import する。

    connection_was_healthy

    fn connection_was_healthy(connected_ms~ : Int64, healthy_ms? : Int64) -> Bool

    切れる前の接続が十分に長くもっていたか。もっていたなら失敗の数を 0 に戻してすぐ再接続する。

    innertube_live_chat_body

    fn innertube_live_chat_body(config :
    InnertubeConfig
    , continuation : String) -> Json

    innertube_live_chat_url

    fn innertube_live_chat_url(config :
    InnertubeConfig
    ) -> String

    live_chat_messages_url

    fn live_chat_messages_url(live_chat_id : String, api_key : String, page_token : String?) -> String

    liveChatMessages.list(part=snippet,authorDetails) の URL。

    merge_env

    fn merge_env(env : Map[String, String], dotenv : Map[String, String]) -> Map[String, String]

    環境変数を優先し、無いキーだけを .env で補う。

    parse_dotenv

    fn parse_dotenv(text : String) -> Map[String, String]

    .env の中身を読む。KEY=value の行だけを取り、# のコメント・空行・export を無視する。 値は前後の空白を除き、"…" / '…' で囲まれていれば中身をそのまま(囲みの外の # … は捨てる)。

    parse_get_live_chat

    get_live_chat のレスポンスを分解する。 通常のテキストとスーパーチャットを読み、システムメッセージやバナーは読み飛ばす。

    parse_irc_line

    fn parse_irc_line(raw : String) ->
    IrcMessage

    IRC の 1 行([@tags] [:prefix] COMMAND [params] [:trailing])を解釈する。

    parse_live_chat_id

    fn parse_live_chat_id(json : Json) -> String raise
    YouTubeError

    videos.list のレスポンスから activeLiveChatId を取り出す。

    parse_live_chat_page

    liveChatMessages.list のレスポンスを分解する。 displayMessage の無い項目(メンバー加入通知など)は読み飛ばす。

    parse_live_chat_page_html

    live_chat ページから innertube の設定を取り出す。

    parse_stdin_line

    fn parse_stdin_line(line : String, at~ : Int64, anonymous_id? : String, source? : String) ->
    Comment
    ?

    標準入力の 1 行(参加者ID<TAB>発言)を Comment にする。 TAB が無ければ匿名参加者(stdin:<anonymous_id>)の発言。空行は None。 source は ID のプレフィックス(外部ブリッジをパイプするときに tiktok などへ変える)。

    participant_id

    fn participant_id(source : String, id : String) -> String

    参加者 ID を作る。Source 名をプレフィックスにして、Source 間で衝突しないようにする。

    pong_line

    fn pong_line(payload : String) -> String

    PING への返答行。

    qr_matrix

    fn qr_matrix(text : String) ->
    Matrix
    ?

    文字列(参加用 URL)を QR のマトリクスにする。空文字や長すぎる文字列は None。 誤り訂正レベルは M。スマホのカメラで画面越しに読む用途に十分な強さ。

    qr_svg

    fn qr_svg(text : String) -> String?

    文字列を余白付きの QR の SVG にする。

    reconnect_delay_ms

    fn reconnect_delay_ms(failures : Int, base_ms? : Int, max_ms? : Int) -> Int

    再接続までの待ち時間(ミリ秒)。失敗が続くごとに倍にし、max_ms で止める。

    twitch_login_lines

    fn twitch_login_lines(nick : String, channel : String) -> Array[String]

    匿名ユーザーで読み取り専用接続するためのログイン行。 twitch.tv/tags を要求して user-id・表示名・エモートの位置・送信時刻を受け取る。

    videos_list_url

    fn videos_list_url(video_id : String, api_key : String) -> String

    videos.list(part=liveStreamingDetails) の URL。

    Source Files