I’ve tried each of the forms of OAuth token generation, the bot connects, but neither of the Topics I’m listening to do I receive Messages for. Just Ping & Pong. Not even Whispers. I feel like I’m really close to getting this working but I’ve exhausted everything I can think of.
Scripted for Node.js, uses “websocket” and “request” Node.js packages. API Ext is a custom AJAX wrapper I wrote for Helix/Kraken Twitch APIs. The design of the PubSub class object is to take in the User/Channel names from the config and resolve them into IDs using https://api.twitch.tv/helix/users?login={0} and https://api.twitch.tv/api/channels/{0} APIs.
Please tell me what I’m doing wrong.
var WebSocket = require('websocket').w3cwebsocket;
var apiExt = require('./apiext.js');
var request = require('request');
var apiExtInstance = new apiExt();
var PubSubBot = (function PubSubBot(state, $event) {
var $url = state.url || 'wss://pubsub-edge.twitch.tv';
var $scopes = [
'bits:read',
'chat:edit',
'chat:read',
'whispers:edit',
'whispers:read',
'channel:moderate',
'channel:read:subscriptions',
'channel_subscriptions',
'moderation:read',
'channel_read',
'user_read',
];
var $pingTimeout = null;
var $pongTimeout = null;
var $socket = null;
var $outboundQueue = null;
var $sendInterval = setInterval(function () {
if ($socket && ($socket.readyState === $socket.CLOSED || $socket.readyState === $socket.CLOSING)) {
setTimeout(connect, 1);
return;
}
else if (!$outboundQueue || $outboundQueue.length < 1 || !$socket || $socket.readyState !== $socket.OPEN) {
return;
}
var message = $outboundQueue.pop();
if (message) {
$socket.send(message);
}
}, 550);
newSession(function () {
if (state.user_id === 0) {
apiExtInstance.getUserInfo(state.user_name, state.client_id, function (error, response, body) {
state.user_id = body.data[0].id;
if (state.channel_id !== 0) {
setTimeout(connect, 1);
}
});
}
if (state.channel_id === 0) {
apiExtInstance.getChannelInfo(state.channel_name, state.client_id, function (error, response, body) {
state.channel_id = body._id;
if (state.user_id !== 0) {
setTimeout(connect, 1);
}
});
}
});
function htmlDecode(input) {
return input.replace(/&#(\d+);/g, function (match, dec) {
return String.fromCharCode(dec);
});
}
function newSession(callback) {
if (!state.token) {
var parameters = [
'response_type=code',
'redirect_uri=' + encodeURIComponent('http://localhost'),
'client_id=' + state.client_id,
'scope=' + $scopes.join('+'),
];
console.log('https://id.twitch.tv/oauth2/authorize?' + parameters.join('&'));
return;
}
//var parameters = [
// 'response_type=token',
// 'redirect_uri=' + encodeURIComponent('http://localhost'),
// 'client_id=' + state.client_id,
// 'scope=' + $scopes.join('+'),
//];
//console.log('https://id.twitch.tv/oauth2/authorize?' + parameters.join('&'));
//request.get(
// 'https://id.twitch.tv/oauth2/authorize?' + parameters.join('&'),
// {},
// function (error, response, body) {
// if (!error && response.statusCode === 200) {
// var match, authenticity_token, redirect_path;
// if (match = body.match(/id=\"authenticity_token\" type=\"hidden\" value=\"([^\"]+)\"/)) {
// authenticity_token = htmlDecode(match[1]);
// }
// if (match = body.match(/name=\"redirect_path\" value=\"([^\"]+)\"/)) {
// redirect_path = htmlDecode(match[1]);
// }
// request.post(
// 'https://id.twitch.tv/oauth2/authorize',
// {
// authenticity_token: authenticity_token,
// redirect_path: redirect_path,
// client_id: state.client_id,
// origin_uri: '',
// embed: false,
// username: state.user_name,
// password: state.password,
// },
// function (error, response, body) {
// console.log(body);
// if (!error && response.statusCode === 200) {
// }
// });
// }
// });
//var parameters = [
// 'grant_type=authorization_code',
// 'code=' + state.token,
// 'client_id=' + state.client_id,
// 'client_secret=' + state.secret,
// 'redirect_uri=' + encodeURIComponent('http://localhost'),
//];
//request.post(
// 'https://id.twitch.tv/oauth2/token?' + parameters.join('&'),
// {},
// function (error, response, body) {
// if (!error && response.statusCode === 200) {
// state.oauth = body.access_token;
// state.refresh_token = body.refresh_token;
// var t = new Date();
// t.setSeconds(t.getSeconds() + body.expires_in);
// state.expires = t;
// if (callback && typeof callback === 'function') {
// callback();
// }
// }
// });
//var parameters = [
// 'grant_type=client_credentials',
// 'client_id=' + state.client_id,
// 'client_secret=' + state.secret,
// 'scope=' + $scopes.join('+'),
//];
//request.post(
// 'https://id.twitch.tv/oauth2/token?' + parameters.join('&'),
// {},
// function (error, response, body) {
// if (!error && response.statusCode === 200) {
// body = JSON.parse(body);
// state.oauth = body.access_token;
// var t = new Date();
// t.setSeconds(t.getSeconds() + body.expires_in);
// state.expires = t;
// if (callback && typeof callback === 'function') {
// callback();
// }
// }
// });
if (callback && typeof callback === 'function') {
callback();
}
}
function init() {
if ($socket !== null) {
if ($socket.readyState === $socket.CONNECTING || $socket.readyState === $socket.OPEN) close();
delete $socket;
}
$outboundQueue = [];
$socket = new WebSocket($url);
$socket.onopen = socketOpen;
$socket.onclose = socketClose;
$socket.onmessage = socketMessage;
$socket.onerror = socketError;
}
function connect() {
if (state.user_id === 0 || state.channel_id === 0) return;
if (state.expires === null || state.expires.getTime() < (new Date()).getTime()) {
newSession(function () {
init();
});
} else {
init();
}
}
function ping() {
send({ type: 'PING' });
$pingTimeout = setTimeout(ping, 3 * 60 * 1000);
$pongTimeout = setTimeout(function () {
console.log('$pongTimeout');
setTimeout(connect, 1);
}, 10000); // if pong isn't received within 10 seconds, reconnect
}
function send(message) {
if (typeof message === 'object' || Array.isArray(message)) {
message = JSON.stringify(message);
}
switch ($socket.readyState) {
case $socket.CLOSING:
case $socket.CLOSED:
console.log('$socket.CLOSED');
setTimeout(connect, 1);
$outboundQueue.push(message);
break;
case $socket.OPEN:
case $socket.CONNECTING:
$outboundQueue.push(message);
break;
default:
break;
}
}
function close() {
$socket.close();
$outboundQueue = [];
}
function socketOpen() {
console.log('socketOpen');
var frame = {
type: 'LISTEN',
nonce: 'listenToTopics-' + (Math.random() * 100),
data: {
topics: [
'channel-bits-events-v1.' + state.channel_id,
'channel-subscribe-events-v1.' + state.channel_id,
'chat_moderator_actions.' + state.user_id + '.' + state.channel_id,
'whispers.' + state.user_id,
],
auth_token: state.oauth,
},
};
console.log(frame);
send(frame);
ping();
}
function socketMessage(event) {
var message = JSON.parse(event.data);
switch (message.type) {
case 'PONG':
clearTimeout($pongTimeout);
$pongTimeout = null;
break;
case 'RESPONSE':
break;
case 'RECONNECT':
console.log('RECONNECT');
setTimeout(connect, 1);
break;
case 'MESSAGE':
console.log('MESSAGE');
console.log(message);
parseMessage(message.data);
break;
default:
break;
}
}
function socketClose() {
console.log('socketClose');
setTimeout(connect, 1);
}
function socketError(error) {
console.error('socketError');
//console.error(error);
}
function parseMessage(data) {
var message = JSON.parse(data.message);
switch (data.topic) {
// https://dev.twitch.tv/docs/v5/guides/PubSub/
case 'channel-bits-events-v1.' + state.channel_id:
$event('bits', message);
break;
// https://discuss.dev.twitch.com/t/in-line-broadcaster-chat-mod-logs/7281/12
case 'chat_moderator_actions.' + state.user_id + '.' + state.channel_id:
$event('moderation', message);
break;
case 'whispers.' + state.user_id:
$event('whisper', message);
break;
case 'channel-subscribe-events-v1.' + state.channel_id:
$event('sub', message);
break;
default:
break;
}
}
});
if (typeof module !== "undefined" && module.exports) {
module.exports = PubSubBot;
}
if (typeof window !== "undefined") {
window.PubSubBot = PubSubBot;
}
var pubSub = require('./pubsub.js');
var $config = {
user_id: 0,
user_name: 'Bot's Username',
channel_id: 0,
channel_name: 'ChannelName',
client_id: 'ClientID Token',
secret: 'Secret Token',
expires: null,
oauth: 'OAuth Token',
token: 'Code Token',
};
var $event = function(type, message) {
switch (type) {
case 'bits':
break;
case 'moderation':
break;
case 'whisper':
break;
case 'sub':
break;
}
console.log(type);
console.log(message);
};
var pubSubInstance = new pubSub($config, $event);