presence.ts 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. type Functionlize<T> = {
  2. [P in keyof T]: () => T[P];
  3. }
  4. interface Route extends Functionlize<Partial<PresenceDataFull>> {
  5. path: RegExp
  6. playback?: () => boolean
  7. run?: () => PresenceData
  8. }
  9. enum Settings {
  10. TIMESTAMP = 'timestamp',
  11. BUTTONS = 'buttons',
  12. LOGO = 'logo',
  13. }
  14. enum Icons {
  15. PAUSED = 'paused',
  16. PLAYED = 'played',
  17. SEARCHING = 'searching',
  18. LOCATION = 'location',
  19. DISCOVERY = 'discovery',
  20. }
  21. enum Logos {
  22. LIGHT = 'light-logo',
  23. DARK = 'dark-logo',
  24. }
  25. let video = { duration: 0, currentTime: 0, paused: true }
  26. const presence = new Presence({
  27. clientId: '770030754356396052',
  28. })
  29. const startTimestamp: number = Math.floor(Date.now() / 1000)
  30. function router({
  31. path,
  32. presenceData,
  33. }: {
  34. path: string
  35. presenceData: PresenceData
  36. }): Route {
  37. const routes: Route[] = [
  38. { path: /^\/$/, details: () => 'On Homepage' },
  39. {
  40. path: /^\/episode\//,
  41. run: () => {
  42. [presenceData.startTimestamp, presenceData.endTimestamp] = presence.getTimestamps(
  43. Math.floor(video.currentTime),
  44. Math.floor(video.duration),
  45. )
  46. if (video.paused) {
  47. delete presenceData.startTimestamp
  48. delete presenceData.endTimestamp
  49. }
  50. return presenceData
  51. },
  52. playback: () => !video.paused,
  53. smallImageKey: () => (video.paused ? Icons.PAUSED : Icons.PLAYED),
  54. smallImageText: () => (video.paused ? 'Paused' : 'Played'),
  55. state: () =>
  56. document
  57. .querySelectorAll('.container')[1]
  58. ?.textContent
  59. ?.slice(1, -1)
  60. .split(' ')
  61. .slice(0, -2)
  62. .join(' '),
  63. details: () =>
  64. `Episode: ${document
  65. .querySelectorAll('.container')[1]
  66. ?.textContent
  67. ?.slice(1, -1)
  68. .split(' ')
  69. .pop()}`,
  70. buttons: () => [
  71. { label: 'Watch Episode', url: location.href },
  72. {
  73. label: 'View Anime',
  74. url: document
  75. .querySelector('.anime-page-link')
  76. ?.querySelector('a')
  77. ?.getAttribute('href') ?? '',
  78. },
  79. ],
  80. },
  81. {
  82. path: /^\/\?search_param=(.*)/,
  83. smallImageKey: () => Icons.SEARCHING,
  84. smallImageText: () => 'Searching',
  85. details: () =>
  86. `Results: ${document.querySelectorAll('.col-lg-2').length ?? 0}`,
  87. state: () =>
  88. `Searching: ${document
  89. .querySelectorAll('.container')[1]
  90. ?.textContent
  91. ?.split(' ')
  92. .slice(4, -1)
  93. .join(' ')}`,
  94. buttons: () => [{ label: 'Results', url: location.href }],
  95. },
  96. {
  97. path: /^\/anime\/(.*)/,
  98. smallImageKey: () => Icons.LOCATION,
  99. smallImageText: () => 'Viewing',
  100. details: () => 'Viewing an Anime',
  101. state: () => document.querySelector('.anime-details-title')?.textContent ?? undefined,
  102. buttons: () => [
  103. { label: 'View Anime', url: location.href },
  104. {
  105. label: 'Last Episode',
  106. url: document
  107. .querySelector('#DivEpisodesList')
  108. ?.lastElementChild
  109. ?.querySelector('a')
  110. ?.getAttribute('href') ?? '',
  111. },
  112. ],
  113. },
  114. {
  115. path: /^\/(%d9%82%d8%a7%d8%a6%d9%85%d8%a9-%d8%a7%d9%84%d8%a7%d9%86%d9%85%d9%8a|anime-(.*))/,
  116. smallImageKey: () => Icons.DISCOVERY,
  117. smallImageText: () => 'Browsing',
  118. details: () => 'Browsing for Anime',
  119. buttons: () => [{ label: 'Browse', url: location.href }],
  120. },
  121. {
  122. path: /^\/%d9%85%d9%88%d8%a7%d8%b9%d9%8a%d8%af-%d8%b9%d8%b1%d8%b6-%d8%ad%d9%84%d9%82%d8%a7%d8%aa-%d8%a7%d9%84%d8%a7%d9%86%d9%85%d9%8a/,
  123. smallImageKey: () => Icons.DISCOVERY,
  124. smallImageText: () => 'Discovering',
  125. details: () => 'Discovering Episodes Releases',
  126. buttons: () => [{ label: 'Discover', url: location.href }],
  127. },
  128. ]
  129. return routes.find(route => route.path.test(path))!
  130. }
  131. presence.on(
  132. 'iFrameData',
  133. (data: { duration: number, currentTime: number, paused: boolean }) => {
  134. video = data
  135. },
  136. )
  137. presence.on('UpdateData', async () => {
  138. const [showTimestamp, showButtons, logo] = await Promise.all([
  139. presence.getSetting<boolean>(Settings.TIMESTAMP),
  140. presence.getSetting<boolean>(Settings.BUTTONS),
  141. presence.getSetting<number>(Settings.LOGO),
  142. ])
  143. let presenceData: PresenceData = {
  144. largeImageKey: [Logos.LIGHT, Logos.DARK][logo] || Logos.LIGHT,
  145. }
  146. if (showTimestamp)
  147. presenceData.startTimestamp = startTimestamp
  148. const route = router({
  149. presenceData,
  150. path: location.href.replace(`https://${location.hostname}`, ''),
  151. })
  152. if (!route)
  153. return presence.setActivity(presenceData)
  154. if (route.run)
  155. presenceData = route.run()
  156. if (route.state)
  157. presenceData.state = route.state()
  158. if (route.details)
  159. presenceData.details = route.details()
  160. if (showButtons && route.buttons)
  161. presenceData.buttons = route.buttons() as [ButtonData, ButtonData?]
  162. if (route.largeImageKey)
  163. presenceData.largeImageKey = route.largeImageKey()
  164. if (route.smallImageKey)
  165. presenceData.smallImageKey = route.smallImageKey()
  166. if (route.smallImageText)
  167. presenceData.smallImageText = route.smallImageText()
  168. if (showTimestamp && route.endTimestamp)
  169. presenceData.endTimestamp = route.endTimestamp()
  170. presence.setActivity(presenceData, route.playback ? route.playback() : false)
  171. })