YTOAuthClient

YouTube OAuth2 client library for Tampermonkey

Ce script ne doit pas être installé directement. C'est une librairie destinée à être incluse dans d'autres scripts avec la méta-directive // @require https://update.greasyfork.org/scripts/575889/1811329/YTOAuthClient.js

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==UserScript==
// @name         YTOAuthClient
// @namespace    https://greasyfork.org/
// @version      1.0.0
// @description  YouTube OAuth2 client library for Tampermonkey
// @author       yourname
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @connect      accounts.google.com
// @connect      www.googleapis.com
// ==/UserScript==

(function () {
  'use strict';

  class YTOAuthClient {
    constructor({ clientId, redirectUri = 'https://localhost' } = {}) {
      if (!clientId) throw new Error('clientId is required');
      this._clientId = clientId;
      this._redirectUri = redirectUri;
      this._scope = 'https://www.googleapis.com/auth/youtube.readonly';
      this._tokenKey = `yt_oauth_token_${clientId}`;
    }

    // ─── トークン管理 ──────────────────────────────────

    getToken() {
      const raw = GM_getValue(this._tokenKey, null);
      if (!raw) return null;
      const token = JSON.parse(raw);
      // 有効期限チェック
      if (Date.now() > token.expiresAt) {
        this.clearToken();
        return null;
      }
      return token.accessToken;
    }

    saveToken(accessToken, expiresIn) {
      GM_setValue(this._tokenKey, JSON.stringify({
        accessToken,
        expiresAt: Date.now() + expiresIn * 1000,
      }));
    }

    clearToken() {
      GM_setValue(this._tokenKey, null);
    }

    isAuthenticated() {
      return this.getToken() !== null;
    }

    // ─── 認証フロー ────────────────────────────────────

    startAuth() {
      const params = new URLSearchParams({
        client_id: this._clientId,
        redirect_uri: this._redirectUri,
        response_type: 'token',
        scope: this._scope,
      });
      window.open(
        `https://accounts.google.com/o/oauth2/v2/auth?${params}`,
        '_blank'
      );
    }

    // リダイレクト先ページで呼ぶ(@match に redirectUri を追加した上で)
    handleRedirect() {
      if (!location.href.startsWith(this._redirectUri)) return false;
      const hash = new URLSearchParams(location.hash.slice(1));
      const accessToken = hash.get('access_token');
      const expiresIn = Number(hash.get('expires_in') ?? 3600);
      if (!accessToken) return false;
      this.saveToken(accessToken, expiresIn);
      return true;
    }

    // ─── API リクエスト ────────────────────────────────

    request({ url, method = 'GET', data } = {}) {
      return new Promise((resolve, reject) => {
        const token = this.getToken();
        if (!token) return reject(new Error('Not authenticated'));

        GM_xmlhttpRequest({
          method,
          url,
          headers: {
            Authorization: `Bearer ${token}`,
            'Content-Type': 'application/json',
          },
          data: data ? JSON.stringify(data) : undefined,
          onload: (res) => {
            const json = JSON.parse(res.responseText);
            if (res.status === 401) {
              this.clearToken();
              return reject(new Error('Token expired'));
            }
            if (json.error) return reject(json.error);
            resolve(json);
          },
          onerror: (e) => reject(new Error(`Network error: ${e.status}`)),
        });
      });
    }

    // ─── YouTube API ───────────────────────────────────

    async getLiveId() {
      const data = await this.request({
        url: 'https://www.googleapis.com/youtube/v3/liveBroadcasts'
          + '?part=snippet,id&broadcastStatus=active&broadcastType=all',
      });
      const item = data.items?.[0];
      if (!item) return null;
      return {
        liveId: item.id,
        liveChatId: item.snippet.liveChatId,
      };
    }
  }

  window.YTOAuthClient = YTOAuthClient;
})();