Advertisement
Guest User

jetriBetter

a guest
Apr 3rd, 2022
147
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name        jetriBetter
  3. // @namespace   jetriBetter
  4. // @match       https://hololive.jetri.co/*
  5. // @run-at      document-start
  6. // @grant       none
  7. // @version     1.0
  8. // @author      トワ…
  9. // @description Uses the APIs from holodex for the holotools website
  10. // ==/UserScript==
  11.  
  12. "use strict";
  13.  
  14. // Emits 2 event types on `document`, where the event's `detail` property holds the jetri-fied API response:
  15. //   "jetri-live": /v1/live
  16. //   "jetri-channels": /v1/channels (`total` and `count` properties are always 1)
  17.  
  18. const jetriLiveFromHolodex = (x) => ({
  19.   id: x.id,
  20.   yt_video_key: x.id,
  21.   bb_video_id: null,
  22.   title: x.title,
  23.   thumbnail: null,
  24.   status: x.status,
  25.   live_schedule: x.start_scheduled,
  26.   live_start: x.start_actual ?? null,
  27.   live_end: null,
  28.   live_viewers: x.live_viewers ?? null,
  29.   channel: {
  30.     id: x.channel.id,
  31.     yt_channel_id: x.channel.id,
  32.     bb_space_id: null,
  33.     name: x.channel.name,
  34.     photo: x.channel.photo,
  35.     published_at: null, // unused
  36.     twitter_link: null, // unused
  37.     view_count: 0,
  38.     subscriber_count: 0,
  39.     video_count: 0,
  40.   },
  41. });
  42.  
  43. const jetriChannelFromHolodex = (x, i) => ({
  44.   id: x.id,
  45.   yt_channel_id: x.id,
  46.   bb_space_id: null,
  47.   name: x.name,
  48.   description: "",
  49.   photo: x.photo,
  50.   published_at: i, // used only for sorting
  51.   twitter_link: x.twitter,
  52.   view_count: 0,
  53.   subscriber_count: x.subscriber_count,
  54.   video_count: x.video_count,
  55.   video_original: x.video_count,
  56. });
  57.  
  58. const handleLive = ({ target }) => {
  59.   const streams = [];
  60.   for (const stream of JSON.parse(target.responseText)) {
  61.     const isYoutubeStream = stream.placeholderType !== "external-stream"
  62.       && (stream.status === "live" || stream.status === "upcoming");
  63.     if (isYoutubeStream) {
  64.       streams.push(jetriLiveFromHolodex(stream));
  65.     }
  66.   }
  67.  
  68.   const result = streams.reduce((a, stream) => {
  69.     a[stream.status].push(stream);
  70.     return a;
  71.   }, {
  72.     live: [],
  73.     upcoming: [],
  74.     ended: [],
  75.     cached: true,
  76.   });
  77.   Object.defineProperty(target, "responseText", {
  78.     value: JSON.stringify(result),
  79.     writable: true,
  80.     enumerable: true,
  81.     configrable: true,
  82.   });
  83.  
  84.   return result;
  85. };
  86.  
  87. const handleChannels = ({ target }) => {
  88.   const channels = JSON.parse(target.responseText).map(jetriChannelFromHolodex);
  89.  
  90.   Object.defineProperty(target, "responseText", {
  91.     value: JSON.stringify({
  92.       count: 1,
  93.       total: 1,
  94.       channels,
  95.     }),
  96.     writable: true,
  97.     enumerable: true,
  98.     configrable: true,
  99.   });
  100.  
  101.   return channels;
  102. };
  103.  
  104. const makeLoadHandler = (wrapper, name) => (fn) => (event) => {
  105.   if (event.target.readyState === 4) {
  106.     const detail = wrapper(event);
  107.     document.dispatchEvent(new CustomEvent(name, { detail }));
  108.   }
  109.   fn.call(event.target, event);
  110. };
  111.  
  112. const onreadystatechangeDescriptor =
  113.   Object.getOwnPropertyDescriptor(XMLHttpRequest.prototype, "onreadystatechange");
  114.  
  115. const hijackReadystatechange = (xhr, wrapper) => void Object
  116.   .defineProperty(xhr, "onreadystatechange", {
  117.     get() {
  118.       return onreadystatechangeDescriptor.get.call(this);
  119.     },
  120.     set(fn) {
  121.       onreadystatechangeDescriptor.set.call(this, wrapper(fn));
  122.     },
  123.     enumerable: true,
  124.     configrable: true,
  125.   });
  126.  
  127. const origOpen = XMLHttpRequest.prototype.open;
  128. const holodexLiveApiUrl =
  129.   "https://holodex.net/api/v2/live?type=placeholder%2Cstream&org=Hololive";
  130. const holodexChannelsApiUrl =
  131.   "https://holodex.net/api/v2/channels?limit=100&offset=0&type=vtuber&org=Hololive&sort=suborg&order=asc";
  132.  
  133. Object.defineProperty(XMLHttpRequest.prototype, "open", {
  134.   value: function open(method, url) {
  135.     if (method.toUpperCase() === "GET") {
  136.       const _url = new URL(url, "https://api.holotools.app/");
  137.       if (_url.pathname === "/v1/live") {
  138.         hijackReadystatechange(this, makeLoadHandler(handleLive, "jetri-live"));
  139.  
  140.         return origOpen.call(this, "GET", holodexLiveApiUrl);
  141.       } else if (_url.pathname === "/v1/channels") {
  142.         hijackReadystatechange(this, makeLoadHandler(handleChannels, "jetri-channels"));
  143.  
  144.         return origOpen.call(this, "GET", holodexChannelsApiUrl);
  145.       }
  146.     }
  147.  
  148.     return origOpen.apply(this, arguments);
  149.   },
  150.   writable: true,
  151.   configrable: true,
  152. });
  153.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement