presence.ts 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502
  1. import { Assets } from 'premid'
  2. const presence = new Presence({
  3. clientId: '837833278777065503',
  4. })
  5. const browsingTimestamp = Math.floor(Date.now() / 1000)
  6. /**
  7. * Replaces "-" with " ", optional first letter of every word in uppercase (the first letter will be always uppercase)
  8. * @param input Input replacing "-" with " "; First letter always uppercase
  9. * @param everyFirstLetterUppercase If true, first letter of each word is capitalized
  10. * @returns {string} The input with "-" replaced with " " and the first letter of each word capitalized
  11. // (false) "terms-of-service" -> "Terms of service"; (true) "rift-s" -> "Rift S"
  12. */
  13. function splitOnDashes(input: string, everyFirstLetterUppercase = false): string {
  14. return input
  15. .split('-')
  16. .map((s, i) =>
  17. i === 0 || everyFirstLetterUppercase
  18. ? s.charAt(0).toUpperCase() + s.slice(1)
  19. : s,
  20. )
  21. .join(' ')
  22. }
  23. function isInViewport(ele: HTMLElement) {
  24. if (!ele)
  25. return false
  26. const bounding = ele.getBoundingClientRect()
  27. return (
  28. bounding.top >= 0
  29. && bounding.left >= 0
  30. && bounding.right
  31. <= (window.innerWidth || document.documentElement.clientWidth)
  32. && bounding.bottom
  33. <= (window.innerHeight || document.documentElement.clientHeight)
  34. )
  35. }
  36. presence.on('UpdateData', async () => {
  37. const presenceData: PresenceData = {
  38. largeImageKey: 'https://cdn.rcd.gg/PreMiD/websites/O/Oculus/assets/logo.jpg',
  39. startTimestamp: browsingTimestamp,
  40. }
  41. const hostName = document.location.hostname.replace('www.', '')
  42. const path = window.location.pathname.split('/').slice(1)
  43. const setting = {
  44. showButtons: await presence.getSetting<boolean>('buttons'),
  45. showTimestamp: await presence.getSetting<boolean>('timestamp'),
  46. showCartTotal: await presence.getSetting<boolean>('shop_total'),
  47. }
  48. switch (hostName) {
  49. // Support pages
  50. case 'support.oculus.com': {
  51. presenceData.details = 'Viewing Support Page:'
  52. if (path[0] === '' || !path[0]) {
  53. presenceData.state = 'Home'
  54. }
  55. else {
  56. // Gets the biggest title of page
  57. const article = document.querySelector('h1')?.textContent
  58. || document.querySelector('h2')?.textContent
  59. || document.querySelector('h4')?.textContent
  60. if (article) {
  61. presenceData.state = article.length > 128 ? `${article.slice(0, 125)}...` : article
  62. presenceData.buttons = [
  63. {
  64. label: 'Open Article',
  65. url: `https://${hostName}/${path[0]}`,
  66. },
  67. ]
  68. }
  69. else {
  70. presenceData.state = 'Unknown Article'
  71. }
  72. }
  73. break
  74. }
  75. case 'secure.oculus.com': {
  76. presenceData.details = 'Oculus Account'
  77. if (path[0] === 'my' && path[1] === 'profile')
  78. presenceData.state = 'Profile'
  79. else if (path[0] === 'my' && path[1] === 'orders')
  80. presenceData.state = 'Orders'
  81. else if (path[0] === 'my' && path[1] === 'subscriptions')
  82. presenceData.state = 'Subscriptions'
  83. else if (path[0] === 'my' && path[1] === 'quest')
  84. presenceData.state = 'Quest Experiences'
  85. else if (path[0] === 'my' && path[1] === 'rift')
  86. presenceData.state = 'Rift Experiences'
  87. else if (path[0] === 'my' && path[1] === 'gear-vr-go')
  88. presenceData.state = 'Gear VR and Oculus Go Experiences'
  89. else if (path[0] === 'my' && path[1] === 'payment-methods')
  90. presenceData.state = 'Payment methods'
  91. else if (path[0] === 'my' && path[1] === 'devices')
  92. presenceData.state = 'Devices'
  93. else if (path[0] === 'my' && path[1] === 'security')
  94. presenceData.state = 'Security'
  95. else if (path[0] === 'my' && path[1] === 'friends')
  96. presenceData.state = 'Friends'
  97. else if (path[0] === 'my' && path[1] === 'notifications')
  98. presenceData.state = 'Notification Settings'
  99. else if (path[0] === 'my' && path[1] === 'emails')
  100. presenceData.state = 'Email Preferences'
  101. else if (path[0] === 'my' && path[1] === 'privacy')
  102. presenceData.state = 'Privacy Center'
  103. else if (path[0] === 'my' && path[1] === 'linked-accounts')
  104. presenceData.state = 'Facebook Settings'
  105. else if (path[0] === 'my' && path[1] === 'preview-apps')
  106. presenceData.state = 'Preview apps'
  107. else if (path[0] === 'my' && path[1] === 'authorized-organizations')
  108. presenceData.state = 'Authorized organizations'
  109. else if (path[0] === 'my' && path[1] === 'places')
  110. presenceData.state = 'Places'
  111. else presenceData.state = 'Other'
  112. break
  113. }
  114. // Main pages
  115. case 'oculus.com': {
  116. // Hompage
  117. if (path[0] === '' || !path[0]) {
  118. presenceData.details = 'Viewing Homepage'
  119. }
  120. else {
  121. switch (path[0]) {
  122. case 'legal': {
  123. presenceData.smallImageKey = Assets.Reading
  124. presenceData.smallImageText = 'Reading'
  125. presenceData.details = splitOnDashes(path[1]!, false)
  126. break
  127. }
  128. // Specific headsets
  129. case 'quest':
  130. case 'quest-2':
  131. case 'go':
  132. case 'rift':
  133. case 'rift-s': {
  134. presenceData.details = 'Viewing Product:'
  135. presenceData.state = splitOnDashes(path[0])
  136. if (
  137. isInViewport(
  138. document.querySelector(
  139. 'h2.rbpbduva.slrhy5ou.sw32xbbe.jma5w017.pvc73czq.dos3uok6.bsc86jdp.sj117cy1.e5fbeixf.o5uldogj.c7pcq5cm.i13o4lny.l4s1jwut.thcaz4uc.tcjo4660.mm8y8im2.mnbq7hy4',
  140. )!,
  141. )
  142. || isInViewport(
  143. document.querySelector(
  144. '.b52kj89e.sq39p0kj.rbj7b54s.pjumf6uq.a0585srg.i92ihv9n.baw4mjhw.ptamchqq.j0w2kb1n.i2wm47ke.qol2cro8.okr54ooa.klsajntx.rd0pab4s.oq0i6scd.ayos5gsh.h21vdxyh.og1bctnk.crbex1nj.plgazb4k.tr6l6ww2.e7vbqoz9.r49rl50p.q8abdh1i.tcghoajs.cp4ljrx1.a0avu08k.anf3k8p9.q2mnpuig.nqouel9a.k5dtgqef.aawx349i',
  145. )!,
  146. )
  147. || isInViewport(
  148. document.querySelector('div > div.a5eizvf9.rm933jvs')!,
  149. )
  150. ) {
  151. presenceData.details = 'Reading Reviews of:'
  152. }
  153. else if (path[1]) {
  154. presenceData.details = `Product - ${splitOnDashes(path[0])}`
  155. presenceData.state = document.querySelector(
  156. 'h1.rbpbduva.slrhy5ou.sw32xbbe.tssb32rf.q8m3c5hr.sjvld55j.h2xm5abc.t2genh0f.aiyvpu16.d29ffpmv.ecv66445.mm8y8im2.i7qx9w64.rhjqn6gv.p5bjl15m.rz0v8pod.mnbq7hy4',
  157. )?.textContent || 'Unknown'
  158. }
  159. presenceData.buttons = [
  160. {
  161. label: 'View Product',
  162. url: `https://${hostName}/${path[0]}`,
  163. },
  164. ]
  165. break
  166. }
  167. case 'accessories': {
  168. presenceData.details = path[1] === 'quest-2'
  169. || path[1] === 'quest'
  170. || path[1] === 'rift-s'
  171. || path[1] === 'rift'
  172. || path[1] === 'go'
  173. ? 'Viewing Accessories for:'
  174. : 'Viewing Accessory:'
  175. presenceData.state = splitOnDashes(path[1]!)
  176. if (
  177. path[1] !== 'quest-2'
  178. && path[1] !== 'quest'
  179. && path[1] !== 'rift-s'
  180. && path[1] !== 'rift'
  181. && path[1] !== 'go'
  182. ) {
  183. presenceData.buttons = [
  184. {
  185. label: 'View Accessory',
  186. url: `https://${hostName}/accessories/${path[1]}`,
  187. },
  188. ]
  189. }
  190. break
  191. }
  192. case 'cart': {
  193. if (setting.showCartTotal) {
  194. presenceData.details = 'Shop - Cart'
  195. presenceData.state = `Total: ${
  196. document.querySelectorAll(
  197. 'div.alzwoclg.sl27f92c > span.rbpbduva.slrhy5ou.ozgaokry.oti77mm6.ghiirjs1.mm8y8im2.i7qx9w64.rhjqn6gv.p5bjl15m.rz0v8pod.mnbq7hy4',
  198. )[1]?.textContent || '$0'
  199. }`
  200. }
  201. else {
  202. presenceData.details = 'Viewing Page'
  203. presenceData.state = 'Cart'
  204. }
  205. break
  206. }
  207. case 'checkout': {
  208. if (setting.showCartTotal) {
  209. presenceData.details = 'Shop - Checkout'
  210. presenceData.state = `Total: ${
  211. document.querySelector(
  212. '.rbpbduva.ku1pdt15.ttdjqg0v.oz5golib.nd8uflda.i7qx9w64.rhjqn6gv',
  213. )?.textContent || '$0'
  214. }`
  215. }
  216. else {
  217. presenceData.details = 'Viewing Page'
  218. presenceData.state = 'Checkout'
  219. }
  220. break
  221. }
  222. case 'compare': {
  223. const headset = {
  224. left: document.querySelector<HTMLSelectElement>('div._9erd select._9ere')!,
  225. right: document.querySelectorAll<HTMLSelectElement>('div._9erd select._9ere')[1]!,
  226. }
  227. presenceData.details = 'Comparing Headsets:'
  228. presenceData.state = `${
  229. headset.left?.options[headset.left.selectedIndex]?.text
  230. || 'Unknown'
  231. } x ${
  232. headset.right?.options[headset.right.selectedIndex]?.text
  233. || 'Unknown'
  234. }`
  235. break
  236. }
  237. case 'vr-for-good': {
  238. if (path[1] === 'stories' && path[2]) {
  239. presenceData.details = 'VR for Good - Story:'
  240. presenceData.state = document.querySelector(
  241. 'h1._2e90._2e93._2e94.article-hero__title',
  242. )?.textContent || splitOnDashes(path[2])
  243. presenceData.buttons = [
  244. {
  245. label: 'Read Story',
  246. url: `https://${hostName}/vr-for-good/stories/${path[2]}`,
  247. },
  248. ]
  249. }
  250. else {
  251. presenceData.details = 'Viewing Page:'
  252. presenceData.state = 'VR for Good'
  253. }
  254. break
  255. }
  256. case 'safety-center': {
  257. presenceData.details = 'Viewing Safety Center'
  258. if (path[1])
  259. presenceData.state = `For ${splitOnDashes(path[1])}`
  260. break
  261. }
  262. // Blogs
  263. case 'blog': {
  264. // All blogs
  265. if (!path[1]) {
  266. presenceData.details = 'Viewing all Blogs'
  267. }
  268. // Viewing a blog
  269. else {
  270. presenceData.details = 'Reading Blog:'
  271. let blog = document.querySelector('#blog-heading')?.textContent
  272. if (blog && blog.length > 128)
  273. blog = `${blog.slice(0, 125)}...`
  274. presenceData.state = blog ?? 'Unknown Blog'
  275. presenceData.buttons = [
  276. {
  277. label: 'Read Blog Post',
  278. url: `https://${hostName}/${path[0]}/${path[1]}`,
  279. },
  280. ]
  281. }
  282. break
  283. }
  284. // Store pages
  285. case 'experiences': {
  286. if (
  287. !path[1]
  288. || path[1] === 'gaming'
  289. || path[1] === 'fitness'
  290. || path[1] === 'entertainment'
  291. ) {
  292. if (path[1]) {
  293. presenceData.details = 'Viewing Experience:'
  294. presenceData.state = splitOnDashes(path[1])
  295. presenceData.buttons = [
  296. {
  297. label: 'View Experience',
  298. url: `https://${hostName}/experiences/${path[1]}`,
  299. },
  300. ]
  301. }
  302. else {
  303. presenceData.details = 'Viewing Experiences'
  304. }
  305. break
  306. }
  307. else {
  308. presenceData.details = `Store for ${splitOnDashes(path[1])}`
  309. presenceData.buttons = [
  310. {
  311. label: 'View Store',
  312. url: `https://${hostName}/${path[0]}`,
  313. },
  314. ]
  315. // Store home page
  316. if (path[2] === '' || !path[2]) {
  317. presenceData.state = 'Home'
  318. }
  319. // Section aka showcases
  320. else {
  321. switch (path[2]) {
  322. case 'section': {
  323. presenceData.details = `Store for ${splitOnDashes(
  324. path[1],
  325. )} - Showcase`
  326. presenceData.state = document.querySelectorAll('.section-header__title')[0]
  327. ?.textContent ?? 'Loading...'
  328. presenceData.buttons.push({
  329. label: 'View Showcase',
  330. url: `https://${hostName}/${path[0]}/${path[1]}/${path[2]}/${path[3]}`,
  331. })
  332. // Developer posts
  333. break
  334. }
  335. case 'developer-post': {
  336. const title = document.querySelector('._9cq4')?.textContent
  337. ?? 'Unknown'
  338. presenceData.state = `Dev-Post: ${
  339. title.length > 118 ? `${title.slice(0, 115)}...` : title
  340. }`
  341. presenceData.buttons.push({
  342. label: 'Read Dev-Post',
  343. url: `https://${hostName}/${path[0]}/${path[1]}/${path[2]}/${path[3]}`,
  344. })
  345. // Searching
  346. break
  347. }
  348. case 'search': {
  349. presenceData.details = `Store for ${splitOnDashes(
  350. path[1],
  351. )} - Search`
  352. presenceData.state = document.querySelector('.disco-search__query')
  353. ?.textContent ?? 'Unknown'
  354. // Bundles
  355. break
  356. }
  357. default:
  358. if (
  359. document.querySelector(
  360. 'div.bundle-detail-page__description > h1',
  361. )?.textContent
  362. ) {
  363. presenceData.details = `Store for ${splitOnDashes(
  364. path[1],
  365. )} - Bundle`
  366. presenceData.state = document.querySelector(
  367. 'div.bundle-detail-page__description > h1',
  368. )?.textContent ?? 'Loading...'
  369. presenceData.buttons.push({
  370. label: 'View bundle',
  371. url: `https://${hostName}/${path[0]}/${path[1]}/${path[2]}`,
  372. })
  373. // Games
  374. }
  375. else {
  376. presenceData.details = `Store for ${splitOnDashes(
  377. path[1],
  378. )} - Game`
  379. presenceData.state = document.querySelectorAll('.app-description__title')[0]
  380. ?.textContent ?? 'Loading...'
  381. presenceData.buttons.push({
  382. label: 'View Game',
  383. url: `https://${hostName}/${path[0]}/${path[1]}/${path[2]}`,
  384. })
  385. }
  386. }
  387. }
  388. break
  389. }
  390. }
  391. case 'research': {
  392. presenceData.details = 'Viewing Page:'
  393. presenceData.state = 'Research'
  394. presenceData.buttons = [
  395. {
  396. label: 'View Page',
  397. url: `https://${hostName}/${path[0]}`,
  398. },
  399. ]
  400. break
  401. }
  402. case 'careers': {
  403. presenceData.details = 'Viewing Page:'
  404. presenceData.state = 'Career'
  405. presenceData.buttons = [
  406. {
  407. label: 'View Page',
  408. url: `https://${hostName}/${path[0]}`,
  409. },
  410. ]
  411. break
  412. }
  413. case 'holiday': {
  414. presenceData.details = 'Viewing Page:'
  415. presenceData.state = document.querySelector(
  416. 'h1.rbpbduva.p00k3eym.s2xhriuh.lvqykqo5.se9zvvl8.m98qiilh.dlvx4kqw',
  417. )?.textContent ?? 'Holiday deal'
  418. presenceData.buttons = [
  419. {
  420. label: 'View Page',
  421. url: `https://${hostName}/${path[0]}`,
  422. },
  423. ]
  424. break
  425. }
  426. case 'referrals': {
  427. presenceData.details = 'Viewing Page:'
  428. presenceData.state = 'Referrals'
  429. break
  430. }
  431. default: {
  432. presenceData.details = 'Viewing Page:'
  433. presenceData.state = 'Unknown Page'
  434. break
  435. }
  436. }
  437. }
  438. break
  439. }
  440. }
  441. if (!presenceData.details) {
  442. presence.setActivity()
  443. }
  444. else {
  445. // Delete button(s) / timestamp relating to the setting
  446. if (presenceData.buttons && !setting.showButtons)
  447. delete presenceData.buttons
  448. if (!setting.showTimestamp)
  449. delete presenceData.startTimestamp
  450. presence.setActivity(presenceData)
  451. }
  452. })