presence.ts 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import { ActivityType, Assets } from 'premid'
  2. const LOGO_URL = 'https://cdn.rcd.gg/PreMiD/websites/T/Tidal/assets/logo.png'
  3. const presence = new Presence({ clientId: '901591802342150174' })
  4. async function getStrings() {
  5. return presence.getStrings(
  6. {
  7. play: 'general.playing',
  8. pause: 'general.paused',
  9. viewSong: 'general.buttonViewSong',
  10. },
  11. await presence.getSetting<string>('lang').catch(() => 'en'),
  12. )
  13. }
  14. let strings: Awaited<ReturnType<typeof getStrings>>
  15. let oldLang: string | null = null
  16. presence.on('UpdateData', async () => {
  17. if (!document.querySelector('#footerPlayer'))
  18. return presence.setActivity({ largeImageKey: LOGO_URL })
  19. const [newLang, timestamps, cover, buttons] = await Promise.all([
  20. presence.getSetting<string>('lang').catch(() => 'en'),
  21. presence.getSetting<boolean>('timestamps'),
  22. presence.getSetting<boolean>('cover'),
  23. presence.getSetting<boolean>('buttons'),
  24. ])
  25. if (oldLang !== newLang || !strings) {
  26. oldLang = newLang
  27. strings = await getStrings()
  28. }
  29. const presenceData: PresenceData = {
  30. largeImageKey: LOGO_URL,
  31. type: ActivityType.Listening,
  32. }
  33. const songTitle = document.querySelector<HTMLAnchorElement>(
  34. '[data-test=\'footer-track-title\'] > div > a',
  35. )
  36. const currentTime = document.querySelector<HTMLElement>(
  37. 'time[data-test="current-time"]',
  38. )?.textContent
  39. const paused = document
  40. .querySelector('div[data-test="play-controls"] div > button')
  41. ?.getAttribute('data-test') === 'play'
  42. const repeatType = document
  43. .querySelector(
  44. 'div[data-test="play-controls"] > button[data-test="repeat"]',
  45. )
  46. ?.getAttribute('data-type')
  47. presenceData.details = songTitle?.textContent
  48. // get artists
  49. presenceData.state = Array.from(
  50. document.querySelectorAll<HTMLAnchorElement>('#footerPlayer .artist-link a'),
  51. )
  52. .map(artist => artist.textContent)
  53. .join(', ')
  54. if (cover) {
  55. presenceData.largeImageKey = document
  56. .querySelector(
  57. 'figure[data-test=current-media-imagery] > div > div > div > img',
  58. )
  59. ?.getAttribute('src')
  60. ?.replace('80x80', '640x640')
  61. }
  62. if (
  63. (Number.parseFloat(currentTime?.[0] ?? '') * 60 + Number.parseFloat(currentTime?.[1] ?? '')) * 1000 > 0
  64. || !paused
  65. ) {
  66. [presenceData.startTimestamp, presenceData.endTimestamp] = presence.getTimestamps(
  67. presence.timestampFromFormat(currentTime ?? ''),
  68. presence.timestampFromFormat(
  69. document.querySelector<HTMLElement>('time[data-test="duration"]')
  70. ?.textContent ?? '',
  71. ),
  72. )
  73. presenceData.smallImageKey = paused ? Assets.Pause : Assets.Play
  74. presenceData.smallImageText = paused ? strings.pause : strings.play
  75. }
  76. if (
  77. document
  78. .querySelector(
  79. 'div[data-test="play-controls"] > button[data-test="repeat"]',
  80. )
  81. ?.getAttribute('aria-checked') === 'true'
  82. ) {
  83. presenceData.smallImageKey = repeatType === 'button__repeatAll' ? Assets.Repeat : Assets.RepeatOne
  84. presenceData.smallImageText = repeatType === 'button__repeatAll' ? 'Playlist on loop' : 'On loop'
  85. delete presenceData.endTimestamp
  86. }
  87. if (buttons) {
  88. presenceData.buttons = [
  89. {
  90. label: strings.viewSong,
  91. url: songTitle?.href ?? '',
  92. },
  93. ]
  94. }
  95. if (!timestamps)
  96. delete presenceData.endTimestamp
  97. presence.setActivity(presenceData)
  98. })