presence.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405
  1. const enum Assets {
  2. Logo = "https://cdn.rcd.gg/PreMiD/websites/F/Fandom/assets/logo.png",
  3. }
  4. if (
  5. document.location.pathname.includes("/wiki/")
  6. ? document.querySelector(".skin-oasis") ||
  7. ((document.querySelector(".skin-fandomdesktop") ||
  8. document.querySelector(".skin-fandommobile")) &&
  9. !document.querySelector(".is-gamepedia"))
  10. : true
  11. ) {
  12. ((): void => {
  13. const presence = new Presence({
  14. clientId: "644400074008297512",
  15. }),
  16. browsingTimestamp = Math.floor(Date.now() / 1000);
  17. let currentURL = new URL(document.location.href),
  18. currentPath = currentURL.pathname.replace(/^\/|\/$/g, "").split("/"),
  19. presenceData: PresenceData = {
  20. details: "Viewing an unsupported page",
  21. largeImageKey: Assets.Logo,
  22. startTimestamp: browsingTimestamp,
  23. };
  24. const updateCallback = {
  25. _function: null as () => void,
  26. get function(): () => void {
  27. return this._function;
  28. },
  29. set function(parameter) {
  30. this._function = parameter;
  31. },
  32. get present(): boolean {
  33. return this._function !== null;
  34. },
  35. },
  36. /**
  37. * Initialize/reset presenceData.
  38. */
  39. resetData = (
  40. defaultData: PresenceData = {
  41. details: "Viewing an unsupported page",
  42. largeImageKey: Assets.Logo,
  43. startTimestamp: browsingTimestamp,
  44. }
  45. ): void => {
  46. currentURL = new URL(document.location.href);
  47. currentPath = currentURL.pathname.replace(/^\/|\/$/g, "").split("/");
  48. presenceData = { ...defaultData };
  49. },
  50. /**
  51. * Search for URL parameters.
  52. * @param urlParam The parameter that you want to know about the value.
  53. */
  54. getURLParam = (urlParam: string): string => {
  55. return currentURL.searchParams.get(urlParam);
  56. };
  57. ((): void => {
  58. presence.info("Running...");
  59. if (currentURL.host === "www.fandom.com") {
  60. /*
  61. Chapter 1
  62. This one is for the editorial part of Fandom.
  63. */
  64. switch (currentPath[0]) {
  65. case "": {
  66. presenceData.details = "On the index page";
  67. break;
  68. }
  69. case "signin": {
  70. presenceData.details = "Signing in";
  71. break;
  72. }
  73. case "register": {
  74. presenceData.details = "Registering an account";
  75. break;
  76. }
  77. case "articles": {
  78. presenceData.details = "Reading an article";
  79. presenceData.state = document.querySelector(
  80. ".article-header__title"
  81. ).textContent;
  82. break;
  83. }
  84. case "topics": {
  85. presenceData.details = "Viewing a topic";
  86. presenceData.state = document.querySelector(
  87. ".topic-header__title"
  88. ).firstElementChild.textContent;
  89. break;
  90. }
  91. case "video": {
  92. presenceData.details = "Watching a video";
  93. delete presenceData.startTimestamp;
  94. updateCallback.function = (): void => {
  95. presenceData.state = document.querySelector(
  96. ".video-page-featured-player__title"
  97. ).textContent;
  98. try {
  99. if (
  100. document
  101. .querySelector(".jw-icon-playback")
  102. .getAttribute("aria-label") === "Pause"
  103. ) {
  104. [presenceData.startTimestamp, presenceData.endTimestamp] =
  105. presence.getTimestampsfromMedia(
  106. document.querySelector(".jw-video")
  107. );
  108. } else delete presenceData.endTimestamp;
  109. } catch (e) {
  110. delete presenceData.endTimestamp;
  111. }
  112. };
  113. break;
  114. }
  115. case "curated": {
  116. presenceData.details = "Viewing a curation";
  117. presenceData.state =
  118. document.querySelector(".card__title").textContent;
  119. break;
  120. }
  121. case "u": {
  122. presenceData.details = "Viewing a profile page";
  123. presenceData.state = `${
  124. document.querySelector(".profile-info-card__name").textContent
  125. } (${
  126. document.querySelector(".profile-info-card__username").textContent
  127. })`;
  128. break;
  129. }
  130. default: {
  131. presenceData.details = "Viewing a page";
  132. switch (currentPath[0]) {
  133. case "explore": {
  134. presenceData.state = "Explore";
  135. break;
  136. }
  137. case "about": {
  138. presenceData.state = "About";
  139. break;
  140. }
  141. case "carriers": {
  142. presenceData.state = "Carriers";
  143. break;
  144. }
  145. case "terms-of-use": {
  146. presenceData.state = "Terms of Use";
  147. break;
  148. }
  149. case "privacy-policy": {
  150. presenceData.state = "Privacy Policy";
  151. break;
  152. }
  153. case "mediakit": {
  154. presenceData.state = "Media Kit";
  155. break;
  156. }
  157. case "local-sitemap":
  158. {
  159. presenceData.state = "Local Sitemap";
  160. // No default
  161. }
  162. break;
  163. }
  164. }
  165. }
  166. } else if (currentPath.includes("wiki")) {
  167. /*
  168. Chapter 2
  169. This one is for the wiki part on the Fandom, which was Wikia a while ago
  170. */
  171. let title: string, sitename: string;
  172. const actionResult = (): string =>
  173. getURLParam("action") || getURLParam("veaction"),
  174. lang = currentPath[0] === "wiki" ? "en" : currentPath[0],
  175. titleFromURL = (): string => {
  176. return decodeURIComponent(
  177. currentPath[0] === "index.php"
  178. ? getURLParam("title")
  179. : currentPath[0] === "wiki"
  180. ? currentPath.slice(1).join("/")
  181. : currentPath.slice(2).join("/").replaceAll("_", " ")
  182. );
  183. };
  184. try {
  185. title = document.querySelector("h1").textContent.trim();
  186. } catch (e) {
  187. title = titleFromURL();
  188. }
  189. try {
  190. sitename = (
  191. document.querySelector(
  192. "meta[property='og:site_name']"
  193. ) as HTMLMetaElement
  194. ).content;
  195. } catch (e) {
  196. sitename = (
  197. document.querySelector(".wds-community-header__sitename") ||
  198. document.querySelector(
  199. ".fandom-community-header__community-name"
  200. ) ||
  201. document.querySelector(".wds-community-bar__sitename")
  202. ).textContent.trim();
  203. }
  204. /**
  205. * Returns details based on the namespace.
  206. * @link https://en.wikipedia.org/wiki/Wikipedia:Namespace
  207. */
  208. const namespaceDetails = (): string => {
  209. return (
  210. {
  211. "-2": "Viewing a media",
  212. "-1": "Viewing a special page",
  213. 0: "Reading an article",
  214. 1: "Viewing a talk page",
  215. 2: "Viewing a user page",
  216. 3: "Viewing a user talk page",
  217. 4: "Viewing a project page",
  218. 5: "Viewing a project talk page",
  219. 6: "Viewing a file",
  220. 7: "Viewing a file talk page",
  221. 8: "Viewing an interface page",
  222. 9: "Viewing an interface talk page",
  223. 10: "Viewing a template",
  224. 11: "Viewing a template talk page",
  225. 12: "Viewing a help page",
  226. 13: "Viewing a help talk page",
  227. 14: "Viewing a category",
  228. 15: "Viewing a category talk page",
  229. 100: "Viewing a portal",
  230. 101: "Viewing a portal talk page",
  231. 110: "Viewing a forum page",
  232. 111: "Viewing a forum talk page",
  233. 420: "Viewing a GeoJson page",
  234. 421: "Viewing a GeoJson talk page",
  235. 500: "Viewing a user blog", // handled again by function below
  236. 501: "Viewing a user blog comment", // depercated, redirected
  237. 502: "Viewing a blog",
  238. 503: "Viewing a blog talk page",
  239. 710: "Viewing a media's subtitles",
  240. 711: "Viewing a media's subtitles talk page",
  241. 828: "Viewing a module",
  242. 829: "Viewing a module talk page",
  243. 1200: "Viewing a message wall",
  244. 1201: "Viewing a thread",
  245. 1202: "Viewing a message wall greeting",
  246. 2000: "Viewing a forum board", // depercated, redirected
  247. 2001: "Viewing a forum board thread", // depercated, redirected
  248. 2002: "Viewing a forum topic", // depercated, redirected
  249. }[
  250. [...document.querySelector("body").classList]
  251. .find(v => /ns--?\d/.test(v))
  252. .slice(3)
  253. ] || "Viewing a wiki page"
  254. );
  255. };
  256. if (title === "Home") {
  257. sitename = (
  258. document.querySelector(
  259. "meta[property='og:title']"
  260. ) as HTMLMetaElement
  261. ).content;
  262. presenceData.details = "On the home page";
  263. } else if (document.querySelector(".unified-search__form")) {
  264. presenceData.details = "Searching for a page";
  265. presenceData.state = (
  266. document.querySelector(
  267. ".unified-search__input__query"
  268. ) as HTMLInputElement
  269. ).value;
  270. } else if (actionResult() === "history") {
  271. presenceData.details = "Viewing revision history";
  272. presenceData.state = titleFromURL();
  273. } else if (getURLParam("diff")) {
  274. presenceData.details = "Viewing difference between revisions";
  275. presenceData.state = titleFromURL();
  276. } else if (getURLParam("oldid")) {
  277. presenceData.details = "Viewing an old revision of a page";
  278. presenceData.state = titleFromURL();
  279. } else if (namespaceDetails() === "Viewing a user blog") {
  280. if (title) {
  281. presenceData.details = "Reading a user blog post";
  282. presenceData.state = `${title} by ${
  283. document.querySelector(".page-header__blog-post-details")
  284. .firstElementChild.textContent
  285. }`;
  286. } else {
  287. presenceData.details = namespaceDetails();
  288. presenceData.state = titleFromURL();
  289. }
  290. } else if (
  291. document.querySelector("#ca-ve-edit") ||
  292. getURLParam("veaction")
  293. ) {
  294. presenceData.state = `${
  295. title.toLowerCase() === titleFromURL().toLowerCase()
  296. ? `${title}`
  297. : `${title} (${titleFromURL()})`
  298. }`;
  299. updateCallback.function = (): void => {
  300. if (actionResult() === "edit" || actionResult() === "editsource")
  301. presenceData.details = "Editing a page";
  302. else presenceData.details = namespaceDetails();
  303. };
  304. } else if (actionResult() === "edit") {
  305. presenceData.details = document.querySelector("#ca-edit")
  306. ? "Editing a page"
  307. : "Viewing source";
  308. presenceData.state = titleFromURL();
  309. } else {
  310. presenceData.details = namespaceDetails();
  311. presenceData.state = `${
  312. title.toLowerCase() === titleFromURL().toLowerCase()
  313. ? `${title}`
  314. : `${title} (${titleFromURL()})`
  315. }`;
  316. }
  317. if (presenceData.state) presenceData.state += ` | ${sitename}`;
  318. else presenceData.state = sitename;
  319. if (lang !== "en") {
  320. if (presenceData.state) presenceData.state += ` (${lang})`;
  321. else presenceData.details += ` (${lang})`;
  322. }
  323. } else if (currentPath[0] === "f") {
  324. /*
  325. Chapter 3
  326. This one is for the discussion parts on each wikis.
  327. */
  328. let sitename: string;
  329. try {
  330. sitename = (
  331. document.querySelector(
  332. "meta[property='og:site_name']"
  333. ) as HTMLMetaElement
  334. ).content;
  335. } catch (e) {
  336. sitename = document.querySelector(
  337. ".wds-community-header__sitename"
  338. ).textContent;
  339. }
  340. updateCallback.function = (): void => {
  341. if (!currentPath[1]) {
  342. const category = document.querySelector(
  343. ".category-filter__dropdown-toggle"
  344. ).textContent;
  345. if (category === "Categories")
  346. presenceData.details = "Viewing the discussion page";
  347. else {
  348. presenceData.details = "Viewing a discussion category";
  349. presenceData.state = category;
  350. }
  351. } else if (currentPath[1] === "p") {
  352. presenceData.details = "Reading a discussion post";
  353. presenceData.state = `${
  354. document.querySelector(".post-info__title").textContent
  355. } | ${sitename}`;
  356. } else if (currentPath[1] === "u") {
  357. presenceData.details = "Viewing a discussion user page";
  358. presenceData.state = `${
  359. document.querySelector(".user-overview__username").textContent
  360. } | ${sitename}`;
  361. }
  362. if (presenceData.state) presenceData.state += ` | ${sitename}`;
  363. else presenceData.state = sitename;
  364. };
  365. }
  366. })();
  367. if (updateCallback.present) {
  368. const defaultData = { ...presenceData };
  369. presence.on("UpdateData", async () => {
  370. resetData(defaultData);
  371. updateCallback.function();
  372. presence.setActivity(presenceData);
  373. });
  374. } else {
  375. presence.on("UpdateData", async () => {
  376. presence.setActivity(presenceData);
  377. });
  378. }
  379. })();
  380. }