xhr.ts 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. import { fetchAdvanced, type Stringifiable } from "@sv443-network/userutils";
  2. import type { ResourceKey } from "../types.js";
  3. import { getResourceUrl } from "./misc.js";
  4. import { error } from "./logging.js";
  5. /**
  6. * Constructs a URL from a base URL and a record of query parameters.
  7. * If a value is null, the parameter will be valueless. If a value is undefined, the parameter will be omitted.
  8. * All values will be stringified using their `toString()` method and then URI-encoded.
  9. * @returns Returns a string instead of a URL object
  10. */
  11. export function constructUrlString(baseUrl: string, params: Record<string, Stringifiable | null>) {
  12. return `${baseUrl}?${
  13. Object.entries(params)
  14. .filter(([, v]) => v !== undefined)
  15. .map(([key, val]) => `${key}${val === null ? "" : `=${encodeURIComponent(String(val))}`}`)
  16. .join("&")
  17. }`;
  18. }
  19. /**
  20. * Constructs a URL object from a base URL and a record of query parameters.
  21. * If a value is null, the parameter will be valueless. If a value is undefined, the parameter will be omitted.
  22. * All values will be URI-encoded.
  23. * @returns Returns a URL object instead of a string
  24. */
  25. export function constructUrl(base: string, params: Record<string, Stringifiable | null>) {
  26. return new URL(constructUrlString(base, params));
  27. }
  28. /**
  29. * Sends a request with the specified parameters and returns the response as a Promise.
  30. * Ignores [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS), contrary to fetch and fetchAdvanced.
  31. */
  32. export function sendRequest<T = any>(details: GM.Request<T>) {
  33. return new Promise<GM.Response<T>>((resolve, reject) => {
  34. GM.xmlHttpRequest({
  35. timeout: 10_000,
  36. ...details,
  37. onload: resolve,
  38. onerror: reject,
  39. ontimeout: reject,
  40. onabort: reject,
  41. });
  42. });
  43. }
  44. /** Fetches a CSS file from the specified resource with a key starting with `css-` */
  45. export async function fetchCss(key: ResourceKey & `css-${string}`) {
  46. try {
  47. const css = await (await fetchAdvanced(await getResourceUrl(key))).text();
  48. return css ?? undefined;
  49. }
  50. catch(err) {
  51. error("Couldn't fetch CSS due to an error:", err);
  52. return undefined;
  53. }
  54. }
  55. export type ReturnYoutubeDislikesVotesObj = {
  56. /** The watch ID of the video */
  57. id: string;
  58. /** ISO timestamp of when the video was uploaded */
  59. dateCreated: string;
  60. /** Amount of likes */
  61. likes: number;
  62. /** Amount of dislikes */
  63. dislikes: number;
  64. /** Like to dislike ratio from 0.0 to 5.0 */
  65. rating: number;
  66. /** Amount of views */
  67. viewCount: number;
  68. /** Whether the video was deleted */
  69. deleted: boolean;
  70. };
  71. /**
  72. * Fetches the votes object for a YouTube video from the [Return YouTube Dislikes API.](https://returnyoutubedislike.com/docs)
  73. * @param watchId The watch ID of the video
  74. */
  75. export async function fetchVideoVotes(watchId: string) {
  76. return (await sendRequest<ReturnYoutubeDislikesVotesObj>({
  77. method: "GET",
  78. url: `https://returnyoutubedislikeapi.com/votes?videoId=${watchId}`,
  79. })).response as ReturnYoutubeDislikesVotesObj;
  80. }