presence.ts 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. const presence = new Presence({
  2. clientId: "844106861711196179",
  3. }),
  4. strings = presence.getStrings({
  5. play: "general.playing",
  6. pause: "general.paused",
  7. live: "general.live",
  8. }),
  9. browsingTimestamp = Math.floor(Date.now() / 1000),
  10. containsTerm = (term: string) => document.location.pathname.includes(term);
  11. enum myCANALAssets {
  12. Logo = "https://cdn.rcd.gg/PreMiD/websites/M/myCANAL/assets/0.png",
  13. }
  14. // Resize fonction made by pierrequiroul https://github.com/PreMiD/Presences/pull/8910
  15. export const cropPreset = {
  16. // Crop values in percent correspond to Left, Right, Top, Bottom.
  17. squared: [0, 0, 0, 0],
  18. vertical: [0.22, 0.22, 0, 0.3],
  19. horizontal: [0.425, 0.025, 0, 0],
  20. };
  21. export async function getThumbnail(
  22. src: string = myCANALAssets.Logo,
  23. cropPercentages: typeof cropPreset.squared = cropPreset.squared,
  24. progress = 2,
  25. borderWidth = 15
  26. ): Promise<string> {
  27. return new Promise(resolve => {
  28. const img = new Image(),
  29. wh = 320; // Size of the square thumbnail
  30. img.crossOrigin = "anonymous";
  31. img.src = src;
  32. img.onload = function () {
  33. let croppedWidth,
  34. croppedHeight,
  35. cropX = 0,
  36. cropY = 0;
  37. // Determine if the image is landscape or portrait
  38. const isLandscape = img.width > img.height;
  39. if (isLandscape) {
  40. // Landscape mode: use left and right crop percentages
  41. const cropLeft = img.width * cropPercentages[0];
  42. croppedWidth = img.width - cropLeft - img.width * cropPercentages[1];
  43. croppedHeight = img.height;
  44. cropX = cropLeft;
  45. } else {
  46. // Portrait mode: use top and bottom crop percentages
  47. const cropTop = img.height * cropPercentages[2];
  48. croppedWidth = img.width;
  49. croppedHeight = img.height - cropTop - img.height * cropPercentages[3];
  50. cropY = cropTop;
  51. }
  52. // Determine the scale to fit the cropped image into the square canvas
  53. let newWidth, newHeight, offsetX, offsetY;
  54. if (isLandscape) {
  55. newWidth = wh - 2 * borderWidth;
  56. newHeight = (newWidth / croppedWidth) * croppedHeight;
  57. offsetX = borderWidth;
  58. offsetY = (wh - newHeight) / 2;
  59. } else {
  60. newHeight = wh - 2 * borderWidth;
  61. newWidth = (newHeight / croppedHeight) * croppedWidth;
  62. offsetX = (wh - newWidth) / 2;
  63. offsetY = borderWidth;
  64. }
  65. const tempCanvas = document.createElement("canvas");
  66. tempCanvas.width = wh;
  67. tempCanvas.height = wh;
  68. const ctx = tempCanvas.getContext("2d"),
  69. // Remap progress from 0-1 to 0.03-0.97 (smallImageKey borders)
  70. remappedProgress = 0.07 + progress * (0.93 - 0.07);
  71. // 1. Fill the canvas with a black background
  72. ctx.fillStyle = "#172e4e";
  73. ctx.fillRect(0, 0, wh, wh);
  74. // 2. Draw the radial progress bar
  75. if (remappedProgress > 0) {
  76. ctx.beginPath();
  77. ctx.moveTo(wh / 2, wh / 2);
  78. const startAngle = Math.PI / 4; // 45 degrees in radians, starting from bottom-right
  79. ctx.arc(
  80. wh / 2,
  81. wh / 2,
  82. wh,
  83. startAngle,
  84. startAngle + 2 * Math.PI * remappedProgress
  85. );
  86. ctx.lineTo(wh / 2, wh / 2);
  87. // Create a triangular gradient
  88. const gradient = ctx.createLinearGradient(0, 0, wh, wh);
  89. gradient.addColorStop(0, "rgba(245, 3, 26, 1)");
  90. gradient.addColorStop(0.5, "rgba(63, 187, 244, 1)");
  91. gradient.addColorStop(1, "rgba(164, 215, 12, 1)");
  92. ctx.fillStyle = gradient;
  93. ctx.fill();
  94. }
  95. // 3. Draw the cropped image centered and zoomed out based on the borderWidth
  96. ctx.drawImage(
  97. img,
  98. cropX,
  99. cropY,
  100. croppedWidth,
  101. croppedHeight,
  102. offsetX,
  103. offsetY,
  104. newWidth,
  105. newHeight
  106. );
  107. resolve(tempCanvas.toDataURL("image/png"));
  108. };
  109. img.onerror = function () {
  110. resolve(src);
  111. };
  112. });
  113. }
  114. presence.on("UpdateData", async () => {
  115. const presenceData: PresenceData = {
  116. largeImageKey: myCANALAssets.Logo,
  117. },
  118. video = document.querySelector<HTMLVideoElement>(".iIZX3IGkM2eBzzWle1QQ"),
  119. showCover = await presence.getSetting<boolean>("cover"),
  120. mainTitle = document.querySelector(".bodyTitle___HwRP2");
  121. switch (document.location.pathname) {
  122. case "/mes-videos/":
  123. presenceData.state = "Mes Vidéos";
  124. break;
  125. case "/chaines/":
  126. presenceData.state = "Chaînes";
  127. break;
  128. case "/programme-tv/":
  129. presenceData.state = "Programme TV";
  130. break;
  131. case "/cinema/":
  132. presenceData.state = "Films";
  133. break;
  134. case "/series/":
  135. presenceData.state = "Séries";
  136. break;
  137. case "/jeunesse/":
  138. presenceData.state = "Jeunesse";
  139. break;
  140. case "/live/":
  141. presenceData.state = "Chaînes en direct";
  142. break;
  143. }
  144. if (video && !isNaN(video.duration)) {
  145. const titleTvShows = document.querySelectorAll(".MGrm26svmXpUhj6dfbGN");
  146. let channelID = new URLSearchParams(window.location.search).get("channel");
  147. switch (true) {
  148. case containsTerm("live"):
  149. channelID = `${channelID.charAt(0)} ${channelID.substring(1)}`;
  150. presenceData.details = document.querySelector(
  151. ".A6AH2oNkXUuOKJN5IYrL"
  152. ).textContent;
  153. presenceData.state = `sur ${
  154. document.querySelector<HTMLImageElement>(
  155. `#\\3${channelID}_onclick > div > div.card__content_0dae1b.cardContent___DuNAN.ratio--169 > div[class*="cardLogoChannel"] > div > img`
  156. )?.alt
  157. }`;
  158. [presenceData.startTimestamp, presenceData.endTimestamp] =
  159. presence.getTimestamps(video.currentTime, video.duration);
  160. presenceData.largeImageKey = showCover
  161. ? document.querySelector<HTMLImageElement>(
  162. `#\\3${channelID}_onclick > div > div.card__content_0dae1b.cardContent___DuNAN.ratio--169 > div[class*="cardLogoChannel"] > div > img`
  163. ).src
  164. : myCANALAssets.Logo;
  165. presenceData.smallImageKey = Assets.Live;
  166. presenceData.smallImageText = "En direct";
  167. delete presenceData.startTimestamp;
  168. delete presenceData.endTimestamp;
  169. presenceData.startTimestamp = browsingTimestamp;
  170. break;
  171. case containsTerm("cinema"):
  172. presenceData.details = document.querySelector(
  173. ".A6AH2oNkXUuOKJN5IYrL"
  174. ).textContent;
  175. [presenceData.startTimestamp, presenceData.endTimestamp] =
  176. presence.getTimestamps(video.currentTime, video.duration);
  177. presenceData.largeImageKey = showCover
  178. ? (presenceData.largeImageKey = await getThumbnail(
  179. document.querySelector<HTMLMetaElement>("[property='og:image']")
  180. ?.content
  181. ))
  182. : myCANALAssets.Logo;
  183. presenceData.smallImageKey = video.paused ? Assets.Pause : Assets.Play;
  184. presenceData.smallImageText = video.paused
  185. ? (await strings).pause
  186. : (await strings).play;
  187. break;
  188. case containsTerm("series"):
  189. case containsTerm("jeunesse"):
  190. presenceData.details = titleTvShows[0].textContent.trim();
  191. presenceData.state = titleTvShows[1].textContent.trim();
  192. [presenceData.startTimestamp, presenceData.endTimestamp] =
  193. presence.getTimestamps(video.currentTime, video.duration);
  194. presenceData.largeImageKey = showCover
  195. ? (presenceData.largeImageKey = await getThumbnail(
  196. document.querySelector<HTMLMetaElement>("[property='og:image']")
  197. ?.content
  198. ))
  199. : myCANALAssets.Logo;
  200. presenceData.smallImageKey = video.paused ? Assets.Pause : Assets.Play;
  201. presenceData.smallImageText = video.paused
  202. ? (await strings).pause
  203. : (await strings).play;
  204. break;
  205. }
  206. if (video.paused) {
  207. delete presenceData.startTimestamp;
  208. delete presenceData.endTimestamp;
  209. }
  210. } else if (mainTitle) {
  211. presenceData.details = "Regarde...";
  212. presenceData.state = mainTitle.textContent;
  213. } else presenceData.details = "Navigue...";
  214. presence.setActivity(presenceData);
  215. });