YouTube OAuth2 client library for Tampermonkey
此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.org/scripts/575889/1811329/YTOAuthClient.js
// ==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;
})();