presence.ts 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. const presence = new Presence({
  2. clientId: "815947069117169684",
  3. }),
  4. getStrings = async () =>
  5. presence.getStrings(
  6. {
  7. play: "general.playing",
  8. pause: "general.paused",
  9. browse: "general.browsing",
  10. episode: "general.episode",
  11. searchSomething: "general.searchSomething",
  12. watchVideo: "general.buttonWatchVideo",
  13. viewPage: "general.viewPage",
  14. viewingShow: "general.viewShow",
  15. viewingMovie: "general.viewMovie",
  16. watchMovie: "general.buttonWatchMovie",
  17. watchEpisode: "general.buttonViewEpisode",
  18. searching: "general.search",
  19. },
  20. await presence.getSetting<string>("lang").catch(() => "en")
  21. ),
  22. browsingTimestamp = Math.floor(Date.now() / 1000);
  23. let strings: Awaited<ReturnType<typeof getStrings>>,
  24. oldPath: string = null,
  25. oldLang: string = null,
  26. seriesInfo: SeriesInfo[] = null,
  27. isWatingForResponse = false;
  28. async function getSeriesInfo(
  29. id: string,
  30. request: "/product/listall" | "/vod/product-list"
  31. ): Promise<SeriesInfo[]> {
  32. isWatingForResponse = true;
  33. /* eslint-disable camelcase */
  34. const params = new URLSearchParams({
  35. platform_flag_label: "web",
  36. area_id: "2",
  37. language_flag_id: "3",
  38. platformFlagLabel: "web",
  39. areaId: "2",
  40. languageFlagId: "3",
  41. countryCode: location.pathname.split("/")[2].toUpperCase(),
  42. ut: "0",
  43. r: request,
  44. series_id: id,
  45. product_id: id,
  46. os_flag_id: "1",
  47. }).toString(),
  48. /* eslint-enable camelcase */
  49. token = await generateToken(),
  50. resp = await fetch(
  51. `https://api-gateway-global.viu.com/api/mobile?${params}`,
  52. {
  53. headers: {
  54. accept: "application/json",
  55. authorization: `Bearer ${token}`,
  56. },
  57. method: "GET",
  58. }
  59. );
  60. return resp.json().then(x => {
  61. isWatingForResponse = false;
  62. if (request === "/product/listall") return x.data.product;
  63. else return x.data.product_list;
  64. });
  65. }
  66. async function generateToken() {
  67. const response = await fetch(
  68. "https://api-gateway-global.viu.com/api/auth/token",
  69. {
  70. headers: {
  71. "content-type": "application/json",
  72. },
  73. body: JSON.stringify({
  74. appVersion: "3.0.10",
  75. countryCode: location.pathname.split("/")[2].toUpperCase(),
  76. language: 4,
  77. platform: "browser",
  78. platformFlagLabel: "web",
  79. uuid: self.crypto.randomUUID(),
  80. }),
  81. method: "POST",
  82. }
  83. );
  84. return response.json().then(x => x.token);
  85. }
  86. /* eslint-disable camelcase */
  87. // Hack to resolve Deepscan
  88. const no_op = (a: number) => a + 1;
  89. no_op(0);
  90. interface SeriesInfo {
  91. is_movie: number;
  92. series_id: string;
  93. series_name: string;
  94. series_category_name?: string;
  95. series_cover_landscape_image_url?: string;
  96. series_cover_portrait_image_url?: string;
  97. number?: string;
  98. }
  99. /* eslint-enable camelcase */
  100. const enum Assets {
  101. Logo = "https://cdn.rcd.gg/PreMiD/websites/V/Viu/assets/0.png",
  102. LogoText = "https://cdn.rcd.gg/PreMiD/websites/V/Viu/assets/1.png",
  103. }
  104. presence.on("UpdateData", async () => {
  105. const [newLang, buttonsOn, presenceLogo] = await Promise.all([
  106. presence.getSetting<string>("lang").catch(() => "en"),
  107. presence.getSetting<boolean>("buttons"),
  108. presence.getSetting<number>("logo"),
  109. ]);
  110. if (oldLang !== newLang || !strings) {
  111. oldLang = newLang;
  112. strings = await getStrings();
  113. }
  114. if (
  115. (!seriesInfo || oldPath !== location.pathname) &&
  116. location.pathname.includes("/vod/") &&
  117. !isWatingForResponse
  118. ) {
  119. oldPath = location.pathname;
  120. const info = await getSeriesInfo(
  121. location.pathname.split("/")[5],
  122. "/product/listall"
  123. );
  124. if (info)
  125. seriesInfo = await getSeriesInfo(info[0].series_id, "/vod/product-list");
  126. }
  127. const presenceData: PresenceData = {
  128. details: strings.browse,
  129. smallImageKey: Assets.Reading,
  130. largeImageKey: [Assets.Logo, Assets.LogoText, Assets.Logo, Assets.Logo][
  131. presenceLogo
  132. ],
  133. startTimestamp: browsingTimestamp,
  134. };
  135. if (document.location.pathname.includes("/vod/")) {
  136. const video = document.querySelector("video"),
  137. fullEpisodeName = document.querySelector(
  138. ".css-330rps #series_ep_title"
  139. )?.textContent,
  140. isMovie = !fullEpisodeName;
  141. if (video) {
  142. let episodeNumber = "",
  143. episodeName = "",
  144. hasEpName = false,
  145. part: string[] = [];
  146. if (fullEpisodeName) {
  147. episodeNumber = fullEpisodeName.split(".")[0];
  148. episodeName = fullEpisodeName.split(".").slice(1).join(".");
  149. hasEpName = !episodeName.includes("EP.");
  150. part = episodeName.match(/([1-9]\/[1-9])/g);
  151. }
  152. presenceData.details =
  153. document.querySelector("#series_title").textContent;
  154. if (isMovie) presenceData.state = "Movie";
  155. else {
  156. presenceData.state = `EP.${episodeNumber}${
  157. part ? ` • ${part[0]} ` : ""
  158. }${hasEpName ? ` • ${episodeName}` : ""}`;
  159. }
  160. const coverPortraitImage =
  161. seriesInfo?.[0].series_cover_portrait_image_url,
  162. coverLandscapeImage = seriesInfo?.[0].series_cover_landscape_image_url;
  163. if (presenceLogo > 1) {
  164. presenceData.largeImageKey =
  165. [
  166. coverPortraitImage || coverLandscapeImage,
  167. coverLandscapeImage || coverPortraitImage,
  168. ][presenceLogo - 2] || Assets.Logo;
  169. }
  170. presenceData.smallImageKey = video.paused ? Assets.Pause : Assets.Play;
  171. presenceData.smallImageText = video.paused ? strings.pause : strings.play;
  172. [presenceData.startTimestamp, presenceData.endTimestamp] =
  173. presence.getTimestampsfromMedia(video);
  174. if (buttonsOn) {
  175. presenceData.buttons = [
  176. {
  177. label: isMovie ? strings.watchMovie : strings.watchEpisode,
  178. url: document.baseURI,
  179. },
  180. ];
  181. }
  182. if (video.paused) {
  183. delete presenceData.startTimestamp;
  184. delete presenceData.endTimestamp;
  185. }
  186. } else {
  187. presenceData.details = isMovie
  188. ? strings.viewingMovie
  189. : strings.viewingShow;
  190. presenceData.state = document.querySelector("#series_title").textContent;
  191. }
  192. } else if (
  193. document.querySelector("input#search_input_txt") &&
  194. document.location.search
  195. ) {
  196. presenceData.details = strings.searchSomething;
  197. presenceData.smallImageKey = Assets.Search;
  198. presenceData.smallImageText = strings.searching;
  199. }
  200. presence.setActivity(presenceData);
  201. });