本文へ移動
Bounty Developer

作業ガイド

SPIKES の運営メンバーが、やりたいことから画面と手順を引くための頁である。作業ごとに、開く画面、操作の順番、誰がやれるか、同じことを API や AI(MCP)でするにはどうするかを書いている。

画面のパスの中の「<イベント>」「<募集>」のような印は、開いているイベントや募集ごとに変わる部分である。<イベント> はイベントの URL の名前に置き換える。印の無いパスは押すとその画面が開く(ログインと権限が要る)。

やりたいこと

権限の呼び方

各作業の「誰がやれるか」は、次の呼び方で書いている。

運営スタッフ
SPIKES の運営の役割を持つ人。主のイベント(SPIKES が主催する本体のイベント)の管理の画面を開ける。
運営の管理者
運営の役割のうち管理者の人。どのイベントでも、そのイベントの管理者として扱われる。API トークンの発行は運営の管理者だけができる。
イベントの管理者
4S でそのイベントの管理者・編集者になっている人。主のイベント以外のイベントは、この人たちが管理の画面を使う。確かめてから時間が経っていると、管理の画面を開いたときに 4S で確かめ直す。
団体の管理者
ギルド・学生発ベンチャーに在籍中のオーナーと管理者。団体の募集と名簿を扱う。運営であっても団体の中のことは代わりに扱わない。

タイムテーブル(企画)を作って公開する

コンテンツを日とステージの格子に置き、登壇者を確定し、検査を通してから公開する。

誰がやれるか

主のイベントでは運営スタッフが開ける。ステージそのものの管理と担当者の選択は運営の管理者だけが行う。ほかのイベントでは、そのイベントの管理者が同じ段の画面を使う。公開と公開の取り下げは、運営かイベントの管理者がブラウザの画面で行う。

手順

  1. ラインを用意する。ラインはステージを受け持つ担当の組で、名前・点の色・受け持つステージ・既定のスタッフを持つ。日程の札の色はラインの色になる。

    • /events/<イベント>/manage/lines
  2. コンテンツを作り、日・ステージ・時刻の格子に置く。下書きの間は同じステージで時間が重なってもよく、画面に重なりとして出る。

    • /events/<イベント>/manage/content
  3. 登壇者を足す。運営がプロフィールの提案を書き、本人が限定リンクから直し、人が画面で確定して初めて掲載の内容になる。確定する前の紹介文や写真は公開に出ない。

    • /events/<イベント>/manage/speakers
  4. 検査を回す。公開を止める指摘(重なり・時刻やステージの欠け・長さの誤り)は直してから進む。止めない指摘(会期の外・格子からのずれ・ラインや登壇者の欠け)は内容を見て判断する。

    • /events/<イベント>/manage/content
  5. 公開する。公開したコンテンツだけが、参加者の日程・会場図・チェックインの QR と、公開の API に出る。下書きに戻すと同じ URL は外から読めなくなる。

気を付けること

  • 同じブロックを複数の人が同時に直したときは、後から保存した側が今の状態を受け取り、上書きはしない。保存できなかったら画面を読み直してから直す。
  • 企画の一覧の「公募を作る」から、その企画に合う募集の下書きを作れる(次の作業)。

API と MCP で同じことをするには

企画の API で同じ下書きを組める。ブラウザのログイン、アプリのトークン、コンテンツの範囲を持つ API トークンのどれでも呼べる。MCP からは時間割・ライン・登壇者・サムネイルの下書きまでで、公開・登壇者の確定・限定リンクの発行は人が画面で行う。公開中のコンテンツへのトークンからの書き込みは断られる。

続けて読む

募集(BOSHU)を作って公開し、応募を審査する

募集を作って質問を組み、文面を確かめて公開し、応募を審査して結果を通知する。

誰がやれるか

募集の持ち主で決まる。運営と主のイベントの募集は運営スタッフ、ほかのイベントの募集はそのイベントの管理者、ギルド・ベンチャーの募集は団体の管理者が扱う。ギルド・ベンチャーの募集の公開の承認は、システム管理(/admin)に入れる運営スタッフが行う。

手順

  1. 募集を作る。イベントの募集は、そのイベントの管理の募集の一覧から作る。主のイベントの一覧の「新規作成」は運営の募集を作る。団体の募集は団体の管理の画面から作る。

    • /events/<イベント>/manage/boshu
    • /guilds/<団体>/manage/boshu/new
    • /ventures/<団体>/manage/boshu/new
  2. 題・説明・応募の条件と、追加の質問を組む。質問はモジュールアンケートで、棚のモジュールを置いて作る(次の次の作業)。

    • /events/<イベント>/manage/boshu/<募集>/edit
  3. ギルド・ベンチャーの募集は承認待ちで作られ、運営が承認するまで参加者に見えない。承認の後に、運営の管理者でない持ち主が中身を直すと承認待ちに戻る。

  4. 応募と結果のメールの文面を確認してから公開する。公開・受付中・承認済みがそろうと、持ち主に応じた宛先へ一度だけ告知が出る。

    • /events/<イベント>/manage/boshu/<募集>
  5. 応募を審査する。応募ごとに保留・採択・補欠・見送りを付け、まとめて決めることも、採択した人の出演順を並べることもできる。応募者の詳細で回答を読む。

    • /events/<イベント>/manage/boshu/<募集>/applicants/<応募>
  6. 「結果を通知」で、まだ知らせていない応募にまとめて結果を届ける。本人に見えるのは知らせた後の決定だけで、それまでは審査中に見える。

気を付けること

  • 先着の募集は応募の時点で受付が確定し、審査の段は無い。
  • 登壇・ピッチの採択を通知すると、対象のコンテンツの登壇者としてタイムテーブルへ写る。
  • 個人情報の印を付けた質問の回答は、運営の管理者とその募集の管理者だけが読める。審査の画面・CSV・外部の API で同じ判定になる。

参加登録・チケットの設問を直す(モジュールアンケート)

イベントの「フォーム」で、登録フォームとチケットの設問をモジュールの並びで組む。

誰がやれるか

そのイベントの管理者(4S でそのイベントの管理者・編集者の人と、運営の管理者)。募集の追加の質問は、その募集を扱える人が募集の編集の画面で直す。

手順

  1. イベントの管理の「フォーム」を開き、「登録フォーム」と「チケットの設問」を切り替える。チケットの画面の系統の「設問」からも同じ画面へ移る。切り替えても直しかけの内容は消えない。

    • /events/<イベント>/manage/forms
    • /events/<イベント>/manage/tickets
  2. 棚のモジュールを「追加」でページの最後へ置き、つまみを掴んで並べ替える。キーボードでも並べ替えられる。その設問だけの質問は固有のモジュールとして足す。

  3. プレビューで、答える人にどう見えるかを確かめる。登録済みの値は引用され、欠けている欄だけを聞く形になる。

  4. チケットの設問は、下書きを直してから公開する。募集の設問は保存がそのまま新しい版になり、古い応募は答えたときの文言のまま読める。

気を付けること

  • プロフィールのページには共通モジュールしか置けない。本人に残す回答の鍵をイベントをまたいでそろえるためである。
  • ログインが要るモジュールを置いた募集は、応募のログインを「不要」にできない。組み立ての画面がその理由を出す。
  • 団体と登録者の段は直せないシステムのページで、鍵付きの「固定」のカードとして出る。

API と MCP で同じことをするには

MCP の道具で、棚を読む・設問をモジュールの形で読む・操作を試す・操作を当てる、ができる。試す道具は保存と同じ処理を走らせて必ず巻き戻し、版が増えるかや承認のやり直しが起きるかを返す。共通モジュールそのものの編集と公開は人が画面で行う。

続けて読む

団体の申請を審査する

学生団体・学生発ベンチャーの登録申請を読み、承認するか見送る。

誰がやれるか

団体の登録の審査は、システム管理(/admin)に入れる運営スタッフが行う。イベントのパートナー募集に申し込んだ団体の参加は、そのイベントの運営が団体の審査の画面で判定する。

手順

  1. 団体の登録審査を開く。審査待ちの申請が並び、審査を終えた申請は審査履歴に並ぶ。

  2. 団体名を押して詳細を開き、フォームの回答・招待・法人番号の照合を確かめる。

    • /admin/orgs/<申請>
  3. 承認するか見送る。承認すると団体が公開され、申請者が在籍中のオーナーになる。見送りでは公開しない。結果は本人へ通知される。学生発ベンチャーは、学生発の定義を満たさなければ承認できない。

  4. パートナー募集の申込は、イベントの管理の団体の審査で判定する。ほかのイベントでは、SPIKES の運営が団体の登録を判定するまで、ここでは判定できない。

    • /events/<イベント>/manage/partners/review

気を付けること

  • 承認前の団体は、団体のページ・一覧・ランキングのどれにも出ない。
  • 同じ名前の団体があれば新しい登録は断られ、その団体の管理の申し出へ案内される。

API と MCP で同じことをするには

審査は運営の画面で行う。公開の API で読めるのは、承認済みの団体と在籍の情報で、名前・メール・連絡先・申請の回答は載らない。

続けて読む

会場図とブースを用意する

会場図にブースの枠を置き、出展者を割り当て、チェックインの QR を用意する。

誰がやれるか

主のイベントの運営スタッフが開ける。ブース配置の操作は運営の管理者だけで、運営スタッフは見るだけである。会場図・ブース・チェックインの記録を持つのは、今は主のイベントだけである。

手順

  1. 会場図をつくる画面で、図面にステージ・ブースの枠と番号を置く。

    • /events/<イベント>/manage/venue/design
  2. 出展の画面で、出展者(ブース)を登録する。ブースはロゴ・カバー・担当者の顔と名前を持てる。

    • /events/<イベント>/manage/exhibitions
  3. ブース配置の画面で、出展者をブースの枠へドラッグで割り当て、確かめてから公開する。割当は全日か日付ごとで、同じ日に同じ枠へ複数のブースは置けず、同じブースを複数の枠にも置けない。

    • /events/<イベント>/manage/venue/placement
  4. 会場の画面で、セッションとブースのチェックインの状態を確かめる。掲示する QR は QR 印刷の画面から印刷する。

    • /events/<イベント>/manage/venue
    • /events/<イベント>/manage/venue/print

気を付けること

  • 出展者が決まっていない枠は、参加者の会場図にピンを立てない。
  • 資料からセッション・ブース・会場図をひとまとまりで取り込む入口もある。取り込みは審査待ちに送るところまでで、公開は人が決める。

API と MCP で同じことをするには

出展ブースの情報と、会場図のブースの枠と番号は MCP で下書きできる。その図を現行にするのは人である。公開されたプログラムと現行の会場図は、ログインの要らない公開の API から読める。

続けて読む

AI(MCP)に下書きを作らせる

Claude や ChatGPT を BOUNTY につなぎ、文で頼んで下書きを作らせ、画面で確かめて公開する。

誰がやれるか

BOUNTY のアカウントでつなぐ。AI ができることは、ログインした本人の権限と所属の範囲までである。外部のシステムからトークンで呼ぶための API トークンは、運営の管理者だけが発行できる。

手順

  1. MCP の接続案内を開き、使っている AI(Claude・ChatGPT・Claude Code・Codex など)の手順を見る。

  2. AI のコネクタに接続先の URL を貼り、BOUNTY でログインして許可する。トークンを貼る手順は無い。

  3. やりたいことを文で頼む。頼める範囲は下の「AI に頼めること」の一覧のとおりで、AI は適切な道具を選んで非公開の下書きを作る。

  4. BOUNTY の画面で下書きを確かめ、人が公開・承認する。MCP で入れた企画の下書きには、公開の前に内容を確かめる印が付く。

  5. つなぐのをやめるときは、マイページの「連携中のアプリ」から解除する。

  6. 外部のシステムからトークンで呼ぶときは、運営の管理者が API トークンを発行し、使える入口を発行時に選ぶ。鍵は発行時だけ表示される。

API と MCP で同じことをするには

MCP の道具は、人が画面で使うのと同じ API を通る。ただし届くのは下書きと提案までで、公開・承認・確定は人が画面で行う。どの道具が何をするかは、MCP の接続案内と、各機能の頁の API と MCP の説明に書いてある。

AI に頼めること

  • 募集募集・設問・選考・自動返信の下書き
  • 会社ページ紹介文・ロゴ・メンバー
  • プログラムセッションと登壇の下書き
  • 企画時間割・ライン・登壇者
  • 出展ブースブースの情報とロゴ
  • 会場マップ図面にブースを配置

続けて読む

外部のサイトに応募フォームを埋め込む

公開した募集の「共有」から埋め込みコードを写し、外部のページに貼る。

誰がやれるか

その募集を扱える人が埋め込みコードを写せる。埋め込める先のサイトは運営が設定し、設定の無いサイトの中ではフォームは開かない。

手順

  1. 募集を公開する。コードを写すだけでは募集は公開されない。

    • /events/<イベント>/manage/boshu/<募集>
  2. 募集の管理の画面の「共有」を開く。応募リンクと、埋め込み先が設定されていれば埋め込みコードが出る。未設定なら運営へ設定を頼む。

  3. 「埋め込みコードをコピー」で iframe のコードを写し、外部のページに貼る。

  4. 埋め込みでも、応募は同じ仕組みと同意を通る。応募する人は別の窓で 4S のログインか登録をして、同じ枠の中で入力・応募・編集・取り下げをする。

  5. 埋め込めないページでは、応募リンク(独立した応募の画面)をそのまま案内する。

    • /forms/boshu/<募集>

API と MCP で同じことをするには

外部のページの JavaScript からは、ログインなしで募集のフォームの定義(公開中の質問の版と入力の形)を読める。応募・回答・本人の情報は返さない。応募を外部のシステムで扱うときは、運営の管理者が発行したトークンで募集の外部 API を呼ぶ。

続けて読む

外部サイトに会場マップとタイムテーブルを出す

公開したタイムテーブルと会場図を、ログインの要らない公開の API から外部のページで読み、自分のデザインで描く。

誰がやれるか

読むだけなら誰でもよく、ログインもトークンも要らない。どのサイトからでもブラウザの JavaScript で読める。出る中身を決めるのは、タイムテーブルと会場図を公開する運営かイベントの管理者である。

手順

  1. タイムテーブルを公開する。外部のページに出るのは公開したコンテンツだけで、下書き・非公開・審査で隠したものは出ない(「タイムテーブル(企画)を作って公開する」)。

    • /events/<イベント>/manage/content
  2. 会場図とブース配置を公開する。出るのは現行のベース図とブースの枠と、確定した割当だけで、仮の割当や下書きの出展者は出ない(「会場図とブースを用意する」)。

    • /events/<イベント>/manage/venue/placement
  3. イベントの URL の名前を確かめる。公開の API はイベントの URL の名前で引く。イベントが無いか非公開なら、決まった誤りの印だけが返る。

    • /events/<イベント>
  4. 外部のページから企画の全体を一度で読む。日・ステージ・ライン・登壇者・コンテンツ・会場とフロア・会場図が一つの応答に入っている。下の見本をそのまま貼ると、日ごと・ステージごとのタイムテーブルと、ブースの枠を重ねた会場図が出る。

  5. タイムテーブルを組む。時刻のあるコンテンツを日で分け、ステージの並びごとに開始の時刻の順で並べる。時刻は世界時で届くので、イベントのタイムゾーンで表示する。

  6. 会場図を描く。ベース図を画像として置き、同じ座標(viewBox の単位)でブースの枠を重ね、選んだ日の割当から出展者を引く。色や枠の見た目はサイトの側で決める。

  7. 空のときの表示を決める。公開したものがまだ無ければ日・コンテンツ・会場図は空で返るので、「まだありません」を出す。イベントが無い・非公開になったときは枠ごと隠す。

  8. 読み直しの間隔を決める。応答は同じ中身なら同じ印(ETag)を持ち、ブラウザは変わっていなければ本文を読み直さない。当日の差し替えや取り下げを拾うため、ページを開いたときと数分おきに読み直す。

気を付けること

  • 実際の外部サイトでの表示の確認は、出す先のサイトが決まってから、そのサイトの上で行う。
  • 応答の版(schemaVersion)を確かめ、知らない版なら描かずに知らせる。同じ版のまま欄が足されることがあるので、知らない欄は読み飛ばす。
  • 取り下げたコンテンツ・ロゴ・写真・会場図の画像は、同じ URL で読めなくなる。画像を外部のサイトに写して置かず、応答の URL をそのまま使う。サムネイルは絵の版が URL に入るので、絵が変われば URL も変わる。
  • ブースの番号は会場・フロアごとに一意で、会場が違えば同じ番号がありうる。会場を分けたイベントでは、番号に会場・フロアの名前を添える。
  • API の文字(題・説明・名前)は HTML として差し込まず、文字として入れる。

応答の欄

event
イベントの名前・URL の名前・タイムゾーン・会期・会場の名前。時刻の表示はこのタイムゾーンを使う。
days
日の一覧(イベントのタイムゾーンの暦日)。会期と、公開中のコンテンツのある日。タブや見出しの並びにそのまま使う。
stages
ステージ(名前・短い記号・並び・会場とフロア)。タイムテーブルの列はこの並びにする。
lines
ステージを受け持つライン。色は名前で届くので、サイトの色に対応させる。
speakers
公開中のコンテンツに出る登壇者。名前・肩書・会社は掲載の内容、紹介文・写真・英語の欄はプロフィールを確定した人だけで、ほかは空。
contents
公開中のコンテンツ。schedule が timed なら day と開始・終了の時刻(世界時)とステージを持ち、allday は会期を通して、unscheduled は時刻が未定。登壇者・サムネイル・結んだ募集・出展者(ブースの行だけ)を持つ。
areas
会場とフロアの木(会場 → そのフロアの順)。会場を分けていないイベントでは空。
maps
会場図を会場・フロアごとに一枚。base はベース図の画像の URL と大きさと viewBox、暗い地で読める描き直し(描き直せない図は空)。slots はブースの枠(番号・形・viewBox の単位の頂点・区画・種別)。
maps[].assignments
確定した割当。日ごと(day が空なら全日)に、枠の鍵とコンテンツの id を結ぶ。出展者の名前やロゴは contents から引く。
map
古い読み手のための一枚の会場図(maps の最初と同じ図)。新しく作るページは maps を読む。

見本

依存の無い一枚の HTML。BOUNTY の公開の URL とイベントの URL の名前を書き換えて外部のページに貼ると、日の切り替え・ステージごとのタイムテーブル・ブースの枠を重ねた会場図が出る。見た目はサイトのデザインに合わせて直す。

  • GET /api/v1/public/events/{slug}/plan
  • GET /api/v1/public/images/{key}
HTML
<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>タイムテーブルと会場マップ</title>
<style>
  #bounty-plan { color-scheme: light; color: #111; background: #fff; font-family: system-ui, sans-serif; max-width: 960px; margin: 0 auto; padding: 16px; }
  .days button { margin: 0 4px 8px 0; }
  .days button[aria-pressed="true"] { font-weight: bold; }
  .stage ul { list-style: none; margin: 0 0 16px; padding: 0; }
  .stage li { padding: 6px 0; border-top: 1px solid #ddd; }
  .stage time { font-variant-numeric: tabular-nums; margin-right: 8px; }
  .map svg { display: block; width: 100%; height: auto; max-height: 80vh; background: #fff; }
  .slot-list { list-style: none; margin: 8px 0 16px; padding: 0; }
  .slot-list li { padding: 6px 0; border-top: 1px solid #ddd; }
  .slot { fill: transparent; stroke: #999; }
  .slot.assigned { fill: rgba(250, 200, 0, 0.45); stroke: #333; }
  .slot-number { font-size: 12px; text-anchor: middle; dominant-baseline: central; pointer-events: none; }
</style>
</head>
<body>
<div id="bounty-plan">読み込み中…</div>
<script>
(function () {
  // 公開の読み取り API(ログイン不要・どのオリジンからでも読める)
  var ORIGIN = "https://bounty.example"; // BOUNTY の公開の URL(オリジン)に置き換える
  var SLUG = "spikes-2026-tokyo"; // イベントの URL の名前に置き換える
  var SVG = "http://www.w3.org/2000/svg";
  var root = document.getElementById("bounty-plan");
  var selectedDay = null;

  // 文字は必ず textContent で入れる(API の文字を HTML として解釈させない)
  function h(tag, attrs, children) {
    var node = document.createElement(tag);
    Object.keys(attrs || {}).forEach(function (k) { node.setAttribute(k, attrs[k]); });
    (children || []).forEach(function (c) { node.append(c); });
    return node;
  }
  function s(tag, attrs) {
    var node = document.createElementNS(SVG, tag);
    Object.keys(attrs || {}).forEach(function (k) { node.setAttribute(k, attrs[k]); });
    return node;
  }

  async function load() {
    // ブラウザの HTTP キャッシュが ETag で確かめ直す(変わっていなければ 304 で本文を読まない)
    var res = await fetch(ORIGIN + "/api/v1/public/events/" + encodeURIComponent(SLUG) + "/plan");
    if (res.status === 404) { root.hidden = true; return; } // 無い・非公開のイベント: 枠ごと隠す
    if (!res.ok) { if (!root.dataset.ready) root.textContent = "読み込めませんでした。時間をおいて開き直してください。"; return; }
    var plan = await res.json();
    if (plan.schemaVersion !== 2) { root.textContent = "対応していない版です。"; return; }
    root.hidden = false;
    root.dataset.ready = "1";
    render(plan);
  }

  function render(plan) {
    var tz = plan.event.timezone;
    var clock = new Intl.DateTimeFormat("ja-JP", { timeZone: tz, hour: "2-digit", minute: "2-digit" });
    // day は「イベントのタイムゾーンの暦日」の YYYY-MM-DD。暦日として出すので UTC で読む
    var dayName = new Intl.DateTimeFormat("ja-JP", { timeZone: "UTC", month: "numeric", day: "numeric", weekday: "short" });
    var speakerName = new Map(plan.speakers.map(function (p) { return [p.id, p.name]; }));
    var contentById = new Map(plan.contents.map(function (c) { return [c.id, c]; }));
    var areaById = new Map(plan.areas.map(function (a) { return [a.id, a]; }));
    // フロアは親の会場の名前を添える(「本館 1F」)
    function areaLabel(id) {
      var area = areaById.get(id);
      if (!area) return "会場マップ";
      var parent = area.parentId ? areaById.get(area.parentId) : null;
      return parent ? parent.name + " " + area.name : area.name;
    }

    if (!plan.days.includes(selectedDay)) selectedDay = plan.days[0] || null;
    root.replaceChildren(h("h2", {}, [plan.event.title]));
    if (!selectedDay) { root.append(h("p", {}, ["公開中のタイムテーブルはまだありません。"])); return; }

    // 日の切り替え
    var tabs = h("div", { class: "days", role: "group", "aria-label": "日" });
    plan.days.forEach(function (day) {
      var b = h("button", { type: "button", "aria-pressed": String(day === selectedDay) }, [dayName.format(new Date(day + "T00:00:00Z"))]);
      b.addEventListener("click", function () { selectedDay = day; render(plan); });
      tabs.append(b);
    });
    root.append(tabs);

    // タイムテーブル: その日の時刻のあるコンテンツを、ステージの並び(stages の順)ごとに時刻順で
    var timed = plan.contents.filter(function (c) { return c.schedule === "timed" && c.day === selectedDay; })
      .sort(function (a, b) { return a.startsAt.localeCompare(b.startsAt); });
    var columns = plan.stages.map(function (st) { return { id: st.id, name: st.name }; }).concat([{ id: null, name: "ステージ未定" }]);
    var shown = 0;
    columns.forEach(function (col) {
      var rows = timed.filter(function (c) { return c.stageId === col.id; });
      if (!rows.length) return;
      shown += rows.length;
      var list = h("ul");
      rows.forEach(function (c) {
        var names = c.speakers.map(function (sp) { return speakerName.get(sp.speakerId); }).filter(Boolean).join("・");
        list.append(h("li", {}, [
          h("time", { datetime: c.startsAt }, [clock.format(new Date(c.startsAt)) + "–" + clock.format(new Date(c.endsAt))]),
          h("strong", {}, [c.title]),
          names ? h("div", {}, [names]) : ""
        ]));
      });
      root.append(h("section", { class: "stage" }, [h("h3", {}, [col.name]), list]));
    });
    if (!shown) root.append(h("p", {}, ["この日の時刻の決まったプログラムはまだありません。"]));

    // 会場図: 葉(会場・フロア)ごとに 1 枚。ベース図の上に、viewBox の単位で枠を重ねる
    if (!plan.maps.length) { root.append(h("p", {}, ["公開中の会場マップはまだありません。"])); return; }
    plan.maps.forEach(function (m) {
      var vb = m.base.viewBox;
      var svg = s("svg", { viewBox: vb.join(" "), role: "img", "aria-label": "会場マップ" });
      svg.append(s("image", { href: m.base.image.url, x: vb[0], y: vb[1], width: vb[2], height: vb[3] }));
      // その日の割当(day = null は全日)。contentId は contents の id
      var assigned = new Map(m.assignments.filter(function (a) { return a.day === null || a.day === selectedDay; })
        .map(function (a) { return [a.slotKey, contentById.get(a.contentId)]; }));
      m.slots.forEach(function (slot) {
        var content = assigned.get(slot.slotKey);
        var shape = s("polygon", { class: content ? "slot assigned" : "slot", points: slot.points.map(function (p) { return p.join(","); }).join(" ") });
        var title = s("title");
        title.textContent = slot.number + (content && content.exhibitor ? " " + content.exhibitor.name : "");
        shape.append(title);
        svg.append(shape);
        var cx = slot.points.reduce(function (sum, p) { return sum + p[0]; }, 0) / slot.points.length;
        var cy = slot.points.reduce(function (sum, p) { return sum + p[1]; }, 0) / slot.points.length;
        var label = s("text", { class: "slot-number", x: cx, y: cy });
        label.textContent = slot.number;
        svg.append(label);
      });
      // 図の下に番号と出展者を並べる(title はスマホで読めない)
      var list = h("ul", { class: "slot-list" });
      m.slots.forEach(function (slot) {
        var content = assigned.get(slot.slotKey);
        if (content) list.append(h("li", {}, [h("strong", {}, [slot.number]), " ", content.exhibitor ? content.exhibitor.name : content.title]));
      });
      // 番号は会場・フロアごとに一意なので、分けているイベントでは名前を添える
      var heading = m.areaId ? areaLabel(m.areaId) : "会場マップ";
      root.append(h("section", { class: "map" }, [h("h3", {}, [heading]), svg, list]));
    });
  }

  load();
  // 当日の差し替え・取り下げを拾うため、数分おきに読み直す(変わっていなければ 304)
  setInterval(load, 5 * 60 * 1000);
})();
</script>
</body>
</html>

API と MCP で同じことをするには

企画の全体と画像は、公開の読み取りの API から読む。ログインもトークンも要らず、どのサイトからでも呼べる。応答の形と見本は API の頁にある。書き込みの入口は無いので、中身を変えるのは管理の画面で公開・取り下げをする人である。

続けて読む

作業ガイド | Bounty Developer