MiniApp は Tokomi のストーリーコンテナ内で動く Web アプリです。ルートに index.html を持つ静的バンドルで、コンテナが注入する window.Shine から AI・ストレージ・キャラクター・ストーリー機能にアクセスします。本ドキュメントはフロントエンド開発者向けで、HTML / JavaScript と一般的なビルドツールの知識を前提とします。
最小の MiniApp で「AI 呼び出し → 結果の永続化 → 公開」を一通り体験します。
SDK はページ読み込み時にコンテナが注入するため window.Shine をそのまま使えます。shine-app.js を自分で読み込まないでください。単一ファイル HTML はトランスパイルされないので、?. / ?? など Chromium 80+ の構文は避けます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Hello Tokomi</title>
<style>
body {
margin: 0; font-family: system-ui; background: #111; color: #eee;
/* フルスクリーン実行:上部セーフエリアは iOS の env() とコンテナ変数の大きい方 */
padding: max(env(safe-area-inset-top, 0px), var(--shine-safe-area-top, 0px)) 16px 16px;
}
button { min-height: 44px; padding: 0 16px; }
</style>
</head>
<body>
<button id="ask">AI に聞く</button>
<p id="out"></p>
<script>
var btn = document.getElementById('ask');
var out = document.getElementById('out');
btn.addEventListener('click', async function () {
btn.disabled = true; // 連打による二重課金を防ぐ
try {
var reply = await Shine.ai.chat({ // 有料機能。標準価格で星辰を消費
system: 'あなたは簡潔なアシスタントです。',
message: 'Tokomi を一文で紹介してください。'
});
out.textContent = reply;
await Shine.storage.set('last_reply', reply); // クラウド KV。ユーザーごとに分離
} catch (e) {
await Shine.ui.toast(e.message || '呼び出しに失敗しました'); // 残高不足はプラットフォームが案内
} finally {
btn.disabled = false;
}
});
</script>
</body>
</html>アプリを新規作成してコードを貼り付けると、右側のシミュレーターでリアルタイムにプレビューでき、「ストア / ストーリー付き」の起動モードを切り替えられます。プレビュー中の有料呼び出しは実際に星辰を消費します。
名前・カテゴリ・アイコン・スクリーンショットを入力し、「星辰消費申告」で使用する有料機能と発動頻度を申告します。プラットフォームはこれに基づき入場前に統一ダイアログで告知し、承認後は自動的に公開されます。
Claude Code / Cursor / Codex などのコーディングアシスタント向け Agent Skill(英語)です。インストールすると、Tokomi MiniApp を扱う際にプラットフォームの必須ルール——SDK はコンテナが注入、起動モードの分岐、星辰課金と 1 タップ 1 枚、旧 WebView 互換基準、審査のレッドライン——を自動的に守り、参照用の完全な SDK リファレンスも同梱します。
references/ 以下のファイルは本ページと同じデータから生成されるため、ドキュメントと常に一致します。
# Claude Code unzip shine-miniapp-skill.zip -d .claude/skills/ # Cursor unzip shine-miniapp-skill.zip -d .cursor/skills/ # Codex / other agents: unzip anywhere and reference SKILL.md from AGENTS.md
単一ファイル HTML はワークベンチで直接編集・プレビューできます。マルチファイル構成はローカルで Vite 開発し、リリース時に npm run build で dist/ を出力、ルートに index.html を含む ZIP にしてワークベンチへアップロードします。
// vite.config.ts
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
export default defineConfig(() => {
const apiTarget = process.env.VITE_API_BASE || 'http://localhost:8000';
return {
// 成果物は相対パスにし、MiniApp コンテナの file:// / サブディレクトリ読み込みに
// 対応させます(絶対パス /assets のままだと 404 で真っ白になります)
base: './',
plugins: [react()],
build: {
// 古い Android WebView(vivo など出荷時 Chromium <80)は ?. / ?? を解釈できず、
// デフォルトの esnext ビルドはパッケージ全体が SyntaxError で真っ黒になります。
// 実ブラウザの target を指定して esbuild に新しい構文をトランスパイルさせます。
// cssTarget は自動で追従し、CSS 圧縮時に top/right/bottom/left の 4 辺が
// 旧端末非対応の inset 短縮形へまとめ直されるのを防ぎます。
target: ['chrome61'],
},
server: {
port: 3000,
host: '0.0.0.0',
proxy: {
// Shine SDK と MiniApp API を同一オリジンにして CORS を回避
'/api': { target: apiTarget, changeOrigin: true },
'/static': { target: apiTarget, changeOrigin: true },
},
},
};
});// src/lib/shine-dev.ts — ラッパー層を 1 枚挟み、アプリコードはここだけを呼ぶ構成を推奨
const DEV_APP_ID = '00000000-0000-0000-0000-000000000000';
function installDevLaunchOptions() {
const sessionId = import.meta.env.VITE_SHINE_SESSION_ID;
const characterId = import.meta.env.VITE_SHINE_CHARACTER_ID;
if (!window.__shine_launch_options && sessionId) {
window.__shine_launch_options = { sessionId, characterId };
}
}
async function ensureSdk() {
if (window.Shine) return;
await new Promise<void>((resolve, reject) => {
const s = document.createElement('script');
s.src = '/static/sdk/v1/shine-app.js';
s.onload = () => resolve();
s.onerror = () => reject(new Error('Shine SDK の読み込みに失敗しました'));
document.head.appendChild(s);
});
}
let inited = false;
export async function initShine() {
installDevLaunchOptions();
await ensureSdk();
if (inited || !window.Shine?.init) return;
window.Shine.init({
appId: import.meta.env.VITE_SHINE_APP_ID || DEV_APP_ID,
token: import.meta.env.VITE_SHINE_TOKEN || '',
baseUrl: '', // 同一オリジン、Vite proxy 経由
});
inited = true;
}// src/main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import App from './App';
import { initShine } from './lib/shine-dev';
void initShine();
createRoot(document.getElementById('root')!).render(
<StrictMode><App /></StrictMode>,
);ログインすると、ここで token とデバッグセッション入りの .env.local をワンクリック生成できます。変数:VITE_API_BASE、VITE_SHINE_APP_ID、VITE_SHINE_TOKEN、VITE_SHINE_SESSION_ID、VITE_SHINE_CHARACTER_ID。
| レイヤー | 環境設定 | SDK / AI | ストーリー機能 | ネイティブ |
|---|---|---|---|---|
| ① UI のみ | npm run dev、token 不要 | サンプルのラッパー層でフォールバック / mock(下記のフォールバック戦略参照) | session なし → ストアモード | 利用不可 |
| ② SDK 連携 | VITE_SHINE_TOKEN を設定 + バックエンド起動 | ai / storage / user は実 HTTP 通信 | 連絡先 / actor はまだ使えません | 利用不可 |
| ③ ストーリー連携 | さらに VITE_SHINE_SESSION_ID / CHARACTER_ID を設定 | 同上 | contact / actor / wallet が利用可能 | 利用不可 |
| ④ リリース検収 | npm run build → ZIP → ワークベンチへアップロード | 本番と同一 | ワークベンチでストーリーセッションを掛けたプレビューも可能 | 実機コンテナのみ |
利用できる機能は起動方法で決まります。Shine.context.getLaunchOptions() で同期的に判定できます:null ならストアモード、{ sessionId, characterId? } ならストーリー付き起動です。
| 起動方式 | getLaunchOptions() | 利用できる機能 |
|---|---|---|
| アプリストアから直接起動 | getLaunchOptions() → null | ai.* / storage / user / ui(一部)/ tts(voiceId モード、ナレーション可)が利用可能。character / contact / actor / wallet は利用不可または空を返す(getLaunchOptions() 自体はどのモードでも呼び出せ、この場合 null を返す) |
| ストーリーセッションから起動 | { sessionId, characterId? } | 全機能が利用可能(actor.speak / actor.notify、scene.inject、wallet、連絡先を含む) |
| ワークベンチプレビュー | 「ストア / ストーリー」のシミュレーションを選択可 | 本番と同一。課金機能は実際に星辰を消費する |
公開フォームの「起動モード」を「ストーリー起動」にすると、入場前にユーザーがストーリーを選ぶため sessionId は必ず存在します。「互換」を選ぶ場合は null を処理する必要があります。
const launch = Shine.context.getLaunchOptions();
if (!launch) {
// ストアモード:セッション依存の入口(actor / contact / wallet / scene)を隠す
}有料機能はプラットフォーム標準価格でユーザーの星辰残高から消費されます。消費の告知と残高不足の案内はプラットフォームが統一して担います——アプリ内で重複実装する必要はなく、実装すべきでもありません。
| API | 課金 |
|---|---|
Shine.ai.chat | 標準価格で課金。コンテナは呼び出しごとに確認しない |
Shine.ai.image | 標準価格で課金。プラットフォームが 1 枚ごとに承認ダイアログを表示し、ユーザーが拒否すると reject(user_denied)、課金なし |
Shine.actor.speak | 選択したモデルの標準価格で課金 |
Shine.tts.speak / synthesize | キャッシュ未ヒット時は文字数で課金(50 文字単位)。キャッシュヒットは無料 |
Shine.actor.notify | 一方向配信は無料。triggerReply: true は NPC の返信として課金 |
Shine.wechat.sendCard | triggerReply はデフォルト true で、NPC の返信として課金 |
| その他の機能 | storage / ui / device / context / character / contact / scene / credits / wallet はすべて無料 |
MiniApp は各メーカーの Android WebView 上で動き、一部のシステム WebView は Chromium 66–79 に留まっています。デスクトップブラウザで動いても実機で動く保証はありません。下表は頻度が高く影響の大きい制約です。React / Vite 構成は build.target で構文を変換できますが、API の欠落と CSS 機能は手動で避ける必要があります。
| トピック | 避ける | 代わりに使う |
|---|---|---|
| ビルドターゲット(React/Vite プロジェクト) | vite.config で build.target を未設定(デフォルトは esnext のため ?. / ?? がそのまま出力され、Chromium 80 未満の端末ではバンドル全体が SyntaxError) | build: { target: ['chrome61'] }(実ブラウザの target。構文は自動トランスパイルされ cssTarget も追従)+ base:'./' を設定;単一ファイル HTML はトランスパイルされないので、そもそも ?. / ?? を書かない |
| 新しい JS API・構文 | トランスパイルなしで .at()、replaceAll、structuredClone、Object.hasOwn、??=/||=/&&=、findLast、Promise.any、Promise.allSettled、matchAll、Object.fromEntries、モジュールのトップレベル await を使用;単一ファイル HTML では ?. / ?? も対象(Chrome 80 が必要で、vivo の 66~79 では落ちる) | arr[arr.length-1]、split().join()、JSON.parse(JSON.stringify())、hasOwnProperty.call を使う;??=/||=/&&= は if 判定+代入に展開する(単一ファイル HTML では ?? / ?. 自体も使用不可);allSettled/matchAll/fromEntries は API 自体が存在せずトランスパイルでは補えないため、書き換えるか polyfill で対応 |
| 色と透明度 | color-mix()/oklch()/lab()/@layer;Tailwind v4 のブラウザ CDN(@tailwindcss/browser@4) | 色は rgb()/rgba()/#hex を使う;Tailwind は Play CDN cdn.tailwindcss.com(v3)を使う |
| Tailwind の透明度 | 5 の倍数でない透明度修飾子(bg-white/8、text-white/12) | 5 の倍数のみ使う(/10、/15、/20…)、または bg-[rgba(...)] |
| アスペクト比 | aspect-ratio / Tailwind の aspect-[3/4] | padding-bottom で比率を確保する:コンテナに position:relative;width:100%;padding-bottom:133%(3:4 は高さ/幅=4/3)、子要素は absolute で四辺 0 |
| flex の間隔 | flex コンテナで gap-* を使う | margin / Tailwind の space-x-*、space-y-*(grid は gap のままで可) |
| 絶対配置の inset | inset ショートハンド / Tailwind の inset-0 | top/right/bottom/left の 4 値を個別に書く |
| 画像の角丸 | 親コンテナの overflow:hidden + border-radius だけで <img> を切り抜く | <img> 自体に border-radius を付ける(Tailwind rounded-full) |
| 3D フリップ | transform-style:preserve-3d + backface-visibility による両面カードフリップ | 単層の rotateY 回転イン + opacity のフェードで代替する |
| すりガラス(ブラー) | backdrop-filter / Tailwind の backdrop-blur | 暗い半透明の下地+グラデーション+ボーダーで擬似的に表現する;マスクは rgba(0,0,0,.7) |
| ビューポート単位 | dvh / svh / lvh | 100% + flex で広げる、または vh を使う |
| その他の新しい CSS | :has()、:is()、:where()、accent-color、content-visibility、text-wrap:balance、subgrid | サポート済みの等価な書き方に置き換えるか、削除する |
| 画像フォーマット | .avif | WebP / PNG / JPG |
| アセットのパス | サイト絶対パス /assets/x.png | 相対パス ./assets/x.png を使う;Vite は base:'./' を設定 |
| 内側のスクロール | 内側のスクロールコンテナにタッチスクロール属性を付けない | スクロールコンテナに -webkit-overflow-scrolling:touch; touch-action:pan-y; を付ける |
| 低スペック端末の性能 | repeat:Infinity の無限アニメーションの多用、背景パーティクルの出しすぎ、backdrop-filter | 常駐の背景パーティクルは 10 個以下・opacity のみ動かす;静的で済むものは静的にする |
| Canvas / WebGL(3D を使う場合のみ) | canvas で webglcontextlost を処理しない——古い Android はメモリ逼迫時に GPU を回収し、コンテキストが恒久的に失効する | webglcontextlost(e.preventDefault() + 描画ループ停止)と webglcontextrestored(テクスチャ/バッファ再構築 + 描画再開)を監視する;three.js は WebGLRenderer の対応イベントを使う;読み込み失敗時は静的なフォールバックを表示する |
MiniApp は結果を現在のストーリーに書き戻せます:オフラインの小劇場に保存する、NPC に知らせる、あるいはキャラクターに直接発言させる。以下の API はいずれもストーリー付き起動(launch に sessionId を含む)が必要です。
| 目的 | API | 課金 |
|---|---|---|
| オフラインのタイムラインにナレーションを表示し、「新しいプロット」通知を出す | scene.inject({ visible }) | 無料 |
| 吹き出しを出さずに NPC に知らせる(演出メモ / 伏線 / ミニゲームの結果) | scene.inject({ hidden }) | 無料 |
| 任意の送信者として NPC またはプレイヤー本人にメッセージを届ける | actor.notify() | 一方向は無料。triggerReply は NPC の返信として課金 |
| キャラクターに今すぐ人格どおりに返答させる | actor.speak() | モデルの標準価格 |
| 特定の連絡先との WeTalk チャットにカードを送る | wechat.sendCard() | triggerReply はデフォルト true で星辰を消費 |
notify はセリフを返さず、受信者が「知った」状態にして自律的に行動させるだけです。即時の返答が必要なら speak を使います。nav.openScene() は内容を伴わない遷移のみで、内容を伴って新しいプロットを起こすには scene.inject() を使います。
以下は審査で必ず確認される項目です。満たさない場合は差し戻されます。
公開フォームで使用する有料機能と発動頻度を申告します。プラットフォームはこれに基づき入場前に統一ダイアログを表示します。虚偽申告は即差し戻しです。
フロー内の自動呼び出し(毎ターンの処理など)は申告すれば許可されます。タイマー / ポーリング / 放置ループでユーザーが操作しなくても課金が続くものは一律差し戻しです。
呼び出し中はボタンを無効化するか loading を表示します。バッチや高額な呼び出しの前には Shine.ui.confirm で確認します。
Shine.ai.image はプラットフォームが 1 枚ごとに承認ダイアログを表示します。ループ / バッチ / 自動連続生成は禁止で、独自の確認ダイアログも不要です。
独自の起動時消費告知やチャージ導線を作らないでください。残高不足はプラットフォームが案内するため、アプリは失敗を catch して UI を復帰させるだけで十分です。
AI が生成したテキスト / 画像を実在の人物・権威機関・事実に見せかけてはいけません。有料導線には「星辰を消費」などの表示を推奨し、有料操作を無料に見せかけることは禁止です。
性的な内容 / 未成年者への誘導 / 過激な暴力は即差し戻しで、悪質な場合はアカウントを凍結します。ai.chat / actor.speak に渡す system prompt もプラットフォームのモデレーション対象です。
そのまま動くリファレンス実装です。単一ファイルのサンプルはワークベンチに読み込んで編集できます。プロジェクト型のサンプルはワークベンチ IDE でリアルタイムにコンパイル・プレビューでき、ZIP をダウンロードしてローカル開発することもできます。
1 つの index.html に全機能を詰め込みました:AI 対話 / 画像生成、ローカルストレージ、UI コンポーネント、ナビゲーションとステータスバー、ストーリーコンテキスト(context / character / contact)、キャラクター発言、ネイティブメディア、ウォレット決済 / 返金——各セクションを個別にタップして試せます。⭐ 付きの呼び出しは星辰を消費します。依存ゼロ・コピーしてすぐ使えるので、機能を素早く試したいときや開発の土台にするのに最適です。
簡易版と同じ機能リストを、React + Vite のマルチファイルプロジェクトに分割した構成です。機能ごとに 1 つの section コンポーネントを用意し、共通の Card / Output / useAction 設計で、構成が明快でスタイルも洗練されています。「サンプルを見る」をタップするとワークベンチ IDE でリアルタイムにコンパイル・プレビューでき、不要な section ファイルを削除すればそのままプロジェクトテンプレートとして使えます。
React + Vite で構築した本格人狼ゲームのプロジェクトです。Shine SDK で AI プレイヤーの発言、ユーザー情報、クラウドセーブを実装しています。「サンプルを見る」からワークベンチ IDE で実際のマルチファイルソースを閲覧し、ブラウザ内でリアルタイムにコンパイル・プレビューできます。ZIP をダウンロードしてローカルで npm 開発し、build した dist で公開することも可能です。
フロントエンドのみ・単一ファイルの五子棋です。着手、勝敗判定、待った、リセットを備え、依存ゼロでコピーすればすぐ遊べます。完成したミニゲームを 1 つの index.html に収める方法を学び、さらに Shine SDK でランキングやシェアなどの遊びを足すのに適しています。
公式の横画面ミニゲームサンプルです。純 Canvas のピンポンで、左のパドルをドラッグして AI と対戦し、先に 7 点取ったほうが勝ちです。横画面の 3 点セット——ui.setOrientation('landscape') での横画面固定、ui.setNavigationBar + ui.setFullscreen でのフルスクリーン没入、max(env(), var(--shine-safe-area-*)) での左右ノッチセーフエリア対応——を重点的にデモします。依存ゼロ、コピーすればすぐ遊べます。
React + Vite 製の「小説を一緒に読む」リーダーです。本棚、没入型リーディング、ナイトモードを備え、Shine SDK で AI 書伴を接続——読みながらキャラクターとストーリーを語り合い、寄り添うコメントをもらえます。「サンプルを見る」から、motion アニメーション付きの完全なマルチファイルプロジェクトをワークベンチ IDE でリアルタイムにコンパイル・プレビューできます。
各メソッドにパラメータ・戻り値・課金・実行環境の制約を明記しています。実装の正は static/sdk/v1/shine-app.src.js です。新しいメソッドは呼び出す前に Shine.canIUse() で確認してください。
ネイティブコンテナ(Flutter WebView)はページ読み込み時に SDK を自動注入し、起動パラメータを書き込むため、通常は手動での init は不要です。ブラウザ / ワークベンチプレビューでは手動でスクリプトを読み込み、init を呼び出してください。
Shine.init({ appId, token?, baseUrl? })| パラメータ | 型 | 必須 | 説明 |
|---|---|---|---|
appId | string (UUID) | はい | MiniApp の appId。ワークベンチプレビューでは 00000000-0000-0000-0000-000000000000 が使えます |
token | string | いいえ | クリエイターのサインイン後の Bearer token。ブラウザから API を直接呼ぶ場合は必須 |
baseUrl | string | いいえ | API のベース URL(例:https://api.shineapp.cn)。末尾の / は付けない |
void<!-- ローカルデバッグ / ワークベンチプレビュー時のみ必要。公開後はプラットフォームが自動注入するため削除して構いません -->
<script src="https://api.shineapp.cn/static/sdk/v1/shine-app.js"></script>
<script>
Shine.init({
appId: '<あなたの appId>',
token: '<サインイン token>',
baseUrl: 'https://api.shineapp.cn',
});
</script>各ネームスペースと並列の機能検出 API。SDK はレンダリング時にリアルタイムでインライン化される(常に最新)一方、ユーザーの App は古いビルドの可能性があります。新しいメソッドは古いコンテナでブリッジ越しに失敗するため、canIUse でフォールバックしてください。
Shine.canIUse(schema)sync現在のコンテナが特定の SDK 機能をサポートしているか検出します。
| パラメータ | 型 | 説明 |
|---|---|---|
schema | string | 機能名。本ドキュメントのメソッド名と同じ表記(例:'device.vibrate' / 'tts.listVoices' / 'actor.notify')* |
boolean — 同期的に返る
if (Shine.canIUse('device.vibrate')) {
await Shine.device.vibrate({ style: 'light' });
}
if (!Shine.canIUse('tts.listVoices')) {
// 旧バージョンの App:ナレーション音声の選択 UI を隠し、テキストのみで進行
}汎用 AI 機能。system / message はクリエイターが完全に制御し、プラットフォームはモデル呼び出しと課金のみを担当します。
Shine.ai.chat(options)asyncAI との対話を 1 回実行し、モデルの返答テキストを返します。
| パラメータ | 型 | 説明 |
|---|---|---|
system | string | クリエイター定義の system prompt。最大 5000 文字* |
message | string | 今回のユーザー入力。最大 2000 文字* |
context | "story" | "actor" | "director" | null | 任意のコンテキストモードのヒント(現行バージョンでは主にクリエイター自身が system に組み込む) · デフォルト null |
stream | boolean | ストリーミングで返すかどうか · デフォルト false |
onChunk | (chunk) => void | ストリーミングコールバック。増分テキストを受け取るたびに呼ばれ、指定すると stream が自動で有効になります。全文が必要な場合は chunk を自分で連結してください(ネイティブコンテナは増分のみを渡し、全文引数はありません) |
Promise<string> — AI の返答本文
const reply = await Shine.ai.chat({
system: 'あなたはタロット占い師。神秘的だが親しみやすい口調で話す。',
message: 'この 3 枚のカードを解釈して:愚者、恋人、運命の輪',
});Shine.ai.image(promptOrOptions)asyncテキストの説明から画像を生成します。
| パラメータ | 型 | 説明 |
|---|---|---|
prompt | string | 画像の説明。最大 1000 文字。{ prompt: string } でも渡せます* |
Promise<string> — 画像の OSS 公開 URL
const url = await Shine.ai.image('水彩画風、窓辺に座る茶トラ猫');
// <img src={url} />クラウド KV ストレージ。ユーザーごとに隔離され(A さんのデータは B さんから見えない)、書き込み後は自動失効せず、削除しない限り残ります。key は最大 255 文字、value は string / number / boolean / object / array に対応。スコープは 2 種類:Shine.storage.*(ユーザー単位、そのユーザーの全セッションで共有)と Shine.storage.session.*(セッション単位、ストーリーごとに独立したセーブデータ。詳細は下記 session.* 参照)。メソッド名は両者で完全に同一です。クォータは「ユーザー + App」でスコープ横断的に合算されます。
Shine.storage.get(key)async指定した key の値を読み取ります。
| パラメータ | 型 | 説明 |
|---|---|---|
key | string | ストレージのキー名* |
Promise<any | null> — 未設定なら null を返す
const save = await Shine.storage.get('game_save');
if (save) board = JSON.parse(save);Shine.storage.set(key, value)asynckey を書き込み、または上書きします。
| パラメータ | 型 | 説明 |
|---|---|---|
key | string | ストレージのキー名* |
value | any | JSON シリアライズ可能な任意の値* |
Promise<void>
await Shine.storage.set('coins', 128);
await Shine.storage.set('inventory', { sword: 1, potion: 3 });Shine.storage.remove(key)async指定した key を削除します。
| パラメータ | 型 | 説明 |
|---|---|---|
key | string | 削除するキー名* |
Promise<void>
Shine.storage.list(opts?)async現在の user+app 配下の key を列挙します(value は含まない)。cursor ページネーション。
| パラメータ | 型 | 説明 |
|---|---|---|
prefix | string | 指定プレフィックスの key のみ列挙 |
cursor | string | 前ページの最後の key |
limit | number | 1 ページの件数。最大 200 · デフォルト 100 |
Promise<{ keys: string[]; nextCursor: string | null; hasMore: boolean }>const page = await Shine.storage.list({ prefix: 'save_' });
for (const k of page.keys) console.log(k);Shine.storage.mget(keys)async複数の key を一括で読み取ります(最大 50 件)。
| パラメータ | 型 | 説明 |
|---|---|---|
keys | string[] | 読み取る key のリスト* |
Promise<Record<string, any | null>>
const vals = await Shine.storage.mget(['coins', 'inventory']); console.log(vals.coins, vals.inventory);
Shine.storage.getInfo()asynckey 数、使用容量、クォータ上限を照会します。
Promise<{ keyCount: number; currentSizeBytes: number; limitKeys: number; limitSizeBytes: number }>Shine.storage.clear(opts?)asynckey を一括削除します(prefix 指定可)。プラットフォームは確認を出さないため、自前で ui.confirm を呼んでください。
| パラメータ | 型 | 説明 |
|---|---|---|
prefix | string | 指定プレフィックスのみ削除 |
Promise<{ status: 'ok'; deletedCount: number }>Shine.storage.session.get / set / remove / list / mget / clear / getInfoasyncセッション単位のストレージネームスペース。メソッドは Shine.storage.* と完全に同一ですが「現在のセッション」で隔離されます。同じユーザーが別のストーリー / セッションから同じ App を開くと、それぞれ独立したセーブデータになり、混ざりません。
対応する Shine.storage.* メソッドと同一
// ユーザー単位:ストーリー横断で共有(設定、お気に入り)
await Shine.storage.set('settings', { sound: true });
// セッション単位:ストーリーごとに独立したセーブ(進行 / 育成 / ミニゲーム)
await Shine.storage.session.set('progress', { chapter: 3, hp: 80 });
const p = await Shine.storage.session.get('progress');現在のユーザーの公開情報。「現在のユーザー」は起動方法によって変わります(getInfo 参照)。
Shine.user.getInfo()async現在のユーザー情報を取得します。ストーリー付きで起動した場合(launch に sessionId あり)はプレイヤーの劇中ペルソナ(マスク / user_role)を返し、actor.speak が注入する user_identity と同じソースです。ストアモードでは実アカウントを返します。返却の構造は変わらないため、場面の区別は不要です。
Promise<{ id: string; nickname: string; avatar: string | null }>// ストーリー付きの場合、nickname はプレイヤーの劇中名(マスク)で、キャラクターから見える名前と一致する
const me = await Shine.user.getInfo();
document.getElementById('name').textContent = me.nickname;ネイティブコンテナ内のシステムレベル UI。ブラウザ環境では confirm / prompt / 簡易 toast にフォールバックします。
Shine.ui.toast(message)async画面上部の短時間トースト。
| パラメータ | 型 | 説明 |
|---|---|---|
message | string | 表示するテキスト* |
Promise<void>
Shine.ui.confirm(message)async確認ダイアログ。
| パラメータ | 型 | 説明 |
|---|---|---|
message | string | 確認テキスト* |
Promise<boolean> — OK で true、キャンセルで false
Shine.ui.prompt(message, defaultValue?)async1 行入力ダイアログ。
| パラメータ | 型 | 説明 |
|---|---|---|
message | string | 入力の説明文* |
defaultValue | string | デフォルト値 · デフォルト "" |
Promise<string | null> — キャンセルで null
Shine.ui.showActionSheet(options)async画面下部のアクションシート。
| パラメータ | 型 | 説明 |
|---|---|---|
options | string[] | 選択肢テキストの配列* |
Promise<number> — 選択項目のインデックス。キャンセルで -1
Shine.ui.setNavigationBar(opts)asyncコンテナ上部のナビゲーションバーを制御します。
| パラメータ | 型 | 説明 |
|---|---|---|
visible | boolean | ナビゲーションバーを表示するか。デフォルトは非表示(フルスクリーン)で、戻る等の操作は右上のネイティブカプセルが担います |
title | string | タイトルテキスト |
backgroundColor | string | 背景色(例:#111827 / transparent) |
Promise<void>
await Shine.ui.setNavigationBar({ visible: false });
/* フルスクリーンページのルートコンテナ:iOS は env()、Android はコンテナ配信の変数を使い、大きい方を採用。縦横回転にも自動対応 */
body {
padding:
max(env(safe-area-inset-top,0px), var(--shine-safe-area-top,0px))
max(env(safe-area-inset-right,0px), var(--shine-safe-area-right,0px))
max(env(safe-area-inset-bottom,0px), var(--shine-safe-area-bottom,0px))
max(env(safe-area-inset-left,0px), var(--shine-safe-area-left,0px));
}Shine.ui.getCapsuleRect()syncネイティブカプセルのページ内での位置を取得します。トップバーを自作する際の回避に使います。
{ visible, top, right, bottom, left, width, height } — 単位 CSS px、同期的に返る/* 純 CSS:自作トップバーの右側にカプセル分の余白を空ける */
.app-header {
padding-top: max(env(safe-area-inset-top,0px), var(--shine-safe-area-top,0px));
padding-right: calc(var(--shine-capsule-width,0px) + var(--shine-capsule-right,0px) + 8px);
}
/* JS:正確な座標が必要な場合 */
const cap = Shine.ui.getCapsuleRect();
if (cap.visible) toolbar.style.maxWidth = cap.left - 12 + 'px';
window.addEventListener('shine:capsulechange', relayout);Shine.ui.setStatusBar({ style })asyncステータスバーの文字色を設定します。
| パラメータ | 型 | 説明 |
|---|---|---|
style | "light" | "dark" | light=白文字(暗い背景用)、dark=黒文字* |
Promise<void>
Shine.ui.setOrientation(orientation)async画面の向きをロック、または解除します。
| パラメータ | 型 | 説明 |
|---|---|---|
orientation | "portrait" | "landscape" | "auto" | 画面の向き* |
Promise<void>
Shine.ui.setFullscreen(enabled)async没入型フルスクリーン:システムステータスバーを非表示 / 復元します。
| パラメータ | 型 | 説明 |
|---|---|---|
enabled | boolean | true=ステータスバーを隠してフルスクリーンへ、false=復元* |
Promise<void>
// フルスクリーンゲーム / 画像ビューア / 動画など、邪魔のない場面に最適
await Shine.ui.setNavigationBar({ visible: false });
await Shine.ui.setFullscreen(true);
// ページを離れる / 復元が必要なとき
await Shine.ui.setFullscreen(false);コンテナ・デバイス情報、クリップボード、触覚フィードバック。これらは WebView 内では不安定またはそもそも存在しないため、ネイティブコンテナが提供します。
Shine.device.getSystemInfo()asyncプラットフォーム、ホストアプリのバージョン、ライト / ダークテーマ、画面サイズとセーフエリアを取得します。
Promise<{ platform: 'ios'|'android'|'other'; appVersion: string; theme: 'light'|'dark'; locale: string; screen: { width, height, pixelRatio }; safeArea: { top, right, bottom, left } }>const info = await Shine.device.getSystemInfo(); document.documentElement.dataset.theme = info.theme;
Shine.device.setClipboard({ text })asyncシステムクリップボードに書き込みます。
| パラメータ | 型 | 説明 |
|---|---|---|
text | string | コピーするテキスト。文字列を直接渡すことも可能* |
Promise<{ ok: boolean }>await Shine.device.setClipboard({ text: inviteCode });
await Shine.ui.toast('招待コードをコピーしました');Shine.device.getClipboard()asyncシステムクリップボードのテキストを読み取ります。
Promise<{ text: string }>const { text } = await Shine.device.getClipboard();
if (text) input.value = text.trim();Shine.device.vibrate({ style? })async短い振動の触覚フィードバック(ゲームのコンボ、ボタン押下など)。
| パラメータ | 型 | 説明 |
|---|---|---|
style | 'light' | 'medium' | 'heavy' | 'selection' | 触覚の強さ。selection はピッカーの軽いタップ向き · デフォルト 'light' |
Promise<{ ok: boolean }>if (Shine.canIUse('device.vibrate')) {
await Shine.device.vibrate({ style: combo > 1 ? 'medium' : 'light' });
}起動コンテキスト。同期メソッドで、ネットワークリクエストは発生しません。
Shine.context.getLaunchOptions()syncコンテナが MiniApp 起動時に渡したパラメータを読み取ります。
{ sessionId?: string; characterId?: string } | null
ストアから直接起動 → null、ストーリーから起動 → sessionId を含むconst launch = Shine.context.getLaunchOptions();
if (!launch) {
// ストアモード:フレンドリーな空状態を表示
return;
}
console.log(launch.sessionId, launch.characterId);アタッチされたストーリーの公開メタ情報。world_scenario / personality などの内部 prompt フィールドは返しません。
Shine.character.getDetail(characterId?)asyncアタッチされたストーリーの公開詳細を取得します。
| パラメータ | 型 | 説明 |
|---|---|---|
characterId | string (UUID) | 省略時は launch.characterId を使用 |
Promise<{ id, name, brief, avatar_url, cover_url }>const story = await Shine.character.getDetail(); titleEl.textContent = story.name;
現在のセッションの連絡先:ストーリーの actor + ユーザーが作成した NPC。
Shine.contact.list()async連絡先一覧を取得します。
Promise<Array<{ id: string; name: string; avatar_url: string | null; is_custom_npc: boolean }>>const contacts = await Shine.contact.list(); const partner = contacts[0];
ストーリー内のキャラクター / NPC に設定どおり発言させます。バックエンドの miniapp_prompt_manager が世界観、ペルソナ、記憶、履歴を自動で組み立てます。
Shine.actor.speak(opts)async指定した actor にセリフを 1 つ生成させます。
| パラメータ | 型 | 説明 |
|---|---|---|
actorId | string | contact.list() が返す id(UUID または NPC:xxx)* |
message | string | 今回の指示 / ユーザー入力。最大 2000 文字* |
extraSystem | string | 追加の system。ゲームルールや現在の局面など。最大 5000 文字 |
includeWorldScenario | boolean | ストーリーカード / チャプター / プロット設定を注入 · デフォルト true |
includeActorPersona | boolean | actor の身元とペルソナを注入。オフにすると匿名 NPC に退化 · デフォルト true |
includeActorMemory | boolean | その actor の関係 / 経験の記憶を注入。persona=true のときのみ有効 · デフォルト true |
includeOtherActors | boolean | 他 actor のキャスト一覧を注入。人狼など情報隔離が必要な場面ではオフに · デフォルト true |
includeChatHistory | boolean | バーチャルフォンの時系列チャット履歴を注入。ゲーム系は通常 false のまま · デフォルト false |
customHistory | Array<{role:'user'|'assistant', content:string}> | App 側で管理する会話履歴。最大 50 件、各 4000 文字以内 · デフォルト [] |
Promise<string> — actor のセリフ本文
const reply = await Shine.actor.speak({
actorId: partner.id,
message: 'この先の展開、どうなると思う?',
extraSystem: '二人は第三章を一緒に読んでいる。キャラクターの口調を保つこと。',
includeWorldScenario: true,
includeActorPersona: true,
customHistory: [
{ role: 'user', content: '前のターンで何を話した?' },
{ role: 'assistant', content: '主人公の選択について話していたね…' },
],
});Shine.actor.notify(opts)async任意の差出人になりすまして、受取人へ(デフォルト一方向の)メッセージを届けます——密告 / 接触 / 通知 / 警告 / キャラクターからプレイヤーへの DM など。
| パラメータ | 型 | 説明 |
|---|---|---|
actorId | string | 受取人の actor id(contact.list から)。'user' を渡すとプレイヤー本人へ(受信箱に可視で届く)* |
message | string | 届けるメッセージ本文。最大 2000 文字* |
fromActorId | string | 既存の actor を差出人にする(あるキャラクターからの DM など)。指定すると下の from* 3 フィールドは無視される |
fromName | string | 差出人の表示名(匿名の場面では「不明な番号」「匿名の情報提供者」などを渡す) · デフォルト 本 App 名 |
fromAvatar | string | 差出人のアバター URL |
fromPersona | string | 差出人のペルソナ。省略時は「本 App から、このメッセージを通じて相手と接触した連絡先」という中立テンプレートを適用 |
triggerReply | boolean | 受取人(NPC)にこのメッセージへ即座に反応させるか(NPC 同士のチャット深度の有効化が必要。user への配達では無効) · デフォルト false |
Promise<{ status: 'ok' | 'blocked'; delivered: boolean }>const contacts = await Shine.contact.list();
const cop = contacts.find(c => c.name === '張刑事');
// ① ダークウェブ経由で NPC に匿名の密告:差出人を自由に設定、ユーザーにはこの DM は見えない
await Shine.actor.notify({
actorId: cop.id,
message: '今夜、東の倉庫で取引がある。急げ。',
fromName: '不明な番号',
fromPersona: '正体不明の情報提供者。口数が少なく、決して自分の素性を明かさない。',
});
// ② プレイヤー本人へ配達:メッセージは WeTalk の受信箱に可視で届く
await Shine.actor.notify({
actorId: 'user',
message: '【システム】匿名の脅迫状が届いた……',
fromName: '匿名の手紙',
});
// ③ 既存キャラクターからプレイヤーへ DM(actor → user)
await Shine.actor.notify({
actorId: 'user',
fromActorId: cop.id,
message: '俺だ、張だ。今すぐ伝えなければならないことがある。',
});テキストを読み上げます。音声の指定は 2 通り:voiceId はプラットフォームの音声カタログの音声を使用(ナレーション、システム読み上げ、ストアから直接開いた App はこちら。ストーリーもキャラクターも不要)。actorId はストーリー内のそのキャラクター自身の音声を使用(voice_config、未設定なら性別に応じたデフォルト音声にフォールバック)。ネイティブコンテナではネイティブプレイヤー(just_audio)がストリーミング mp3 をダウンロードしながら再生し、最初の音が速く安定します。再生中は BGM が自動で小さくなり、読み終わると元に戻ります。プラットフォームの MiniMax TTS を再利用しており、同じテキスト+音声はキャッシュにヒットして再課金されません。
Shine.tts.listVoices({ gender?, language? })asyncプラットフォームの音声カタログを取得します。id を取得すればすぐ読み上げ可能——ストーリーもキャラクターも不要です。
| パラメータ | 型 | 説明 |
|---|---|---|
gender | "male" | "female" | 特定の性別の音声のみ取得 |
language | string | 言語コード zh/yue/en/ja/ko。'all'=全言語、省略=中国語系 |
Promise<Array<{ id: string; name: string; gender: string; tags: string[]; languages: string[]; previewUrl: string | null }>>const voices = await Shine.tts.listVoices({ gender: 'female' });
narratorVoiceId = voices[0].id; // 設定でユーザーに選ばせて保存する
await Shine.tts.speak({ voiceId: narratorVoiceId, text: '夜が更け、あなたはその扉を押し開けた。' });Shine.tts.speak({ voiceId | actorId, text, speed?, pitch?, languageBoost? })async合成して再生します(同時に 1 本のみ。新しい呼び出しは前のものを中断)。Promise は再生終了時に resolve します。
| パラメータ | 型 | 説明 |
|---|---|---|
voiceId | string | プラットフォームの音声 id(listVoices 参照)。actorId と二者択一で、指定時は actorId を無視 |
actorId | string | contact.list() が返す id。actor.speak と同じもの。キャラクター自身の音声を使用 |
text | string | 読み上げるテキスト。最大 10000 文字。タグ / ト書きは合成前にクリーニングされる* |
speed | number | 話速 0.5~2.0。省略時は音声自体の設定を継承 |
pitch | number | ピッチ -12~12。省略時は音声自体の設定を継承 |
languageBoost | string | 言語ブースト · デフォルト "Chinese" |
Promise<{ audioUrl: string; durationMs: number; usageCharacters: number; cached: boolean; cost: number }>// ナレーション:ストーリーもキャラクターも不要
await Shine.tts.speak({ voiceId: narratorVoiceId, text: '三日目の朝、雨は上がった。', speed: 0.9 });
// キャラクターのセリフ:本人の音声で
const reply = await Shine.actor.speak({ actorId: him.id, message: '君の番だ' });
await Shine.tts.speak({ actorId: him.id, text: reply });Shine.tts.synthesize({ voiceId | actorId, text, speed?, pitch?, languageBoost? })async再生可能な URL の解決だけを行い、再生はしません。カスタム再生やプリフェッチに便利です。
| パラメータ | 型 | 説明 |
|---|---|---|
voiceId | string | プラットフォームの音声 id。actorId と二者択一 |
actorId | string | contact.id。キャラクター自身の音声を使用 |
text | string | 読み上げるテキスト。最大 10000 文字* |
speed | number | 話速 0.5~2.0 |
pitch | number | ピッチ -12~12 |
languageBoost | string | 言語ブースト · デフォルト "Chinese" |
Promise<{ audioUrl: string; durationMs: number; usageCharacters: number; cached: boolean; cost: number }>const { audioUrl } = await Shine.tts.synthesize({ voiceId: narratorVoiceId, text: '落ち着いて、状況を見極めろ。' });
new Audio(audioUrl).play();Shine.tts.stop()syncspeak で再生中の音声があれば即座に停止します。
void
バックグラウンドミュージック(BGM)。ネイティブコンテナではネイティブプレイヤーが担当:ユーザー操作なしで再生を開始でき、Shine.tts.speak の再生中は BGM が自動で小さくなり、読み終わると元に戻ります(ダッキング自動)。ブラウザプレビューは <audio> でフォールバック(最初の操作がないと再生できない場合があります)。
Shine.audio.playBgm({ url, loop? })asyncBGM をループ再生します(同時に 1 本のみ。再度呼ぶと切り替え)。
| パラメータ | 型 | 説明 |
|---|---|---|
url | string | 音声の URL(mp3 など)。文字列を直接 url として渡すことも可能* |
loop | boolean | ループするかどうか · デフォルト true |
Promise<boolean>
Shine.audio.playBgm({ url: 'https://.../bgm.mp3' });Shine.audio.stopBgm()asyncBGM を停止します。
Promise<boolean>
ネイティブデバイス機能。Flutter コンテナ内でのみ利用可能で、ブラウザでは reject します。
Shine.media.pickImage(opts?)asyncアルバムから画像を選択、または撮影します。
| パラメータ | 型 | 説明 |
|---|---|---|
source | "album" | "camera" | "both" | both の場合はボトムシートでユーザーに選ばせる · デフォルト "both" |
Promise<string | null> — OSS 公開 URL。ユーザーがキャンセルすると null
Shine.media.recordVoiceText(opts?)async長押しで録音し、音声認識テキストを返します。
| パラメータ | 型 | 説明 |
|---|---|---|
maxDuration | number | 最大録音秒数。ハード上限 180 · デフォルト 60 |
Promise<string | null> — 認識テキスト。キャンセルまたは内容なしの場合は null
コンテナの可視性イベント(ミニプログラムの onShow/onHide に対応)。
Shine.lifecycle.onShow(callback)syncMiniApp が可視になったときに発火します。
| パラメータ | 型 | 説明 |
|---|---|---|
callback | () => void | コールバック* |
void
Shine.lifecycle.onHide(callback)syncMiniApp が不可視になったときに発火します。
| パラメータ | 型 | 説明 |
|---|---|---|
callback | () => void | コールバック* |
void
Shine.lifecycle.off(event, callback?)syncリスナーを解除します。
| パラメータ | 型 | 説明 |
|---|---|---|
event | "show" | "hide" | イベント名* |
callback | () => void | 省略するとそのイベントの全リスナーを解除 |
void
オフラインの小劇場:MiniApp が生み出したコンテンツを現在のオフラインセッションに永続化し、「新しい展開」を発火させます。永続化 + バックエンドからの通知で、画面遷移はしません——ユーザーは「新しい展開」の通知を受け取り、自分から入って続きを書きます。ストーリーセッションからの MiniApp 起動が必要です。
Shine.scene.inject(opts)asyncコンテンツを現在のオフラインセッションに永続化し、「新しい展開」通知を発火します。
| パラメータ | 型 | 説明 |
|---|---|---|
visible | string | 表示コンテンツ:ナレーション / 情景描写としてオフラインのタイムラインに表示され、新展開通知を発火 |
hidden | string | 非表示コンテンツ:prompt に入り NPC は知っている状態になるが、吹き出しは表示されない(演出メモ / 伏線 / ミニゲームの結果) |
orderId | string | 冪等キー。同じ orderId は 1 回だけ永続化(連打 / リトライ対策) |
Promise<{ status: string; injectedVisible: boolean; injectedHidden: boolean; idempotent: boolean }>// ミニゲームで鍵を獲得 → 新展開を永続化。ユーザーは「新しい展開」の通知を受けて自分でオフラインに入り続きを書く
await Shine.scene.inject({
visible: '勝ち取ったばかりの銅の鍵を握りしめ、あなたは長年封じられた木の扉を押し開けた。',
hidden: '[演出] プレイヤーはミニゲームで地下室の鍵を獲得。NPC はまだ知らない。サスペンスを演出できる。',
orderId: 'scene-' + Date.now(),
});プラットフォーム星辰の読み取り専用照会(課金なし)。ルール:課金機能(ai.chat / ai.image / actor.speak / tts.speak)を使う App は、公開申請フォームの「星辰消費の申告」を正確に記入するだけで OK——ユーザーの入場前にプラットフォームが統一の告知ダイアログを表示します(リアルタイム単価を自動で添付)。App 内に独自の起動時告知ダイアログを追加しないでください(「AI 利用ルール」参照。審査で必ずチェックされます)。
Shine.credits.getBalance()async現在のユーザーの消費可能な星辰残高を読み取ります。
Promise<{ total: number; free: number; membership: number; permanent: number }>const bal = await Shine.credits.getBalance();
if (bal.total < 2) await Shine.ui.toast('星辰が足りません');Shine.credits.getCost({ feature })async機能 1 回あたりの星辰消費の見積もりを照会します。
| パラメータ | 型 | 説明 |
|---|---|---|
feature | "ai.chat" | "ai.image" | "actor.speak" | 機能の識別子* |
Promise<{ feature: string; cost: number; currency: 'credits' }>WeTalk のストーリー内バーチャルウォレット(単位「分」)。プラットフォームの星辰とは無関係です。ストーリーセッションからの起動が必要。
Shine.wallet.getBalance()async現在のユーザーの本セッションにおける WeTalk ウォレット残高を読み取ります。
Promise<{ balanceCents: number; balanceYuan: number }>Shine.wallet.pay(opts)async支払いを行います(先に支払い確認ダイアログを表示)。
| パラメータ | 型 | 説明 |
|---|---|---|
orderId | string | 冪等キー(36 文字以内)。同一取引には同じ値を使い回し、リトライでも二重請求されない。UUID を連結しないよう注意(長すぎてバックエンドに拒否される)* |
amountCents | number | 金額(分)、正の整数* |
note | string | 用途の説明。明細に記録される |
Promise<{ status: 'ok' | 'cancelled'; charged?: boolean; balanceCents: number; balanceYuan: number }>// orderId は本 App 内・ユーザーごとの冪等キー。36 文字以内、ハイフン区切りの連番で十分
const res = await Shine.wallet.pay({
orderId: 'shop-sword-001',
amountCents: 990,
note: '鉄の剣を購入',
});
if (res.status === 'cancelled') return;Shine.wallet.refund(opts)async指定した orderId の支払い済み金額を払い戻します。
| パラメータ | 型 | 説明 |
|---|---|---|
orderId | string | 払い戻す取引の orderId(36 文字以内)* |
Promise<{ status: string; refundedCents: number; balanceCents: number; balanceYuan: number }>Shine.wallet.credit(opts)asyncユーザーの WeTalk ウォレットに入金します(バーチャルウォレットの「チャージ / 報酬付与」。確認ダイアログなし)。
| パラメータ | 型 | 説明 |
|---|---|---|
orderId | string | 冪等キー(36 文字以内)。同一入金には同じ値を使い回し、リトライでも二重入金されない* |
amountCents | number | 金額(分)、正の整数* |
note | string | 用途の説明。明細に記録される |
Promise<{ status: 'ok'; credited: boolean; balanceCents: number; balanceYuan: number }>WeTalk:カードを現在のセッションの DM に送ります。プラットフォーム既存の link カードレンダリングを再利用。ストーリーセッションからの起動が必要です。
Shine.wechat.sendCard(opts)async「ユーザー」として、ある連絡先との WeTalk DM にカードを 1 枚送ります。
| パラメータ | 型 | 説明 |
|---|---|---|
to | string | 受け取る連絡先の actor id(contact.list から)* |
title | string | カードのタイトル* |
desc | string | カードの本文 / 要約 |
subtitle | string | サブタイトル |
imageUrl | string | サムネイル URL |
footer | string | フッターの出典ラベル。省略時は App 名 |
triggerReply | boolean | 送信後に相手 NPC の返信を発火させるか(デフォルト true、星辰を消費) |
Promise<{ status: 'ok' | 'blocked'; sent: boolean }>const contacts = await Shine.contact.list();
await Shine.wechat.sendCard({
to: contacts[0].id,
title: '今日の運勢:大吉',
desc: '外出と告白に吉、夜更かしは凶。',
footer: '運勢ミニアプリ',
});すべての async メソッドは失敗時に reject(Error) します。ネイティブコンテナと HTTP 直結のどちらも、バックエンドの detail を読みやすい message に変換します。
課金系 API(ai.chat / ai.image / actor.speak / tts.speak)は残高不足や承認拒否時にエラーを投げます。ストーリー機能(actor / contact / wallet / scene)は sessionId がないと即 reject します。media.* はブラウザ環境では reject します。
try {
const reply = await Shine.actor.speak({ actorId, message: 'こんにちは' });
} catch (e) {
await Shine.ui.toast(e.message || '呼び出しに失敗しました');
}