presence.ts 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315
  1. const presence = new Presence({
  2. clientId: "754771926857285782",
  3. });
  4. const enum Assets {
  5. Day = "https://cdn.rcd.gg/PreMiD/websites/T/Town%20of%20Salem/assets/0.png",
  6. Discussion = "https://cdn.rcd.gg/PreMiD/websites/T/Town%20of%20Salem/assets/1.png",
  7. Night = "https://cdn.rcd.gg/PreMiD/websites/T/Town%20of%20Salem/assets/2.png",
  8. Voting = "https://cdn.rcd.gg/PreMiD/websites/T/Town%20of%20Salem/assets/3.png",
  9. Judgement = "https://cdn.rcd.gg/PreMiD/websites/T/Town%20of%20Salem/assets/4.png",
  10. Defense = "https://cdn.rcd.gg/PreMiD/websites/T/Town%20of%20Salem/assets/5.png",
  11. Logo = "https://cdn.rcd.gg/PreMiD/websites/T/Town%20of%20Salem/assets/logo.jpg",
  12. }
  13. enum GameState {
  14. Night = "night",
  15. Day = "day",
  16. End = "end",
  17. AfterGame = "afterGame",
  18. PreGame = "preGame",
  19. }
  20. enum GameType {
  21. Classic = "Classic",
  22. Ranked = "Ranked",
  23. }
  24. interface GameData {
  25. scene: string;
  26. page: string;
  27. day: number;
  28. gameMode: string;
  29. state: GameState;
  30. }
  31. interface Log {
  32. content: string;
  33. id: number;
  34. }
  35. const gameTypeNames: Record<string, string> = {
  36. RankedPractice: "Ranked Practice",
  37. RapidMode: "Custom Rapid Mode",
  38. DraculasPalace: "Dracula's Palace",
  39. ClassicTownTraitor: "Town Traitor",
  40. CovenClassic: "Classic Coven",
  41. CovenRankedPractice: "Coven Ranked Practice",
  42. CovenMafia: "Mafia Returns",
  43. CovenCustom: "Custom Coven",
  44. CovenTownTraitor: "Coven Town Traitor",
  45. CovenAllAny: "Coven All Any",
  46. AllAny: "All Any",
  47. },
  48. oldState: GameData = {
  49. scene: "BigLogin",
  50. page: "",
  51. day: 1,
  52. gameMode: GameType.Classic,
  53. state: GameState.Day,
  54. },
  55. currentState = Object.assign({}, oldState);
  56. let elapsed = Math.round(Date.now() / 1000),
  57. lastId: number = null;
  58. function handleLog(log: string) {
  59. if (
  60. log.startsWith("Switched to ") ||
  61. log.startsWith("Switched additively to")
  62. ) {
  63. const scene = log
  64. .match(/^Switched(?: additively)? to(?: scene)? (.*) Scene/m)[1]
  65. .trim();
  66. currentState.scene = scene;
  67. if (scene === "BigPreGame") currentState.state = GameState.PreGame;
  68. } else if (log.startsWith("Entered HomeSceneController.ShowView()")) {
  69. currentState.page = log
  70. .match(
  71. /^Entered HomeSceneController.ShowView\(\) - View passed in: (.*)$/m
  72. )[1]
  73. .trim();
  74. } else if (log.startsWith("Entered ")) {
  75. switch (log.match(/^Entered (.*)$/m)[1].trim()) {
  76. case "HandleStartRanked": {
  77. currentState.scene = "BigLobby";
  78. currentState.gameMode = GameType.Ranked;
  79. break;
  80. }
  81. case "HandleOnLeaveRankedQueue": {
  82. currentState.scene = "BigHome";
  83. currentState.gameMode = GameType.Classic;
  84. break;
  85. }
  86. }
  87. } else if (log.startsWith("Creating lobby:"))
  88. currentState.gameMode = log.match(/^Creating lobby: (.*?) \|/)[1];
  89. else if (/\[Network\] <color=.*?>\[Received\] <b>/.test(log)) {
  90. const action = log.match(
  91. /\[Network\] <color=.*?>\[Received\] <b>(.*?)<\/b>/
  92. )[1];
  93. switch (action) {
  94. case "PickNames":
  95. case "RoleAndPosition": {
  96. currentState.page = action;
  97. currentState.state = GameState.PreGame;
  98. break;
  99. }
  100. case "StartFirstDay": {
  101. currentState.day = 1;
  102. currentState.state = GameState.Day;
  103. currentState.page = "StartDiscussion";
  104. break;
  105. }
  106. case "StartDay": {
  107. currentState.day++;
  108. currentState.state = GameState.Day;
  109. currentState.page = "";
  110. break;
  111. }
  112. case "StartNight": {
  113. currentState.state = GameState.Night;
  114. currentState.page = "";
  115. break;
  116. }
  117. case "FullMoonNight":
  118. case "StartDiscussion":
  119. case "StartDefense":
  120. case "StartJudgement":
  121. case "StartVoting":
  122. case "WhoDiedAndHow": {
  123. currentState.page = action;
  124. break;
  125. }
  126. case "SomeoneHasWon": {
  127. currentState.state = GameState.End;
  128. break;
  129. }
  130. }
  131. }
  132. }
  133. /**
  134. * Overwrites the default console.log to be able to read the logs.
  135. * Built-in readLogs causes performance problems, as hundreds of logs can be created in half a second.
  136. * It is also hard to determine which logs have not been read yet.
  137. */
  138. const injectedLoggerScript = document.createElement("script");
  139. injectedLoggerScript.type = "text/javascript";
  140. injectedLoggerScript.textContent = `
  141. {
  142. let counter = 0;
  143. console.stdlog = console.log.bind(console);
  144. console.logs = [];
  145. console.log = function() {
  146. const log = arguments[0];
  147. if (/^Switched |^Entered |^Creating |\\[Network\\] <color=.*?>\\[Received\\] <b>/.test(log)) {
  148. console.logs.push({
  149. content: log,
  150. id: counter,
  151. });
  152. counter++;
  153. if (counter > 10000) counter = 0;
  154. }
  155. while (console.logs.length > 100) console.logs.shift();
  156. console.stdlog.apply(console, arguments);
  157. };
  158. }
  159. `;
  160. document.head.appendChild(injectedLoggerScript);
  161. setInterval(async () => {
  162. const logs: Log[] = await presence.getPageletiable('console"]["logs');
  163. let lastUnreadLogIndex = 0;
  164. for (let i = logs.length - 1; i >= 0; i--) {
  165. if (logs[i].id === lastId) {
  166. lastUnreadLogIndex = i + 1;
  167. break;
  168. }
  169. }
  170. for (let i = lastUnreadLogIndex; i < logs.length; i++)
  171. handleLog(logs[i].content);
  172. if (logs.length > 0) lastId = logs[logs.length - 1].id;
  173. }, 1000);
  174. presence.on("UpdateData", () => {
  175. const presenceData: PresenceData = {
  176. largeImageKey: Assets.Logo,
  177. };
  178. if (window.location.pathname !== "/TownOfSalem/") {
  179. presenceData.details = "Browsing BlankMediaGames";
  180. presenceData.state = document.title;
  181. presenceData.startTimestamp = elapsed;
  182. } else {
  183. if (oldState.scene !== currentState.scene)
  184. elapsed = Math.round(Date.now() / 1000);
  185. presenceData.startTimestamp = elapsed;
  186. Object.assign(oldState, currentState);
  187. switch (currentState.scene) {
  188. case "BigLogin": {
  189. presenceData.details = "Logging in";
  190. break;
  191. }
  192. case "BigHome": {
  193. switch (currentState.page) {
  194. case "GameModeSelect": {
  195. presenceData.details = "Selecting Game Mode";
  196. break;
  197. }
  198. case "Customization": {
  199. presenceData.details = "Customizing Character";
  200. break;
  201. }
  202. case "Party": {
  203. presenceData.details = "In a Party";
  204. break;
  205. }
  206. default: {
  207. presenceData.details = "Browsing Main Menu";
  208. presenceData.state = currentState.page;
  209. }
  210. }
  211. break;
  212. }
  213. case "BigLobby": {
  214. presenceData.details = "Waiting in a Lobby";
  215. presenceData.state =
  216. gameTypeNames[currentState.gameMode] ?? currentState.gameMode;
  217. break;
  218. }
  219. case "BigPreGame": {
  220. presenceData.details = "Loading Game";
  221. presenceData.state =
  222. gameTypeNames[currentState.gameMode] ?? currentState.gameMode;
  223. break;
  224. }
  225. case "BigGame": {
  226. presenceData.details = `Playing a ${
  227. gameTypeNames[currentState.gameMode] ?? currentState.gameMode
  228. } Game`;
  229. switch (currentState.state) {
  230. case GameState.PreGame: {
  231. switch (currentState.page) {
  232. case "PickNames": {
  233. presenceData.state = "Choosing Names";
  234. break;
  235. }
  236. case "RoleAndPosition": {
  237. presenceData.state = "Getting a Role";
  238. break;
  239. }
  240. }
  241. break;
  242. }
  243. case GameState.Day: {
  244. presenceData.smallImageKey = Assets.Day;
  245. switch (currentState.page) {
  246. case "StartDiscussion": {
  247. presenceData.state = `Discussion | Day ${currentState.day}`;
  248. presenceData.smallImageKey = Assets.Discussion;
  249. break;
  250. }
  251. case "StartVoting": {
  252. presenceData.state = `Voting | Day ${currentState.day}`;
  253. presenceData.smallImageKey = Assets.Voting;
  254. break;
  255. }
  256. case "WhoDiedAndHow": {
  257. presenceData.state = `Viewing a Death | Day ${currentState.day}`;
  258. break;
  259. }
  260. case "StartDefense": {
  261. presenceData.state = `Defense | Day ${currentState.day}`;
  262. presenceData.smallImageKey = Assets.Defense;
  263. break;
  264. }
  265. case "StartJudgement": {
  266. presenceData.state = `Judgement | Day ${currentState.day}`;
  267. presenceData.smallImageKey = Assets.Judgement;
  268. break;
  269. }
  270. default: {
  271. presenceData.state = `Day ${currentState.day}`;
  272. }
  273. }
  274. break;
  275. }
  276. case GameState.Night: {
  277. presenceData.smallImageKey = Assets.Night;
  278. if (currentState.page === "FullMoonNight")
  279. presenceData.state = `Night ${currentState.day} (Full Moon)`;
  280. else presenceData.state = `Night ${currentState.day}`;
  281. break;
  282. }
  283. case GameState.End: {
  284. presenceData.state = "Viewing End Screen";
  285. break;
  286. }
  287. }
  288. break;
  289. }
  290. case "BigEndGame": {
  291. presenceData.details = `Playing a ${
  292. gameTypeNames[currentState.gameMode] ?? currentState.gameMode
  293. } Game`;
  294. presenceData.state = "Viewing After Game Screen";
  295. break;
  296. }
  297. }
  298. }
  299. if (presenceData.details) presence.setActivity(presenceData);
  300. else presence.setActivity();
  301. });