misc.ts 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import { fetchAdvanced, randomId } from "@sv443-network/userutils";
  2. import { marked } from "marked";
  3. import { branch, repo } from "../constants";
  4. import { type Domain, type ResourceKey } from "../types";
  5. import { error, type TrLocale, warn } from ".";
  6. import langMapping from "../../assets/locales.json" assert { type: "json" };
  7. //#SECTION misc
  8. /**
  9. * Returns the current domain as a constant string representation
  10. * @throws Throws if script runs on an unexpected website
  11. */
  12. export function getDomain(): Domain {
  13. if(location.hostname.match(/^music\.youtube/))
  14. return "ytm";
  15. else if(location.hostname.match(/youtube\./))
  16. return "yt";
  17. else
  18. throw new Error("BetterYTM is running on an unexpected website. Please don't tamper with the @match directives in the userscript header.");
  19. }
  20. /** Returns a pseudo-random ID unique to each session */
  21. export function getSessionId(): string {
  22. let sesId = window.sessionStorage.getItem("_bytm-session-id");
  23. if(!sesId)
  24. window.sessionStorage.setItem("_bytm-session-id", sesId = randomId(8, 36));
  25. return sesId;
  26. }
  27. //#SECTION resources
  28. /**
  29. * Returns the URL of a resource by its name, as defined in `assets/resources.json`, from GM resource cache - [see GM.getResourceUrl docs](https://wiki.greasespot.net/GM.getResourceUrl)
  30. * Falls back to a `raw.githubusercontent.com` URL or base64-encoded data URI if the resource is not available in the GM resource cache
  31. */
  32. export async function getResourceUrl(name: ResourceKey | "_") {
  33. let url = await GM.getResourceUrl(name);
  34. if(!url || url.length === 0) {
  35. const resource = GM.info.script.resources?.[name].url;
  36. if(typeof resource === "string") {
  37. const resourceUrl = new URL(resource);
  38. const resourcePath = resourceUrl.pathname;
  39. if(resourcePath)
  40. return `https://raw.githubusercontent.com/${repo}/${branch}${resourcePath}`;
  41. }
  42. warn(`Couldn't get blob URL nor external URL for @resource '${name}', trying to use base64-encoded fallback`);
  43. // @ts-ignore
  44. url = await GM.getResourceUrl(name, false);
  45. }
  46. return url;
  47. }
  48. /**
  49. * Returns the preferred locale of the user, provided it is supported by the userscript.
  50. * Prioritizes `navigator.language`, then `navigator.languages`, then `"en_US"` as a fallback.
  51. */
  52. export function getPreferredLocale(): TrLocale {
  53. const navLang = navigator.language.replace(/-/g, "_");
  54. const navLangs = navigator.languages
  55. .filter(lang => lang.match(/^[a-z]{2}(-|_)[A-Z]$/) !== null)
  56. .map(lang => lang.replace(/-/g, "_"));
  57. if(Object.entries(langMapping).find(([key]) => key === navLang))
  58. return navLang as TrLocale;
  59. for(const loc of navLangs) {
  60. if(Object.entries(langMapping).find(([key]) => key === loc))
  61. return loc as TrLocale;
  62. }
  63. // if navigator.languages has entries that aren't locale codes in the format xx_XX
  64. if(navigator.languages.some(lang => lang.match(/^[a-z]{2}$/))) {
  65. for(const lang of navLangs) {
  66. const foundLoc = Object.entries(langMapping).find(([key]) => key.startsWith(lang))?.[0];
  67. if(foundLoc)
  68. return foundLoc as TrLocale;
  69. }
  70. }
  71. return "en_US";
  72. }
  73. /** Returns the content behind the passed resource identifier to be assigned to an element's innerHTML property */
  74. export async function resourceToHTMLString(resource: ResourceKey) {
  75. try {
  76. const resourceUrl = await getResourceUrl(resource);
  77. if(!resourceUrl)
  78. throw new Error(`Couldn't find URL for resource '${resource}'`);
  79. return await (await fetchAdvanced(resourceUrl)).text();
  80. }
  81. catch(err) {
  82. error("Couldn't get SVG element from resource:", err);
  83. return null;
  84. }
  85. }
  86. /** Parses a markdown string and turns it into an HTML string - doesn't sanitize against XSS! */
  87. export function parseMarkdown(md: string) {
  88. return marked.parse(md, {
  89. async: true,
  90. gfm: true,
  91. });
  92. }
  93. /** Returns the content of the changelog markdown file */
  94. export async function getChangelogMd() {
  95. return await (await fetchAdvanced(await getResourceUrl("doc-changelog"))).text();
  96. }