Last active
June 20, 2023 04:02
-
-
Save michaelowens/9e87302170bac06c6c14d7269086d494 to your computer and use it in GitHub Desktop.
Show streamers live on kick in your Twitch sidebar
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 Twitch Kick Followers | |
// @namespace Violentmonkey Scripts | |
// @description Keep track of Kick streams. | |
// @run-at document-start | |
// @match *://twitch.tv/* | |
// @match *://www.twitch.tv/* | |
// @version 0.0.0 | |
// @author xikeon | |
// @require https://cdn.jsdelivr.net/combine/npm/@violentmonkey/dom@2,npm/@violentmonkey/ui@0.7 | |
// @updateURL https://gist.github.com/michaelowens/9e87302170bac06c6c14d7269086d494/raw/twitch-kick-followers.user.js | |
// @downloadURL https://gist.github.com/michaelowens/9e87302170bac06c6c14d7269086d494/raw/twitch-kick-followers.user.js | |
// @supportURL https://gist.github.com/michaelowens/9e87302170bac06c6c14d7269086d494#new_comment_field | |
// @grant GM_addStyle | |
// @grant GM_getValue | |
// @grant GM_registerMenuCommand | |
// @grant GM_setValue | |
// ==/UserScript== | |
(function () { | |
'use strict'; | |
var css_248z = ""; | |
var styles = {"title":"style-module_title__Hei5S","desc":"style-module_desc__LACEI"}; | |
var stylesheet=".style-module_title__Hei5S{display:flex;font-weight:700;justify-content:space-between}.style-module_desc__LACEI{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity))}"; | |
const store = { | |
get() { | |
return GM_getValue('following', []); | |
}, | |
add(username) { | |
const value = store.get(); | |
value.push(username); | |
GM_setValue('following', value); | |
}, | |
remove(username) { | |
const value = store.get(); | |
GM_setValue('following', value.filter(u => u !== username)); | |
} | |
}; | |
// global CSS | |
let panel; | |
GM_registerMenuCommand('Configure Followers', () => { | |
if (!panel) { | |
showPanel(); | |
} else { | |
hidePanel(); | |
} | |
}); | |
function showPanel() { | |
if (panel) { | |
return; | |
} | |
panel = VM.getPanel({ | |
content: VM.h(FollowingPanel, null), | |
theme: 'dark', | |
style: [css_248z, stylesheet].join('\n') | |
}); | |
panel.wrapper.style.left = '50%'; | |
panel.wrapper.style.top = '50%'; | |
panel.wrapper.style.transform = 'translate(-50%, -50%)'; | |
// panel.setMovable(true); | |
panel.show(); | |
} | |
function hidePanel() { | |
panel.dispose(); | |
panel = null; | |
} | |
function FollowingList() { | |
const following = store.get(); | |
const deleteItem = (e, username) => { | |
e.preventDefault(); | |
store.remove(username); | |
panel.body.querySelector('#following_list').replaceWith(VM.m(VM.h(FollowingList, null))); | |
}; | |
return VM.h("ul", { | |
id: "following_list" | |
}, following.map(f => VM.h("li", null, f, ' ', VM.h("a", { | |
href: "#", | |
onClick: e => deleteItem(e, f), | |
style: "color: red" | |
}, "[x]")))); | |
} | |
function FollowingPanel() { | |
const addItem = () => { | |
const userInput = panel.body.querySelector('#addUserInput'); | |
if (!userInput.value) return; | |
store.add(userInput.value); | |
panel.body.querySelector('#following_list').replaceWith(VM.m(VM.h(FollowingList, null))); | |
userInput.value = ''; | |
}; | |
return VM.h(VM.Fragment, null, VM.h("div", { | |
className: styles.title | |
}, VM.h("span", null, "Following on Kick"), VM.h("button", { | |
onClick: hidePanel | |
}, "Close")), VM.h("p", { | |
className: styles.desc | |
}, "Add user:", VM.h("input", { | |
type: "text", | |
id: "addUserInput", | |
style: "margin: 0 10px;" | |
}), VM.h("button", { | |
onClick: addItem | |
}, "Add")), VM.h("div", { | |
id: "following_list" | |
}, VM.h(FollowingList, null))); | |
} | |
const realFetch = unsafeWindow.fetch.bind(unsafeWindow); | |
unsafeWindow.fetch = async function (resource, options) { | |
const response = await realFetch(resource, options); | |
if (!(typeof resource === 'string' ? resource : resource.url).includes('gql.twitch.tv/gql')) { | |
return response; | |
} | |
const text = response.text.bind(response); | |
const json = response.json.bind(response); | |
response.text = async function () { | |
const e = await text(); | |
try { | |
const parsedText = JSON.parse(e); | |
return await handleResponse(parsedText), JSON.stringify(parsedText); | |
} catch (t) { | |
return e; | |
} | |
}; | |
response.json = async function () { | |
const parsedJson = await json(); | |
try { | |
return await handleResponse(parsedJson), parsedJson; | |
} catch (t) { | |
return parsedJson; | |
} | |
}; | |
return response; | |
}; | |
async function handleResponse(e) { | |
if (Array.isArray(e)) for (const t of e) await handleGql(t); | |
return e; | |
} | |
async function handleGql(res) { | |
switch (res.extensions.operationName) { | |
case 'FollowingLive_CurrentUser': | |
{ | |
console.log('injection: following live data'); | |
break; | |
} | |
case 'PersonalSections': | |
{ | |
const n = res.data.personalSections.find(e => 'FOLLOWED_SECTION' === e.type || 'RECS_FOLLOWED_SECTION' === e.type); | |
if (n) { | |
const following = store.get(); | |
console.log('checking kick', following); | |
for (const u of following) { | |
var _kickData$livestream; | |
console.log(u); | |
const kickData = await getKickData(u); | |
const is_live = !!((_kickData$livestream = kickData.livestream) != null && _kickData$livestream.is_live); | |
if (!is_live) { | |
continue; | |
} | |
const id = (Math.random() * 10000).toFixed(0); | |
n.items.push({ | |
trackingID: id, | |
promotionsCampaignID: '', | |
user: { | |
id: id, | |
login: kickData.user.username, | |
displayName: kickData.user.username, | |
profileImageURL: kickData.user.profile_pic, | |
primaryColorHex: '00FFE2', | |
broadcastSettings: { | |
id: id, | |
title: kickData.livestream.session_title, | |
__typename: 'BroadcastSettings' | |
}, | |
channel: { | |
id: id, | |
creatorAnniversaries: { | |
id: id, | |
isAnniversary: !1, | |
__typename: 'CreatorAnniversaries' | |
}, | |
__typename: 'Channel' | |
}, | |
__typename: 'User' | |
}, | |
label: 'NONE', | |
content: { | |
id: id, | |
previewImageURL: kickData.user.profile_pic, | |
broadcaster: { | |
id: id, | |
broadcastSettings: { | |
id: id, | |
title: kickData.livestream.session_title, | |
__typename: 'BroadcastSettings' | |
}, | |
__typename: 'User' | |
}, | |
viewersCount: kickData.livestream.viewer_count, | |
self: { | |
canWatch: !0, | |
isRestricted: !1, | |
restrictionType: null, | |
__typename: 'StreamSelfConnection' | |
}, | |
game: { | |
id: '-1', | |
displayName: 'Kick', | |
name: 'Kick', | |
__typename: 'Game' | |
}, | |
type: 'live', | |
__typename: 'Stream' | |
}, | |
__typename: 'PersonalSectionChannel' | |
}); | |
} | |
n.items.sort((e, t) => (t.content.viewersCount || 0) - (e.content.viewersCount || 0)); | |
} | |
break; | |
} | |
case 'StreamMetadata': | |
{ | |
console.log('injection: stream metadata'); | |
break; | |
} | |
} | |
} | |
async function getKickData(username) { | |
const response = await fetch('https://kick.com/api/v1/channels/' + username); | |
return await response.json(); | |
} | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment