presence.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435
  1. const presence = new Presence({
  2. clientId: "900002010156400670",
  3. }),
  4. browsingTimestamp = Math.floor(Date.now() / 1000);
  5. const enum Assets {
  6. Donate = "https://cdn.rcd.gg/PreMiD/websites/M/Miraheze/assets/0.png",
  7. Logo = "https://cdn.rcd.gg/PreMiD/websites/M/Miraheze/assets/logo.png",
  8. Phabricator = "https://cdn.rcd.gg/PreMiD/websites/M/Miraheze/assets/1.png",
  9. }
  10. let currentURL = new URL(document.location.href),
  11. currentPath = currentURL.pathname.replace(/^\/|\/$/g, "").split("/"),
  12. presenceData: PresenceData = {
  13. details: "Viewing an unsupported page",
  14. largeImageKey: Assets.Logo,
  15. startTimestamp: browsingTimestamp,
  16. };
  17. const updateCallback = {
  18. _function: null as () => void,
  19. get function(): () => void {
  20. return this._function;
  21. },
  22. set function(parameter) {
  23. this._function = parameter;
  24. },
  25. get present(): boolean {
  26. return this._function !== null;
  27. },
  28. },
  29. /**
  30. * Initialize/reset presenceData.
  31. */
  32. resetData = (
  33. defaultData: PresenceData = {
  34. details: "Viewing an unsupported page",
  35. largeImageKey: Assets.Logo,
  36. startTimestamp: browsingTimestamp,
  37. }
  38. ): void => {
  39. currentURL = new URL(document.location.href);
  40. currentPath = currentURL.pathname.replace(/^\/|\/$/g, "").split("/");
  41. presenceData = { ...defaultData };
  42. },
  43. /**
  44. * Search for URL parameters.
  45. * @param urlParam The parameter that you want to know about the value.
  46. */
  47. getURLParam = (urlParam: string): string => {
  48. return currentURL.searchParams.get(urlParam);
  49. },
  50. prepare = async (): Promise<void> => {
  51. presence.info("Running...");
  52. if (
  53. currentURL.host === "miraheze.org" ||
  54. currentURL.host === "www.miraheze.org"
  55. ) {
  56. /*
  57. Chapter 1
  58. This one is for the front page of Wikia.org.
  59. */
  60. if (currentPath[0] === "") presenceData.details = "On the index page";
  61. } else {
  62. switch (currentURL.hostname) {
  63. case "phabricator.miraheze.org": {
  64. presenceData.smallImageKey = Assets.Phabricator;
  65. presenceData.smallImageText = "Phabricator";
  66. if (currentPath[0] === "") presenceData.details = "On the home page";
  67. else if (
  68. document
  69. .querySelector(
  70. ".phui-crumbs-view.phui-crumbs-border .phui-crumb-name"
  71. )
  72. .textContent.trim() === "Auth"
  73. )
  74. presenceData.details = "Logging in";
  75. else if (/^T\d+$/.test(currentPath[0])) {
  76. presenceData.details = "Viewing a task";
  77. presenceData.state = document.title.replace(/^[^\w\s\d]{1} /, "");
  78. } else {
  79. switch (currentPath[0]) {
  80. case "maniphest": {
  81. if (currentPath[1] === "task" && currentPath[2] === "edit")
  82. presenceData.details = "Creating a task";
  83. else {
  84. presenceData.details = "Viewing tasks (Maniphest)";
  85. presenceData.state = document.querySelector(
  86. "h1 .phui-header-header"
  87. ).textContent;
  88. }
  89. break;
  90. }
  91. case "project": {
  92. if (!currentPath[1] || currentPath[1] === "query") {
  93. presenceData.details = "Viewing projects";
  94. presenceData.state = document.querySelector(
  95. "h1 .phui-header-header"
  96. ).textContent;
  97. } else if (currentPath[1] === "view") {
  98. presenceData.details = "Viewing a project";
  99. presenceData.state = [
  100. ...document.querySelectorAll(
  101. ".phui-crumbs-view.phui-crumbs-border .phui-crumb-name"
  102. ),
  103. ]
  104. .slice(1)
  105. .map(element => element.textContent.trim())
  106. .join(" > ");
  107. } else {
  108. presenceData.details = "Viewing a project";
  109. const crumbs = [
  110. ...document.querySelectorAll(
  111. ".phui-crumbs-view.phui-crumbs-border .phui-crumb-name"
  112. ),
  113. ];
  114. presenceData.state = crumbs
  115. .slice(1, crumbs.length - 1)
  116. .map(element => element.textContent.trim())
  117. .join(" > ");
  118. }
  119. break;
  120. }
  121. case "diffusion": {
  122. presenceData.details = "Viewing repositories (Diffusion)";
  123. presenceData.state = document.querySelector(
  124. "h1 .phui-header-header"
  125. ).textContent;
  126. break;
  127. }
  128. case "source": {
  129. presenceData.details = "Viewing a repository";
  130. presenceData.state = document.querySelector(
  131. ".phui-crumbs-view.phui-crumbs-border .phui-crumb-name:nth-of-type(1)"
  132. ).textContent;
  133. break;
  134. }
  135. case "phame": {
  136. if (!currentPath[1])
  137. presenceData.details = "Viewing recent posts (Phame)";
  138. else if (currentPath[1] === "post") {
  139. if (currentPath[2] === "view") {
  140. presenceData.details = "Viewing a post";
  141. presenceData.state = document.querySelector(
  142. ".phame-header-title"
  143. ).textContent;
  144. } else {
  145. presenceData.details = "Viewing posts";
  146. presenceData.state = document.querySelector(
  147. "h1 .phui-header-header"
  148. ).textContent;
  149. }
  150. } else if (currentPath[1] === "blog") {
  151. if (currentPath[2] === "view") {
  152. presenceData.details = "Viewing a blog";
  153. presenceData.state = document.querySelector(
  154. ".phame-header-title"
  155. ).textContent;
  156. } else {
  157. presenceData.details = "Viewing blogs";
  158. presenceData.state = document.querySelector(
  159. "h1 .phui-header-header"
  160. ).textContent;
  161. }
  162. }
  163. break;
  164. }
  165. default:
  166. if (/^P\d+$/.test(currentPath[0])) {
  167. presenceData.details = "Viewing a paste";
  168. presenceData.state = document.title.replace(
  169. /^[^\w\s\d]{1} /,
  170. ""
  171. );
  172. } else if (currentPath[0] === "paste") {
  173. if (currentPath[1] === "task" && currentPath[2] === "edit")
  174. presenceData.details = "Creating a paste";
  175. else {
  176. presenceData.details = "Viewing pastes";
  177. presenceData.state = document.querySelector(
  178. "h1 .phui-header-header"
  179. ).textContent;
  180. }
  181. } else if (/^M\d+$/.test(currentPath[0])) {
  182. presenceData.details = "Viewing a mock";
  183. presenceData.state = document.title.replace(
  184. /^[^\w\s\d]{1} /,
  185. ""
  186. );
  187. } else if (currentPath[0] === "pholio") {
  188. if (currentPath[1] === "task" && currentPath[2] === "edit")
  189. presenceData.details = "Creating a mock";
  190. else {
  191. presenceData.details = "Viewing mocks (Pholio)";
  192. presenceData.state = document.querySelector(
  193. "h1 .phui-header-header"
  194. ).textContent;
  195. }
  196. } else if (/^U\d+$/.test(currentPath[0])) {
  197. presenceData.details = "Viewing a short URL";
  198. presenceData.state = document.title.replace(
  199. /^[^\w\s\d]{1} /,
  200. ""
  201. );
  202. } else if (currentPath[0] === "phurl") {
  203. if (currentPath[1] === "task" && currentPath[2] === "edit")
  204. presenceData.details = "Creating a short URL";
  205. else {
  206. presenceData.details = "Viewing short URLs (Phurl)";
  207. presenceData.state = document.querySelector(
  208. "h1 .phui-header-header"
  209. ).textContent;
  210. }
  211. } else {
  212. presenceData.details = `Viewing ${document
  213. .querySelector(
  214. ".phui-crumbs-view.phui-crumbs-border .phui-crumb-name"
  215. )
  216. .textContent.trim()}`;
  217. presenceData.state = [
  218. ...document.querySelectorAll(
  219. ".phui-crumbs-view.phui-crumbs-border .phui-crumb-name"
  220. ),
  221. ]
  222. .slice(1)
  223. .map(element => element.textContent.trim())
  224. .join(" > ");
  225. }
  226. }
  227. }
  228. break;
  229. }
  230. case "blog.miraheze.org": {
  231. presenceData.smallImageKey = Assets.Phabricator;
  232. presenceData.smallImageText = "Phabricator (blog)";
  233. if (!currentPath[1]) {
  234. presenceData.details = "Viewing a blog";
  235. presenceData.state = document.querySelector(
  236. ".phame-header-title"
  237. ).textContent;
  238. } else if (currentPath[0] === "post") {
  239. presenceData.details = "Viewing a post";
  240. presenceData.state = document.querySelector(
  241. ".phame-header-title"
  242. ).textContent;
  243. }
  244. break;
  245. }
  246. case "donate.miraheze.org": {
  247. presenceData.smallImageKey = Assets.Donate;
  248. presenceData.smallImageText = "Donation Gateway";
  249. presenceData.details = "Donating to Miraheze";
  250. break;
  251. }
  252. case "status.miraheze.wiki": {
  253. presenceData.details = "Viewing the status page";
  254. if (document.title !== "Status Page - Miraheze") {
  255. presenceData.state = document.title.replace(
  256. " - Status Page - Miraheze",
  257. ""
  258. );
  259. }
  260. break;
  261. }
  262. default: {
  263. /*
  264. Chapter 2
  265. This one is for the wiki part on the Wikia.org.
  266. */
  267. const mwConfig = await presence.getPageletiable<{
  268. wgSiteName: string;
  269. wgAction: string;
  270. wgPageName: string;
  271. wgCanonicalNamespace: string;
  272. wgNamespaceNumber: number;
  273. wgIsMainPage: boolean;
  274. }>('mw"]["config"]["values'),
  275. siteName = mwConfig.wgSiteName,
  276. actionResult = (): string =>
  277. getURLParam("action") ||
  278. getURLParam("veaction") ||
  279. mwConfig.wgAction,
  280. titleFromURL = (): string => {
  281. return decodeURIComponent(
  282. mwConfig.wgPageName.replaceAll("_", " ")
  283. );
  284. },
  285. title = document.querySelector("h1")
  286. ? document.querySelector("h1").textContent
  287. : titleFromURL(),
  288. /**
  289. * Returns details based on the namespace.
  290. * @link https://en.wikipedia.org/wiki/Wikipedia:Namespace
  291. */
  292. namespaceDetails = (): string => {
  293. // Hardcoded IDs are used for namespaces that are usually consistent.
  294. // For others, use the canonical names as the keys. They will always be English.
  295. const details: { [index: string]: string } = {
  296. "-2": "Viewing a media",
  297. "-1": "Viewing a special page",
  298. 0: "Reading an article",
  299. 1: "Viewing a talk page",
  300. 2: "Viewing a user page",
  301. 3: "Viewing a user talk page",
  302. 4: "Viewing a project page",
  303. 5: "Viewing a project talk page",
  304. 6: "Viewing a file",
  305. 7: "Viewing a file talk page",
  306. 8: "Viewing an interface page",
  307. 9: "Viewing an interface talk page",
  308. 10: "Viewing a template",
  309. 11: "Viewing a template talk page",
  310. 12: "Viewing a help page",
  311. 13: "Viewing a help talk page",
  312. 14: "Viewing a category",
  313. 15: "Viewing a category talk page",
  314. 828: "Viewing a module",
  315. 829: "Viewing a module talk page",
  316. 2300: "Viewing a gadget",
  317. 2301: "Viewing a gadget talk page",
  318. 2302: "Viewing a gadget definition page",
  319. 2303: "Viewing a gadget definition talk page",
  320. GeoJson: "Viewing a GeoJson page",
  321. "GeoJson talk": "Viewing a GeoJson talk page",
  322. TimedText: "Viewing a media's subtitles",
  323. "TimedText talk": "Viewing a media's subtitles talk page",
  324. CNBanners: "Viewing a CNBanners page",
  325. "CNBanners talk": "Viewing a CNBanners talk page",
  326. Schema: "Viewing a schema page",
  327. "Schema talk": "Viewing a schema talk page",
  328. Translations: "Viewing a translations page",
  329. "Translations talk": "Viewing a translations talk page",
  330. },
  331. canonicalNamespace = mwConfig.wgCanonicalNamespace.replaceAll(
  332. "_",
  333. " "
  334. );
  335. return (
  336. details[mwConfig.wgNamespaceNumber] ||
  337. details[canonicalNamespace] ||
  338. `Viewing a/an ${canonicalNamespace} page`
  339. );
  340. };
  341. if (mwConfig.wgIsMainPage && actionResult() === "view")
  342. presenceData.details = "On the main page";
  343. else if (document.querySelector("#wpLoginAttempt"))
  344. presenceData.details = "Logging in";
  345. else if (document.querySelector("#wpCreateaccount"))
  346. presenceData.details = "Creating an account";
  347. else if (document.querySelector(".searchresults")) {
  348. presenceData.details = "Searching for a page";
  349. presenceData.state = (
  350. document.querySelector("input[type=search]") as HTMLInputElement
  351. ).value;
  352. } else if (actionResult() === "history") {
  353. presenceData.details = "Viewing revision history";
  354. presenceData.state = titleFromURL();
  355. } else if (getURLParam("diff")) {
  356. presenceData.details = "Viewing difference between revisions";
  357. presenceData.state = titleFromURL();
  358. } else if (getURLParam("oldid")) {
  359. presenceData.details = "Viewing an old revision of a page";
  360. presenceData.state = titleFromURL();
  361. } else if (
  362. document.querySelector("#ca-ve-edit") ||
  363. getURLParam("veaction")
  364. ) {
  365. presenceData.state = `${
  366. title.toLowerCase() === titleFromURL().toLowerCase()
  367. ? `${title}`
  368. : `${title} (${titleFromURL()})`
  369. }`;
  370. updateCallback.function = (): void => {
  371. if (actionResult() === "edit" || actionResult() === "editsource")
  372. presenceData.details = "Editing a page";
  373. else presenceData.details = namespaceDetails();
  374. };
  375. } else if (actionResult() === "edit") {
  376. presenceData.details = document.querySelector("#ca-edit")
  377. ? "Editing a page"
  378. : "Viewing source";
  379. presenceData.state = titleFromURL();
  380. } else {
  381. presenceData.details = namespaceDetails();
  382. presenceData.state = `${
  383. title.toLowerCase() === titleFromURL().toLowerCase()
  384. ? `${title}`
  385. : `${title} (${titleFromURL()})`
  386. }`;
  387. }
  388. if (presenceData.state) presenceData.state += ` | ${siteName}`;
  389. else presenceData.state = siteName;
  390. }
  391. }
  392. }
  393. };
  394. (async (): Promise<void> => {
  395. await prepare();
  396. if (updateCallback.present) {
  397. const defaultData = { ...presenceData };
  398. presence.on("UpdateData", async () => {
  399. resetData(defaultData);
  400. updateCallback.function();
  401. presence.setActivity(presenceData);
  402. });
  403. } else {
  404. presence.on("UpdateData", async () => {
  405. presence.setActivity(presenceData);
  406. });
  407. }
  408. })();