presence.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. const enum Assets {
  2. Header = "https://cdn.rcd.gg/PreMiD/websites/N/NationStates/assets/0.png",
  3. Logo = "https://cdn.rcd.gg/PreMiD/websites/N/NationStates/assets/logo.png",
  4. Envelope = "https://cdn.rcd.gg/PreMiD/websites/N/NationStates/assets/1.png",
  5. Flag = "https://cdn.rcd.gg/PreMiD/websites/N/NationStates/assets/2.png",
  6. Forum = "https://cdn.rcd.gg/PreMiD/websites/N/NationStates/assets/3.png",
  7. Gift = "https://cdn.rcd.gg/PreMiD/websites/N/NationStates/assets/4.png",
  8. Globe = "https://cdn.rcd.gg/PreMiD/websites/N/NationStates/assets/5.png",
  9. Page = "https://cdn.rcd.gg/PreMiD/websites/N/NationStates/assets/6.png",
  10. Person = "https://cdn.rcd.gg/PreMiD/websites/N/NationStates/assets/7.png",
  11. WorldAssembly = "https://cdn.rcd.gg/PreMiD/websites/N/NationStates/assets/8.png",
  12. Target = "https://cdn.rcd.gg/PreMiD/websites/N/NationStates/assets/9.png",
  13. }
  14. const browsingTimestamp = Math.floor(Date.now() / 1000),
  15. presence = new Presence({
  16. clientId: "1006869424441131109",
  17. }),
  18. presenceData: PresenceData = {
  19. largeImageKey: Assets.Logo,
  20. startTimestamp: browsingTimestamp,
  21. };
  22. async function fetchSelfNationName(): Promise<string | null> {
  23. if (document.body.id !== "loggedin") return null;
  24. const username: string = document.body.getAttribute("data-nname");
  25. if (!username) return null;
  26. const nationdata = await fetch(
  27. `https://www.nationstates.net/cgi-bin/api.cgi?nation=${username}&q=name+type&v=2`
  28. );
  29. if (!nationdata.ok) return null;
  30. const nationparsed = new DOMParser().parseFromString(
  31. await nationdata.text(),
  32. "text/html"
  33. ),
  34. nationname = nationparsed.querySelector("NAME").textContent,
  35. nationtype = `${nationparsed.querySelector("TYPE").textContent} of`,
  36. namesetting: number = await presence.getSetting("displayname");
  37. if (nationname.length + nationtype.length + 2 <= 128 && namesetting > 1)
  38. return `${nationname}, ${nationtype}`;
  39. else if (nationname.length <= 128 && namesetting > 0) return nationname;
  40. else if (namesetting > 0) return `${nationname.substring(0, 128 - 1)}…`;
  41. else return "";
  42. }
  43. async function fetchNationName(id: string): Promise<string | null> {
  44. const nationdata = await fetch(
  45. `https://www.nationstates.net/cgi-bin/api.cgi?nation=${id}&q=name&v=2`
  46. );
  47. if (!nationdata.ok) return null;
  48. const nationname = new DOMParser()
  49. .parseFromString(await nationdata.text(), "text/html")
  50. .querySelector("NAME").textContent;
  51. if (nationname.length + 16 <= 128) return nationname;
  52. else return `${nationname.substring(0, 128 - 17)}…`;
  53. }
  54. async function fetchRegionName(id: string): Promise<string | null> {
  55. const regiondata = await fetch(
  56. `https://www.nationstates.net/cgi-bin/api.cgi?region=${id}&q=name&v=2`
  57. );
  58. if (!regiondata.ok) return null;
  59. const regionname = new DOMParser()
  60. .parseFromString(await regiondata.text(), "text/html")
  61. .querySelector("NAME").textContent;
  62. if (regionname.length + 16 <= 128) return regionname;
  63. else return `${regionname.substring(0, 128 - 17)}…`;
  64. }
  65. async function updatePresenceData(): Promise<void> {
  66. if (document.location.hostname === "www.nationstates.net") {
  67. const isLoggedIn: boolean = document.body.id === "loggedin",
  68. username: string = document.body.getAttribute("data-nname"),
  69. nationname: string = await fetchSelfNationName();
  70. if (nationname && nationname.length > 0) presenceData.details = nationname;
  71. else delete presenceData.details;
  72. if (isLoggedIn && (await presence.getSetting("buttons"))) {
  73. presenceData.buttons = [
  74. {
  75. label: "View Nation",
  76. url: `https://www.nationstates.net/nation=${username}`,
  77. },
  78. ];
  79. } else delete presenceData.buttons;
  80. const path = window.location.pathname.toLowerCase(),
  81. page = path
  82. .substring(path.indexOf("=") + 1)
  83. .replace(/\/.+/iu, "")
  84. .replace(/\?.+/iu, "")
  85. .toLowerCase();
  86. switch (
  87. path.substring(path.indexOf("/") + 1, path.indexOf("=")).toLowerCase()
  88. ) {
  89. case "nation": {
  90. if (isLoggedIn && page === username) {
  91. presenceData.state = "Viewing Nation";
  92. presenceData.smallImageKey = Assets.Flag;
  93. presenceData.smallImageText = "My Nation";
  94. } else {
  95. const nationname: string = await fetchNationName(page);
  96. presenceData.state = nationname
  97. ? `Viewing Nation: ${nationname}`
  98. : "Viewing a Nation";
  99. presenceData.smallImageKey = Assets.Globe;
  100. presenceData.smallImageText = "World";
  101. }
  102. return;
  103. }
  104. case "region": {
  105. const regionname = await fetchRegionName(page);
  106. presenceData.state = regionname
  107. ? `Viewing Region: ${regionname}`
  108. : "Viewing a Region";
  109. presenceData.smallImageKey = Assets.Globe;
  110. presenceData.smallImageText = "World";
  111. return;
  112. }
  113. case "page":
  114. break;
  115. default:
  116. presenceData.state = "Browsing";
  117. delete presenceData.smallImageKey;
  118. return;
  119. }
  120. switch (page) {
  121. case "create_nation":
  122. presenceData.state = "Declaring a New Nation";
  123. presenceData.smallImageKey = Assets.Flag;
  124. presenceData.smallImageText = "My Nation";
  125. break;
  126. case "display_region_rmb":
  127. case "region_control":
  128. case "region_history":
  129. case "region_rank":
  130. presenceData.state = "Attending Regional Activities";
  131. presenceData.smallImageKey = Assets.Flag;
  132. presenceData.smallImageText = "Region";
  133. break;
  134. case "telegram":
  135. case "telegrams":
  136. case "tg":
  137. case "notices":
  138. case "notice":
  139. presenceData.state = "Reading Telegrams";
  140. presenceData.smallImageKey = Assets.Envelope;
  141. presenceData.smallImageText = "Telegrams";
  142. break;
  143. case "write_telegram":
  144. case "compose_telegram":
  145. presenceData.state = "Writing a Telegram";
  146. presenceData.smallImageKey = Assets.Envelope;
  147. presenceData.smallImageText = "Telegrams";
  148. break;
  149. case "world":
  150. case "dossier":
  151. case "change_region":
  152. case "list_entities":
  153. case "list_nations":
  154. case "list_regions":
  155. case "tag_search":
  156. case "activity":
  157. presenceData.state = "Observing the World";
  158. presenceData.smallImageKey = Assets.Globe;
  159. presenceData.smallImageText = "World";
  160. break;
  161. case "deck":
  162. presenceData.state = "Playing Cards";
  163. delete presenceData.smallImageKey;
  164. break;
  165. case "store":
  166. case "cart":
  167. case "order":
  168. presenceData.state = "Browsing the Store";
  169. presenceData.smallImageKey = Assets.Gift;
  170. presenceData.smallImageText = "Store";
  171. break;
  172. case "challenge": {
  173. const opponent = [
  174. ...document
  175. .querySelector(".trumps-challenger")
  176. .querySelectorAll(".nname"),
  177. ].find(
  178. challanger =>
  179. challanger.textContent.toLowerCase().split(" ").join("_") !==
  180. username
  181. );
  182. if (opponent && opponent.textContent)
  183. presenceData.state = `Challenging ${opponent.textContent}`;
  184. else presenceData.state = "Challenging";
  185. presenceData.smallImageKey = Assets.Target;
  186. presenceData.smallImageText = "Challenge";
  187. break;
  188. }
  189. case "settings":
  190. case "banners":
  191. case "upload_flag":
  192. case "tgsettings":
  193. case "subscriptions":
  194. presenceData.state = "Configuring NationStates";
  195. delete presenceData.smallImageKey;
  196. break;
  197. case "dispatches":
  198. presenceData.state = "Browsing Dispatches";
  199. presenceData.smallImageKey = Assets.Page;
  200. presenceData.smallImageText = "Dispatches";
  201. break;
  202. case "dispatch": {
  203. const dispatchname = document
  204. .querySelector("#content")
  205. ?.querySelector(".dispatch")
  206. ?.querySelector("h2")?.textContent;
  207. if (dispatchname) {
  208. presenceData.state = `Dispatch: ${
  209. dispatchname.length > 128 - 10
  210. ? `${dispatchname.substring(0, 128 - 11)}…'`
  211. : dispatchname
  212. }`;
  213. } else presenceData.state = "Browsing Dispatches";
  214. presenceData.smallImageKey = Assets.Page;
  215. presenceData.smallImageText = "Dispatches";
  216. break;
  217. }
  218. case "issues":
  219. case "dilemmas":
  220. presenceData.state = "Browsing Issues";
  221. presenceData.smallImageKey = Assets.Person;
  222. presenceData.smallImageText = "Issues";
  223. break;
  224. case "show_dilemma": {
  225. const issuename = document
  226. .querySelector("#content")
  227. ?.querySelector(".dpaper4")?.textContent;
  228. if (issuename) {
  229. presenceData.state = `Issue: ${
  230. issuename.length > 128 - 7
  231. ? `${issuename.substring(0, 128 - 7)}…'`
  232. : issuename
  233. }`;
  234. } else presenceData.state = "Browsing Issues";
  235. presenceData.smallImageKey = Assets.Person;
  236. presenceData.smallImageText = "Issues";
  237. break;
  238. }
  239. case "enact_dilemma": {
  240. const issuename = document
  241. .querySelector("#dlegislationtext")
  242. ?.querySelector("p.dtitle")?.textContent;
  243. if (issuename) {
  244. presenceData.state = `Issue: ${
  245. issuename.length > 128 - 7
  246. ? `${issuename.substring(0, 128 - 7)}…'`
  247. : issuename
  248. }`;
  249. } else presenceData.state = "Browsing Issues";
  250. presenceData.smallImageKey = Assets.Person;
  251. presenceData.smallImageText = "Issues";
  252. break;
  253. }
  254. case "wa":
  255. case "un":
  256. case "list_un":
  257. case "list_wa":
  258. case "un_proposal":
  259. case "wa_proposal":
  260. presenceData.state = "Attending the World Assembly";
  261. presenceData.smallImageKey = Assets.WorldAssembly;
  262. presenceData.smallImageText = "World Assembly";
  263. break;
  264. case "un_repeal":
  265. case "wa_repeal":
  266. case "un_new_proposal":
  267. case "wa_new_proposal":
  268. presenceData.state = "Writing a new WA proposal";
  269. presenceData.smallImageKey = Assets.WorldAssembly;
  270. presenceData.smallImageText = "World Assembly";
  271. break;
  272. case "ga":
  273. case "sc":
  274. case "wa_past_resolution":
  275. case "wa_past_resolutions":
  276. case "un_past_resolution":
  277. case "un_past_resolutions": {
  278. const resolution = document
  279. .querySelector(".WA_thing")
  280. ?.querySelector("h2")?.textContent;
  281. if (resolution && document.querySelectorAll(".WA_thing").length === 1) {
  282. presenceData.state = `Resolution: ${
  283. resolution.length > 128 - 7
  284. ? `${resolution.substring(0, 128 - 7)}…'`
  285. : resolution
  286. }`;
  287. } else presenceData.state = "Attending the World Assembly";
  288. presenceData.smallImageKey = Assets.WorldAssembly;
  289. presenceData.smallImageText = "World Assembly";
  290. break;
  291. }
  292. default:
  293. presenceData.state = "Browsing";
  294. delete presenceData.smallImageKey;
  295. break;
  296. }
  297. } else if (document.location.hostname === "forum.nationstates.net") {
  298. presenceData.details = "Browsing the Forums";
  299. presenceData.smallImageKey = Assets.Forum;
  300. presenceData.smallImageText = "Forums";
  301. delete presenceData.buttons;
  302. const { title } = document;
  303. if (title.startsWith("NationStates • View")) {
  304. const topicsearch = title.match(/(?<=nationstates\s•\sview\s).+/gi);
  305. if (topicsearch) {
  306. let topic = topicsearch[0];
  307. topic = topic.charAt(0).toUpperCase() + topic.slice(1);
  308. if (topic.length > 128) topic = `${topic.substring(0, 128)}…`;
  309. presenceData.state = topic;
  310. } else delete presenceData.state;
  311. } else delete presenceData.state;
  312. } else {
  313. delete presenceData.state;
  314. delete presenceData.details;
  315. }
  316. }
  317. setInterval(updatePresenceData, 10000);
  318. presence.on("UpdateData", async () => {
  319. if (presenceData.state || presenceData.details)
  320. presence.setActivity(presenceData);
  321. });