Skip to content

Instantly share code, notes, and snippets.

@freaktechnik
Created June 25, 2018 16:24
Show Gist options
  • Save freaktechnik/58688128cea0e14a3aecfb5f6162bd82 to your computer and use it in GitHub Desktop.
Save freaktechnik/58688128cea0e14a3aecfb5f6162bd82 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name FFZ Twitchbots
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Mark more users as bots in chat
// @author Martin Giger
// @match https://www.twitch.tv/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
//TODO only check modded users and users ending in "bot" by default.
// -> have an option to check all users? Other kind of intelligent pre-selection?
function register(attempt = 0) {
if (window.FrankerFaceZ) {
console.log("[FFZ: Twitchbots] loading...");
window.FrankerFaceZ.get().register('addon.twitchbots', (() => {
return class Twitchbots extends window.FrankerFaceZ.utilities.module.Module {
constructor(...args) {
super(...args);
this.botCache = JSON.parse(window.sessionStorage.getItem('botCache') || "{}");
this.typeCache = JSON.parse(window.sessionStorage.getItem('typeCache') || "{}");
//this.inject('settings');
this.inject('chat');
this.inject('chat.badges');
// this.inject('chat.actions');
// this.inject('viewer_cards');
this.getGlobalBots().catch(console.warn);
//TODO setting for bttv bot badging
//TODO setting for global bots
//TODO setting for channel specific bots
window.addEventListener("beforeunload", () => this.destroy());
}
async apiRequest(path, needResponse = false) {
//TODO implement hard caching
const response = await fetch(`https://api.twitchbots.info/v2/${path}`, {
cache: 'force-cache'
});
if(!response.ok && !needResponse) {
return { response };
}
else if(!response.ok) {
throw new Error("Network error");
}
const json = await response.json();
return {
response,
json
};
}
async paginateAPI(path, listName) {
const results = [];
let offset = 0;
let total = 0;
const pageSize = 100;
const limitDelimiter = path.includes("?") ? '&' : '?';
do {
const { json } = await this.apiRequest(`${path}${limitDelimiter}limit=${pageSize}&offset=${offset}`, true);
total = json.total;
offset += pageSize;
results.push(...json[listName]);
} while(offset + pageSize < total);
return results;
}
async getType(typeId) {
if(typeId in this.typeCache) {
return this.typeCache[typeId];
}
let res;
try {
res = await this.apiRequest(`type/${typeId}`, true);
}
catch(e) {
return {};
}
this.typeCache[typeId] = res.json;
return res.json;
}
async getGlobalBots() {
const res = await this.paginateAPI(`bot?multiChannel=1`, 'bots');
for(const bot of res) {
this.botCache[bot.id] = bot;
}
}
async getChannelBots(channelId) {
const res = await this.paginateAPI(`channel/${channelId}/bots`, 'bots');
for(const bot of res) {
this.botCache[bot.id] = bot;
}
}
async getBotInfo(idsToCheck) {
if(!idsToCheck || !idsToCheck.length) {
return;
}
const sliceSize = 100;
let offset = 0;
do {
const slice = idsToCheck.slice(offset, offset + sliceSize);
const { json } = await this.apiRequest(`bot?ids=${slice.join(',')}`);
for(const bot of json.bots) {
this.botCache[bot.id] = bot;
}
offset += sliceSize;
} while(offset < idsToCheck.length);
}
async getBTTVBots(channelLogin) {
const res = fetch(`htrtps://api.betterttv.net/2/channels/${channelLogin}`);
if(res.ok) {
const json = res.json();
return json.bots;
}
return [];
}
onEnable() {
this.log.debug('FFZ:Twitchbots enabled');
this.on('chat:room-add', (...args) => this.updateChannel(...args));
// this.on('chat:user-add', (...args) => this.updateUser(...args));
// this.addChatActions();
// add viewerCard tab with type info / add bot link
// this.viewer_cards.addTab('twitchbots', {
// label: 'Twitchbots',
// component: () => {
// TODO: this should return a vue component
// return {
// props: ['tab', 'user', 'channel', 'self', 'getFFZ' ]
// };
// }
// });
this.updateBots().catch(console.warn);
}
destroy() {
window.sessionStorage.setItem('botCache', JSON.stringify(this.botCache));
window.sessionStorage.setItem('typeCache', JSON.stringify(this.typeCache));
}
addBotBadge(user) {
this.log.debug('FFZ:Twitchbots: add bot badge to ' + user.login);
user.addBadge('ffz-global', 2);
}
async updateUser(user) {
let botInfo = false;
if(user._id in this.botCache) {
botInfo = this.botCache[user._id];
}
else {
const res = await this.apiRequest(`bot/${user._id}`);
if(!res.response.ok) {
return;
}
else if(res.response.status == 200) {
botInfo = res.json;
}
else {
botInfo = false;
}
this.botCache[user._id] = botInfo;
}
if(botInfo) {
this.addBotBadge(user);
}
}
async updateChannel(room) {
await this.getChannelBots(room.id);
for(const id in this.botCache) {
if(this.botCache.hasOwnProperty(id) && this.botCache[id]) {
const user = room.getUser(id, this.botCache[id].username);
await this.updateUser(user);
}
}
const botsToCheck = [];
for(const user in room.users) {
const usr = room.users[user];
if(!this.botCache.hasOwnProperty(usr._id)) {
botsToCheck.push(usr._id);
}
}
await this.getBotInfo(botsToCheck);
for(const id of botsToCheck) {
if(this.botCache.hasOwnProperty(id) && this.botCache[id]) {
await this.updateUser(room.getUser(id, this.botCache[id].username));
}
}
// Lastly, add badges to all bots BTTV has marked for this channel.
const bttvBots = await this.getBTTVBots(room.login);
for(const bot of bttvBots) {
this.addBotBadge(room.getUser(null, bot));
}
}
async updateBots() {
for(const room in this.chat.rooms) {
await this.updateChannel(this.chat.rooms[room]);
}
}
addChatActions() {
//TODO this is not how this works and I'd like to have fancier filters, too
this.chat.actions.addAction('checkBot', {
action: 'open_url',
appearance: {
type: 'image',
icon: "ffz-fa-fa-info",
tooltip: "Check Bot",
color: "#FFF800",
image: "https://twitchbots.info/img/icon.svg"
},
options: {
url: "https://twitchbots.info/check?username={{user.login}}"
},
display: {
deleted: false,
mod_icons: true,
mod: false
}
});
this.chat.actions.addAction('reportBot', {
action: 'open_url',
appearance: {
type: 'icon',
icon: "ffz-fa-fa-user-plus",
tooltip: "Report as Bot",
color: "#FFF800"
},
options: {
url: "https://twitchbots.info/submit?username={{user.login}}&channel={{room.login}}"
},
display: {
deleted: false,
mod: false
}
});
}
}
})()).enable();
}
else if(attempt < 60) {
console.log("[FFZ: Twitchbots] defering injection");
setTimeout(() => register(attempt + 1), 1000);
}
}
register();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment