Created
June 25, 2018 16:24
-
-
Save freaktechnik/58688128cea0e14a3aecfb5f6162bd82 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==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