msterzhang / onelist

一个类似emby的专注于刮削alist聚合网盘形成影视媒体库的程序。
1.4k stars 119 forks source link

功能建议 #52

Open bestruirui opened 1 year ago

bestruirui commented 1 year ago

增加一个自定义tmdbapi地址 可以使用clodflare反代tmdb api 下面这个代码可以反代api.themoviedb.org

// node_modules/reflare/dist/src/database/workers-kv.js
var WorkersKV = class {
  namespace;
  constructor(namespace) {
    this.namespace = namespace;
  }
  get = async (key) => {
    const value = await this.namespace.get(key, {
      type: "json",
      cacheTtl: 60
    });
    return value;
  };
  put = async (key, value) => {
    await this.namespace.put(key, JSON.stringify(value));
  };
  delete = async (key) => {
    await this.namespace.delete(key);
  };
};

// node_modules/reflare/dist/src/middleware.js
var usePipeline = (...initMiddlewares) => {
  const stack = [...initMiddlewares];
  const push = (...middlewares) => {
    stack.push(...middlewares);
  };
  const execute = async (context) => {
    const runner = async (prevIndex, index) => {
      if (index === prevIndex) {
        throw new Error("next() called multiple times");
      }
      if (index >= stack.length) {
        return;
      }
      const middleware = stack[index];
      const next = async () => runner(index, index + 1);
      await middleware(context, next);
    };
    await runner(-1, 0);
  };
  return {
    push,
    execute
  };
};

// node_modules/reflare/dist/src/middlewares/cors.js
var useCORS = async (context, next) => {
  await next();
  const { request, response, route } = context;
  const corsOptions = route.cors;
  if (corsOptions === void 0) {
    return;
  }
  const { origin, methods, exposedHeaders, allowedHeaders, credentials, maxAge } = corsOptions;
  const requestOrigin = request.headers.get("origin");
  if (requestOrigin === null || origin === false) {
    return;
  }
  const corsHeaders = new Headers(response.headers);
  if (origin === true) {
    corsHeaders.set("Access-Control-Allow-Origin", requestOrigin);
  } else if (Array.isArray(origin)) {
    if (origin.includes(requestOrigin)) {
      corsHeaders.set("Access-Control-Allow-Origin", requestOrigin);
    }
  } else if (origin === "*") {
    corsHeaders.set("Access-Control-Allow-Origin", "*");
  }
  if (Array.isArray(methods)) {
    corsHeaders.set("Access-Control-Allow-Methods", methods.join(","));
  } else if (methods === "*") {
    corsHeaders.set("Access-Control-Allow-Methods", "*");
  } else {
    const requestMethod = request.headers.get("Access-Control-Request-Method");
    if (requestMethod !== null) {
      corsHeaders.set("Access-Control-Allow-Methods", requestMethod);
    }
  }
  if (Array.isArray(exposedHeaders)) {
    corsHeaders.set("Access-Control-Expose-Headers", exposedHeaders.join(","));
  } else if (exposedHeaders === "*") {
    corsHeaders.set("Access-Control-Expose-Headers", "*");
  }
  if (Array.isArray(allowedHeaders)) {
    corsHeaders.set("Access-Control-Allow-Headers", allowedHeaders.join(","));
  } else if (allowedHeaders === "*") {
    corsHeaders.set("Access-Control-Allow-Headers", "*");
  } else {
    const requestHeaders = request.headers.get("Access-Control-Request-Headers");
    if (requestHeaders !== null) {
      corsHeaders.set("Access-Control-Allow-Headers", requestHeaders);
    }
  }
  if (credentials === true) {
    corsHeaders.set("Access-Control-Allow-Credentials", "true");
  }
  if (maxAge !== void 0 && Number.isInteger(maxAge)) {
    corsHeaders.set("Access-Control-Max-Age", maxAge.toString());
  }
  context.response = new Response(response.body, {
    status: response.status,
    statusText: response.statusText,
    headers: corsHeaders
  });
};

// node_modules/reflare/dist/src/middlewares/firewall.js
var fields = /* @__PURE__ */ new Set([
  "country",
  "continent",
  "asn",
  "ip",
  "hostname",
  "user-agent"
]);
var operators = /* @__PURE__ */ new Set([
  "equal",
  "not equal",
  "greater",
  "less",
  "in",
  "not in",
  "contain",
  "not contain",
  "match",
  "not match"
]);
var validateFirewall = ({ field, operator, value }) => {
  if (field === void 0 || operator === void 0 || value === void 0) {
    throw new Error("Invalid 'firewall' field in the option object");
  }
  if (fields.has(field) === false) {
    throw new Error("Invalid 'firewall' field in the option object");
  }
  if (operators.has(operator) === false) {
    throw new Error("Invalid 'firewall' field in the option object");
  }
};
var getFieldParam = (request, field) => {
  const cfProperties = request.cf;
  switch (field) {
    case "asn":
      return cfProperties?.asn;
    case "continent":
      return cfProperties?.continent;
    case "country":
      return cfProperties?.country;
    case "hostname":
      return request.headers.get("host") || "";
    case "ip":
      return request.headers.get("cf-connecting-ip") || "";
    case "user-agent":
      return request.headers.get("user-agent") || "";
    default:
      return void 0;
  }
};
var matchOperator = (fieldParam, value) => {
  if (!(value instanceof RegExp)) {
    throw new Error("You must use 'new RegExp('...')' for 'value' in firewall configuration to use 'match' or 'not match' operator");
  }
  return value.test(fieldParam.toString());
};
var notMatchOperator = (fieldParam, value) => !matchOperator(fieldParam, value);
var equalOperator = (fieldParam, value) => fieldParam === value;
var notEqualOperator = (fieldParam, value) => fieldParam !== value;
var greaterOperator = (fieldParam, value) => {
  if (typeof fieldParam !== "number" || typeof value !== "number") {
    throw new Error("You must use number for 'value' in firewall configuration to use 'greater' or 'less' operator");
  }
  return fieldParam > value;
};
var lessOperator = (fieldParam, value) => {
  if (typeof fieldParam !== "number" || typeof value !== "number") {
    throw new Error("You must use number for 'value' in firewall configuration to use 'greater' or 'less' operator");
  }
  return fieldParam < value;
};
var containOperator = (fieldParam, value) => {
  if (typeof fieldParam !== "string" || typeof value !== "string") {
    throw new Error("You must use string for 'value' in firewall configuration to use 'contain' or 'not contain' operator");
  }
  return fieldParam.includes(value);
};
var notContainOperator = (fieldParam, value) => !containOperator(fieldParam, value);
var inOperator = (fieldParam, value) => {
  if (!Array.isArray(value)) {
    throw new Error("You must use an Array for 'value' in firewall configuration to use 'in' or 'not in' operator");
  }
  return value.some((item) => item === fieldParam);
};
var notInOperator = (fieldParam, value) => !inOperator(fieldParam, value);
var operatorsMap = {
  match: matchOperator,
  contain: containOperator,
  equal: equalOperator,
  in: inOperator,
  greater: greaterOperator,
  less: lessOperator,
  "not match": notMatchOperator,
  "not contain": notContainOperator,
  "not equal": notEqualOperator,
  "not in": notInOperator
};
var useFirewall = async (context, next) => {
  const { request, route } = context;
  if (route.firewall === void 0) {
    await next();
    return;
  }
  route.firewall.forEach(validateFirewall);
  for (const { field, operator, value } of route.firewall) {
    const fieldParam = getFieldParam(request, field);
    if (fieldParam !== void 0 && operatorsMap[operator](fieldParam, value)) {
      throw new Error("You don't have permission to access this service.");
    }
  }
  await next();
};

// node_modules/reflare/dist/src/middlewares/headers.js
var setForwardedHeaders = (headers) => {
  headers.set("X-Forwarded-Proto", "https");
  const host = headers.get("Host");
  if (host !== null) {
    headers.set("X-Forwarded-Host", host);
  }
  const ip = headers.get("cf-connecting-ip");
  const forwardedForHeader = headers.get("X-Forwarded-For");
  if (ip !== null && forwardedForHeader === null) {
    headers.set("X-Forwarded-For", ip);
  }
};
var useHeaders = async (context, next) => {
  const { request, route } = context;
  const requestHeaders = new Headers(request.headers);
  setForwardedHeaders(requestHeaders);
  if (route.headers === void 0) {
    context.request = new Request(request.url, {
      body: request.body,
      method: request.method,
      headers: requestHeaders
    });
    await next();
    return;
  }
  if (route.headers.request !== void 0) {
    for (const [key, value] of Object.entries(route.headers.request)) {
      if (value.length === 0) {
        requestHeaders.delete(key);
      } else {
        requestHeaders.set(key, value);
      }
    }
  }
  context.request = new Request(request.url, {
    body: request.body,
    method: request.method,
    headers: requestHeaders
  });
  await next();
  const { response } = context;
  const responseHeaders = new Headers(response.headers);
  if (route.headers.response !== void 0) {
    for (const [key, value] of Object.entries(route.headers.response)) {
      if (value.length === 0) {
        responseHeaders.delete(key);
      } else {
        responseHeaders.set(key, value);
      }
    }
  }
  context.response = new Response(response.body, {
    status: response.status,
    statusText: response.statusText,
    headers: responseHeaders
  });
};

// node_modules/reflare/dist/src/utils.js
var getHostname = (request) => {
  const url = new URL(request.url);
  return url.host;
};
var castToIterable = (value) => Array.isArray(value) ? value : [value];

// node_modules/reflare/dist/src/middlewares/load-balancing.js
var validateUpstream = (upstream) => {
  if (upstream.domain === void 0) {
    throw new Error("Invalid 'upstream' field in the option object");
  }
};
var ipHashHandler = (upstream, request) => {
  const ipString = request.headers.get("cf-connecting-ip") || "0.0.0.0";
  const userIP = ipString.split(".").map((octet, index, array) => parseInt(octet, 10) * 256 ** (array.length - index - 1)).reduce((accumulator, current) => accumulator + current);
  return upstream[userIP % upstream.length];
};
var randomHandler = (upstream) => {
  const weights = upstream.map((option) => option.weight === void 0 ? 1 : option.weight);
  const totalWeight = weights.reduce((acc, num, index) => {
    const sum = acc + num;
    weights[index] = sum;
    return sum;
  });
  if (totalWeight === 0) {
    throw new Error("Total weights should be greater than 0.");
  }
  const random = Math.random() * totalWeight;
  for (const index of weights.keys()) {
    if (weights[index] >= random) {
      return upstream[index];
    }
  }
  return upstream[Math.floor(Math.random() * upstream.length)];
};
var handlersMap = {
  random: randomHandler,
  "ip-hash": ipHashHandler
};
var useLoadBalancing = async (context, next) => {
  const { request, route } = context;
  const { upstream, loadBalancing } = route;
  if (upstream === void 0) {
    throw new Error("The required 'upstream' field in the option object is missing");
  }
  const upstreamIterable = castToIterable(upstream);
  upstreamIterable.forEach(validateUpstream);
  if (loadBalancing === void 0) {
    context.upstream = randomHandler(upstreamIterable, request);
    await next();
    return;
  }
  const policy = loadBalancing.policy || "random";
  const policyHandler = handlersMap[policy];
  context.upstream = policyHandler(upstreamIterable, request);
  await next();
};

// node_modules/reflare/dist/src/middlewares/upstream.js
var rewriteURL = (url, upstream) => {
  const cloneURL = new URL(url);
  const { domain, port, protocol } = upstream;
  cloneURL.hostname = domain;
  if (protocol !== void 0) {
    cloneURL.protocol = `${protocol}:`;
  }
  if (port === void 0) {
    cloneURL.port = "";
  } else {
    cloneURL.port = port.toString();
  }
  return cloneURL.href;
};
var useUpstream = async (context, next) => {
  const { request, upstream } = context;
  if (upstream === null) {
    await next();
    return;
  }
  const url = rewriteURL(request.url, upstream);
  context.request = new Request(url, context.request);
  if (upstream.onRequest) {
    const onRequest = castToIterable(upstream.onRequest);
    context.request = onRequest.reduce((reducedRequest, fn) => fn(reducedRequest, url), request);
  }
  context.response = (await fetch(context.request)).clone();
  if (upstream.onResponse) {
    const onResponse = castToIterable(upstream.onResponse);
    context.response = onResponse.reduce((reducedResponse, fn) => fn(reducedResponse, url), context.response);
  }
  await next();
};

// node_modules/reflare/dist/src/index.js
var filter = (request, routeList) => {
  const url = new URL(request.url);
  for (const route of routeList) {
    if (route.methods === void 0 || route.methods.includes(request.method)) {
      const match = castToIterable(route.path).some((path) => {
        const re = RegExp(`^${path.replace(/(\/?)\*/g, "($1.*)?").replace(/\/$/, "").replace(/:(\w+)(\?)?(\.)?/g, "$2(?<$1>[^/]+)$2$3").replace(/\.(?=[\w(])/, "\\.").replace(/\)\.\?\(([^[]+)\[\^/g, "?)\\.?($1(?<=\\.)[^\\.")}/*$`);
        return url.pathname.match(re);
      });
      if (match) {
        return route;
      }
    }
  }
  return void 0;
};
var defaultOptions = {
  provider: "static",
  routeList: []
};
var useReflare = async (options = defaultOptions) => {
  const pipeline = usePipeline(useFirewall, useLoadBalancing, useHeaders, useCORS, useUpstream);
  const routeList = [];
  if (options.provider === "static") {
    for (const route of options.routeList) {
      routeList.push(route);
    }
  }
  if (options.provider === "kv") {
    const database = new WorkersKV(options.namespace);
    const routeListKV = await database.get("route-list") || [];
    for (const routeKV of routeListKV) {
      routeList.push(routeKV);
    }
  }
  const handle = async (request) => {
    const route = filter(request, routeList);
    if (route === void 0) {
      return new Response("Failed to find a route that matches the path and method of the current request", {
        status: 500
      });
    }
    const context = {
      request: request.clone(),
      route,
      hostname: getHostname(request),
      response: new Response("Unhandled response"),
      upstream: null
    };
    try {
      await pipeline.execute(context);
    } catch (error) {
      if (error instanceof Error) {
        context.response = new Response(error.message, {
          status: 500
        });
      }
    }
    return context.response;
  };
  const unshift = (route) => {
    routeList.unshift(route);
  };
  const push = (route) => {
    routeList.push(route);
  };
  return {
    handle,
    unshift,
    push
  };
};
var src_default = useReflare;

// src/index.ts
var src_default2 = {
  async fetch(request) {
    const reflare = await src_default();
    reflare.push({
      path: "/*",
      upstream: {
        domain: "api.themoviedb.org",
        protocol: "https"
      },
      cors: {
        origin: "*"
      }
    });
    return reflare.handle(request);
  }
};
export {
  src_default2 as default
};
//# sourceMappingURL=index.js.map