2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

この記事は デジタル創作サークル UniProject Advent Calendar 2025 4 日目の記事です。

GitHub の招待をサボりたかったサークル主の話です。

結論(何したん?)

Google Form を出せば GitHub の Organization に入れるようにした。

動機

今までは、役員が手動で招待して手動でチームを付与していました。
ですが、何かの手違いで大晦日にノリで立てたサークルなのにも関わらずメンバー数が合計 100 を突破してしまったのです()

ぼく「GitHub、いちいち招待するのだるくね」

ということで、このフォームを作りました。

どんな感じなん?

フォームの質問項目はこんな感じ。
昔は所属したいチームの選択欄もありましたが、訳あってなくなりました。(後述します。)

GitHubフォームのイメージ図

作り方

フォームを作る

まずはGoogle Form のページからフォームを作成します。

この時、

  • メールアドレス
  • ユーザー名

の項目を必ず追加してください。

また、チームを選択する項目がある場合は、ラジオボタンではなくチェックボックスとしてフォームを作成してください。

そして、スプレッドシートにリンクさせてください。
(画像はリンク済みの画像)

スプレッドシートにリンクボタン

PAT を用意する

GitHub の設定ページから、 [Developper Settings] > [Personal Access Token] > [Tokens (Classic)] を選択します。

「Fine-grained じゃないの?」と思ったそこのあなた、セキュリティ的には正解です。
ただ、残念なことに 2025 年 11 月現在、 Fine-grained Token は、この権限に対応していません...

今から発行する Token は あなたの所属するすべての組織に対する操作をあなたの代わりに行うことができるもの です。
漏洩には十分に気をつけてください。

名前はわかりやすければなんでも構いません。
scope を下の画像のように

  • write:org

に設定してください。

PATの発行

GAS を書く

下のコードをコピペしてください。

code.js
const GITHUB_TOKEN =
  PropertiesService.getScriptProperties().getProperty("GITHUB_TOKEN");
const GITHUB_ORG = "UniPro-tech";

function onFormSubmit(e) {
  const responses = e.namedValues;
  const githubUsername = responses["GitHubのユーザー名"][0];
  const teamNames = createArrayFromString(
    responses[
      `GitHubのチーム
参加したいチームを全て選択してください。`
    ][0]
  );
  const email =
    responses[
      `メールアドレス
@uniproject.jpで終わり、GitHubのアカウントに紐つけてある必要があります。`
    ][0];

  Logger.log(teamNames.length);

  inviteUserToOrg(githubUsername, email);
  Logger.log("Invited to Org");

  for (const teamName of teamNames) {
    const teamSlug = getTeamSlug(teamName);
    if (!teamSlug) {
      Logger.log(`Team Not Found: ${teamName}`);
      continue;
    }
    addUserToTeam(githubUsername, teamSlug);
  }
}

function createArrayFromString(string) {
  if (string.indexOf(",") !== -1) {
    Logger.log("split");
    return string.split(", ");
  } else {
    return [string];
  }
}

function getTeamSlug(teamName) {
  const teamMap = {
    DiscordBot: "discordbot",
    GitHub: "github",
    Kubernetes: "kubernetes",
    Web: "web",
    UniQUE: "unique-dev",
    UserEndTools: "userendtool",
  };
  return teamMap[teamName];
}

function inviteUserToOrg(username, email) {
  const url = `https://api.github.com/orgs/${GITHUB_ORG}/invitations`;

  const payload = {
    invitee: username,
    email: email,
    role: "direct_member",
  };

  const options = {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify(payload),
    headers: {
      Authorization: `token ${GITHUB_TOKEN}`,
      Accept: "application/vnd.github+json",
    },
    muteHttpExceptions: true,
  };

  const response = UrlFetchApp.fetch(url, options);
  Logger.log(`Org Invitation Sended: ${response.getContentText()}`);
}

function addUserToTeam(username, teamSlug) {
  const url = `https://api.github.com/orgs/${GITHUB_ORG}/teams/${teamSlug}/memberships/${username}`;
  const options = {
    method: "put",
    headers: {
      Authorization: `token ${GITHUB_TOKEN}`,
      Accept: "application/vnd.github+json",
    },
    muteHttpExceptions: true,
  };

  const response = UrlFetchApp.fetch(url, options);
  Logger.log(
    `Add Team Successfully (${teamSlug}): ${response.getContentText()}`
  );
}

一つずつ解説していきましょう。

フォームを受け取った時に呼び出される関数

この部分は、

  1. フォームの項目名から各項目の値を出してくる
  2. 関数に投げつける

という処理をしています。

また、for 文では、チームの選択がある場合、配列として処理しています。

function onFormSubmit(e) {
  const responses = e.namedValues;
  const githubUsername = responses["GitHubのユーザー名"][0];
  const teamNames = createArrayFromString(
    responses[
      `GitHubのチーム
参加したいチームを全て選択してください。`
    ][0]
  );
  const email =
    responses[
      `メールアドレス
@uniproject.jpで終わり、GitHubのアカウントに紐つけてある必要があります。`
    ][0];

  Logger.log(teamNames.length);

  inviteUserToOrg(githubUsername, email);
  Logger.log("Invited to Org");

  for (const teamName of teamNames) {
    const teamSlug = getTeamSlug(teamName);
    if (!teamSlug) {
      Logger.log(`Team Not Found: ${teamName}`);
      continue;
    }
    addUserToTeam(githubUsername, teamSlug);
  }
}

招待を送信する部分

この部分では、GitHub API を叩いてユーザーを招待しています。

payloadにあるroleとは、メンバーの種別を表しています。

  • admin
  • direct_member
  • billing_manager

があります。
(一応reinstateなるものもありますが、ここでは割愛します。)

また、email と invitee_id はどちらかのみでも OK ですが、今回は両方指定しています。
(UniPro のメールアドレスを持っていないユーザーの参加を防ぐため)

function inviteUserToOrg(username, email) {
  const url = `https://api.github.com/orgs/${GITHUB_ORG}/invitations`;

  const payload = {
    invitee_id: username,
    email: email,
    role: "direct_member",
  };

  const options = {
    method: "post",
    contentType: "application/json",
    payload: JSON.stringify(payload),
    headers: {
      Authorization: `token ${GITHUB_TOKEN}`,
      Accept: "application/vnd.github+json",
    },
    muteHttpExceptions: true,
  };

  const response = UrlFetchApp.fetch(url, options);
  Logger.log(`Org Invitation Sended: ${response.getContentText()}`);
}

チームに参加させる部分

ここでは、GitHub API を叩いてチームへ追加していきます。
teamSlug はonFormSubmit関数のgetTeamSlugでフォームのラベルから変換した GitHub のチームの URL パスです。

function addUserToTeam(username, teamSlug) {
  const url = `https://api.github.com/orgs/${GITHUB_ORG}/teams/${teamSlug}/memberships/${username}`;
  const options = {
    method: "put",
    headers: {
      Authorization: `token ${GITHUB_TOKEN}`,
      Accept: "application/vnd.github+json",
    },
    muteHttpExceptions: true,
  };

  const response = UrlFetchApp.fetch(url, options);
  Logger.log(
    `Add Team Successfully (${teamSlug}): ${response.getContentText()}`
  );
}

GAS に定数をセットする

先のコードのこの部分

const GITHUB_TOKEN =
  PropertiesService.getScriptProperties().getProperty("GITHUB_TOKEN");
const GITHUB_ORG = "UniPro-tech";

ここで PAT や Organization ID を設定しています。

GITHUB_ORGはそのままですね。
URL 通り、GitHub Oragnization の ID を記載します。

PAT は、環境変数として設定します。

[プロジェクト設定] > [スクリプトプロパティ] で、画像のように設定します。

スクリプトプロパティ 設定例

トリガーを設定する

最後に、トリガーを設定します。

[トリガー] > [トリガーを追加] から画像のように設定します。

トリガー 設定例

これで終わりです。
お疲れ様でした!!

(おまけ) GitHub のチーム招待を何故無くしたか

これは、本来のチームの使い方の話につながります。

まず、GitHub のチームは、CODEOWNER などレビュアーとしてもよく使用します。
サークルのリポなので、基本的にすべてのリポで Write 権限は全員についていますし、チームで管理することはありません。
なので、問題ないと思いがちですが、初心者さんがチームに所属してしまってレビュアーとなってしまうと悲惨です

これらの理由で、チームへ自動参加させる機能は廃止になったのです...(とほほ)

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?