module YtOAuth
Constants
- STYLE_BANNER
Public Instance Methods
Source
# File lib/yt_oauth.rb, line 220 def api_error(data) error = data["error"] || {} message = error["message"].to_s.strip message = error["errors"]&.first&.dig("message").to_s if message.empty? { success: false, error: message.empty? ? "YouTube API error" : message } end
Source
# File lib/yt_oauth.rb, line 30 def auth_allowed?(network:, nick:, is_op:, admin_channels: []) return true if is_op Array(admin_channels).any? do |channel| ChannelTrust.human_admin_nick?(network: network, channel: channel, nick: nick, is_op: is_op) || ChannelTrust.registered_owner_nick?(network: network, channel: channel, nick: nick) end end
Source
# File lib/yt_oauth.rb, line 26 def auth_denied_message "Only channel ops and registered admins can link the bot YouTube account." end
Source
# File lib/yt_oauth.rb, line 57 def clear_account(bot_nick:) RabbotDb.set_plugin_value( plugin: YtConfig::PLUGIN_NAME, key: YtConfig::ACCOUNT_KEY, value: "", nick: bot_nick ) end
Source
# File lib/yt_oauth.rb, line 84 def clear_pending(bot_nick:) RabbotDb.set_plugin_value( plugin: YtConfig::PLUGIN_NAME, key: YtConfig::OAUTH_PENDING_KEY, value: "", nick: bot_nick ) end
Source
# File lib/yt_oauth.rb, line 185 def fetch_channel_info(access_token:, fetch: nil) url = "#{YtConfig::API_BASE}/channels?part=snippet&mine=true" response = http_get(url, headers: { "Authorization" => "Bearer #{access_token}" }, fetch: fetch) data = parse_json(response) return api_error(data) if data["error"] item = Array(data["items"]).first return { success: false, error: "No YouTube channel found for this Google account." } unless item { success: true, channel_id: item["id"], channel_title: item.dig("snippet", "title").to_s } rescue StandardError => e { success: false, error: "Channel lookup failed — #{e.message}" } end
Source
# File lib/yt_oauth.rb, line 203 def form_encode(params) params.map { |key, value| "#{CGI.escape(key.to_s)}=#{CGI.escape(value.to_s)}" }.join("&") end
Source
# File lib/yt_oauth.rb, line 241 def http_get(url, headers: {}, fetch: nil) if fetch return fetch.call(url, method: "GET", headers: headers, body: nil) end RabbotHttp.fetch(url, headers: headers) end
Source
# File lib/yt_oauth.rb, line 227 def http_post(url, body:, fetch: nil) if fetch return fetch.call(url, method: "POST", headers: { "Content-Type" => "application/x-www-form-urlencoded" }, body: body) end uri = URI(url) request = Net::HTTP::Post.new(uri) request["Content-Type"] = "application/x-www-form-urlencoded" request["User-Agent"] = RabbotHttp::DEFAULT_USER_AGENT request.body = body response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == "https") { |http| http.request(request) } response.body end
Source
# File lib/yt_oauth.rb, line 39 def load_account(bot_nick:) RabbotDb.get_plugin_json( plugin: YtConfig::PLUGIN_NAME, key: YtConfig::ACCOUNT_KEY, nick: bot_nick, default: nil ) end
Source
# File lib/yt_oauth.rb, line 66 def load_pending(bot_nick:) RabbotDb.get_plugin_json( plugin: YtConfig::PLUGIN_NAME, key: YtConfig::OAUTH_PENDING_KEY, nick: bot_nick, default: nil ) end
Source
# File lib/yt_oauth.rb, line 22 def login_channel_denied_message "YouTube auth is private — use /msg <bot> !yt auth so device codes are not shown in channel." end
Source
# File lib/yt_oauth.rb, line 214 def oauth_error(data) message = data["error_description"].to_s.strip message = data["error"].to_s if message.empty? "YouTube auth failed — #{message}" end
Source
# File lib/yt_oauth.rb, line 207 def parse_json(body) data = body.is_a?(String) ? JSON.parse(body) : body data rescue JSON::ParserError { "error" => "invalid_json", "error_description" => body.to_s[0, 120] } end
Source
# File lib/yt_oauth.rb, line 125 def poll_device_flow(bot_nick:, fetch: nil) pending = load_pending(bot_nick: bot_nick) return { success: false, error: "No pending auth — use !yt auth first." } unless pending creds = YtConfig.credentials body = form_encode( client_id: creds[:client_id], client_secret: creds[:client_secret], device_code: pending["device_code"], grant_type: "urn:ietf:params:oauth:grant-type:device_code" ) response = http_post(YtConfig::TOKEN_URL, body: body, fetch: fetch) data = parse_json(response) if data["error"] error = data["error"].to_s return { success: false, pending: true, error: YtReport.format_auth_pending_reply } if error == "authorization_pending" return { success: false, pending: true, error: YtReport.format_auth_pending_reply } if error == "slow_down" clear_pending(bot_nick: bot_nick) return { success: false, error: oauth_error(data) } end channel = fetch_channel_info(access_token: data["access_token"], fetch: fetch) return { success: false, error: channel[:error] } unless channel[:success] account = { "refresh_token" => data["refresh_token"], "channel_id" => channel[:channel_id], "channel_title" => channel[:channel_title], "linked_at" => RabbotDb.iso_time } save_account(bot_nick: bot_nick, account: account) clear_pending(bot_nick: bot_nick) { success: true, account: account } rescue StandardError => e { success: false, error: "YouTube auth failed — #{e.message}" } end
Source
# File lib/yt_oauth.rb, line 18 def private_message?(channel) channel.nil? || channel.to_s.strip.empty? end
Source
# File lib/yt_oauth.rb, line 165 def refresh_access_token(account:, fetch: nil) return { success: false, error: "Not linked — use !yt auth." } unless account return { success: false, error: YtConfig.setup_hint } unless YtConfig.configured? creds = YtConfig.credentials body = form_encode( client_id: creds[:client_id], client_secret: creds[:client_secret], refresh_token: account["refresh_token"], grant_type: "refresh_token" ) response = http_post(YtConfig::TOKEN_URL, body: body, fetch: fetch) data = parse_json(response) return { success: false, error: oauth_error(data) } if data["error"] { success: true, access_token: data["access_token"] } rescue StandardError => e { success: false, error: "Token refresh failed — #{e.message}" } end
Source
# File lib/yt_oauth.rb, line 48 def save_account(bot_nick:, account:) RabbotDb.set_plugin_json( plugin: YtConfig::PLUGIN_NAME, key: YtConfig::ACCOUNT_KEY, value: account, nick: bot_nick ) end
Source
# File lib/yt_oauth.rb, line 75 def save_pending(bot_nick:, pending:) RabbotDb.set_plugin_json( plugin: YtConfig::PLUGIN_NAME, key: YtConfig::OAUTH_PENDING_KEY, value: pending, nick: bot_nick ) end
Source
# File lib/yt_oauth.rb, line 93 def start_device_flow(bot_nick:, fetch: nil) return { success: false, error: YtConfig.setup_hint } unless YtConfig.configured? creds = YtConfig.credentials body = form_encode( client_id: creds[:client_id], scope: YtConfig::SCOPE ) response = http_post(YtConfig::DEVICE_CODE_URL, body: body, fetch: fetch) data = parse_json(response) return { success: false, error: oauth_error(data) } if data["error"] pending = { "device_code" => data["device_code"], "user_code" => data["user_code"], "verification_url" => data["verification_url"], "expires_in" => data["expires_in"].to_i, "interval" => data["interval"].to_i, "started_at" => RabbotDb.iso_time } save_pending(bot_nick: bot_nick, pending: pending) { success: true, verification_url: pending["verification_url"], user_code: pending["user_code"], expires_in: pending["expires_in"] } rescue StandardError => e { success: false, error: "YouTube auth failed — #{e.message}" } end