E5续订-Cloudflare-Workers-E5Renew

E5续订-Cloudflare-Workers-E5Renew

功能

使用 Cloudflare Workers 随机调用 Microsoft Graph API 续期 E5 订阅。不保证续期成功。

部署

Cloudflare Workers

  1. 创建一个新的 Workers KV 命名空间

d2b5ca33bd20231210170101

  1. 创建一个新的 Cloudflare Workers 服务 并点击 “快速编辑” 按钮。

d2b5ca33bd20231210170157

  1. 将 index.js 文件的内容复制到代码字段中并点击 “保存并部署”。
const MS_SCOPE = "offline_access User.Read Files.Read.All Mail.Read MailboxSettings.Read";
const MS_GRAPH_ROOT = "https://graph.microsoft.com";
const MS_GRAPH_VER = "1.0";
const MS_GRAPH_API_LIST = [
  `${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me`,
  `${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me/drive`,
  `${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me/drive/recent`,
  `${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me/drive/sharedWithMe`,
  `${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me/drive/root`,
  `${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me/drive/root/children`,
  `${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me/mailFolders`,
  `${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me/mailFolders/inbox`,
  `${MS_GRAPH_ROOT}/v${MS_GRAPH_VER}/me/messages`,
];

addEventListener("fetch", (event) => {
  event.respondWith(
    handleRequest(event.request).catch(
      (err) => new Response(err.stack, { status: 500 })
    )
  );
});

addEventListener('scheduled', event => {
  event.waitUntil(handleScheduled(event));
});

async function handleRequest(request) {
  if (typeof MS_CLIENT_ID === "undefined"
    || typeof MS_CLIENT_SECRET === "undefined"
    || typeof MS_REDIRECT_URI === "undefined") {
    return createHTMLResponse(`<div class="alert alert-danger" role="alert">
      Missing MS_CLIENT_ID, MS_CLIENT_SECRET or MS_REDIRECT_URI
    </div>`, 500);
  }

  const { pathname } = new URL(request.url);

  if (typeof CRON_PATH !== "undefined" && pathname.startsWith(CRON_PATH)) {
    await sendMessage("Scheduled start");
    for (let i = 0; i < MS_GRAPH_API_LIST.length; i++) {
      await fetchMSApi(MS_GRAPH_API_LIST[i]);
      await sleep(randomInt(1000, 5000));
    }
    await sendMessage("Scheduled finish");
  }

  if (await Token.get("refresh_token") !== null) {
    return fetch("https://welcome.developers.workers.dev");
  }

  if (pathname.startsWith("/login")) {
    return handleLogin(request);
  }

  if (pathname.startsWith("/callback")) {
    return handleCallback(request);
  }

  return createHTMLResponse(`<a class="w-50 btn btn-lg btn-primary btn-block" href="/login" role="button">Authorize</a>`);
}

async function handleScheduled(event) {
  await sendMessage("Scheduled start");
  const count = randomInt(2, 10);
  for (let i = 0; i < count; i++) {
    await randomFetchMSApi();
    await sleep(randomInt(1000, 5000));
  }
  await sendMessage("Scheduled finish");
}

async function sendMessage(message) {
  if (typeof TGBOT_TOKEN === "undefined" || typeof TGBOT_CHAT_ID === "undefined") {
    console.log(message);
    return;
  }

  const response = await retryFetch(`https://api.telegram.org/bot${TGBOT_TOKEN}/sendMessage`, {
    method: "post",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      chat_id: TGBOT_CHAT_ID,
      text: message
    })
  });
  if (response.status !== 200) {
    console.error(await response.text());
  }
}

async function handleLogin(request) {
  const url = new URL("https://login.microsoftonline.com/common/oauth2/v2.0/authorize");
  url.searchParams.append("client_id", MS_CLIENT_ID);
  url.searchParams.append("redirect_uri", MS_REDIRECT_URI);
  url.searchParams.append("scope", MS_SCOPE);
  url.searchParams.append("response_type", "code");
  return Response.redirect(url.href);
}

async function handleCallback(request) {
  const response = await retryFetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", {
    method: "post",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded"
    },
    body: new URLSearchParams({
      "client_id": MS_CLIENT_ID,
      "client_secret": MS_CLIENT_SECRET,
      "redirect_uri": MS_REDIRECT_URI,
      "scope": MS_SCOPE,
      "code": new URL(request.url).searchParams.get("code"),
      "grant_type": "authorization_code"
    }),
  });


  try {
    const responseJson = await response.json();
    if (response.status !== 200) {
      return createHTMLResponse(`<div class="alert alert-danger" role="alert">
        <p>Error occurred: ${responseJson["error"]}</p>
        <p>${responseJson["error_description"]}</p>
        <p>See: ${responseJson["error_uri"]}</p>
      </div>`, response.status);
    }

    let userInfo
    await Promise.all([
      Token.put("access_token", responseJson["access_token"], { expirationTtl: responseJson["expires_in"] }),
      Token.put("refresh_token", responseJson["refresh_token"]),
      getUserInfo(responseJson["access_token"]).then((resp) => {
        userInfo = resp;
      }),
    ]);
    return createHTMLResponse(`<div class="alert alert-success" role="alert">
      Successfully logged in as ${userInfo["displayName"]} (${userInfo["mail"]})
    </div>`);
  } catch (e) {
    return createHTMLResponse(`<div class="alert alert-danger" role="alert">
      ${e.message}
    </div>`, 500);
  }
}

async function getAccessToken() {
  const accessToken = await Token.get("access_token");
  if (accessToken !== null) {
    return accessToken;
  }

  const refreshToken = await Token.get("refresh_token");
  if (refreshToken === null) {
    return null;
  }

  const response = await retryFetch("https://login.microsoftonline.com/common/oauth2/v2.0/token", {
    method: "post",
    headers: {
      "Content-Type": "application/x-www-form-urlencoded"
    },
    body: new URLSearchParams({
      "client_id": MS_CLIENT_ID,
      "client_secret": MS_CLIENT_SECRET,
      "redirect_uri": MS_REDIRECT_URI,
      "scope": MS_SCOPE,
      "grant_type": "refresh_token",
      "refresh_token": refreshToken
    }),
  });
  if (response.status !== 200) {
    console.error("Error refreshing access token:", await response.text());
    return null;
  }

  const responseJson = await response.json();
  await Promise.all([
    Token.put("access_token", responseJson["access_token"], { expirationTtl: responseJson["expires_in"] }),
    Token.put("refresh_token", responseJson["refresh_token"]),
  ]);
  return responseJson["access_token"];
}

async function getUserInfo(accessToken) {
  const response = await retryFetch("https://graph.microsoft.com/v1.0/me", {
    headers: {
      "Authorization": "Bearer " + accessToken
    }
  });
  if (response.status !== 200) {
    return null;
  }
  return await response.json();
}

async function randomFetchMSApi() {
  const index = randomInt(0, MS_GRAPH_API_LIST.length);
  return await fetchMSApi(MS_GRAPH_API_LIST[index]);
}

async function fetchMSApi(url) {
  const accessToken = await getAccessToken();
  if (accessToken === null) {
    sendMessage("Not login");
    return;
  }

  try {
    const response = await retryFetch(url, {
      method: "get",
      headers: {
        "Authorization": "Bearer " + accessToken
      }
    });
    if (response.status === 401) {
      Token.delete("access_token");
    }
    sendMessage(url + ": " + response.statusText);
  }
  catch (e) {
    sendMessage(url + ": " + e.message);
  }
}

function randomInt(min, max) {
  if (min > max) {
    return randomInt(max, min);
  }
  return Math.floor(Math.random() * (max - min) + min);
}

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

function retry(fn, times = 3, delay = 1000) {
  return async (...args) => {
    for (let i = 0; i < times; i++) {
      try {
        return await fn(...args);
      } catch (e) {
        console.error(`Retry: #${i} ${e.message}`);
        await sleep(delay);
      }
    }
    console.error("Failed to execute");
  }
}

function retryFetch(url, options) {
  return retry(fetch)(url, options);
}

function createHTMLResponse(slot, status = 200) {
  return new Response(`<!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
      <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
        integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
      <title>Microsoft Graph Login</title>
      <style>
        html,
        body {
          height: 100%
        }
        body {
          display: flex;
          align-items: center;
          background-color: #f5f5f5;
        }
      </style>
    </head>
    <body>
      <div class="container w-70">
        <div class="text-center">
          <h5 class="mb-4">Microsoft Graph Login</h5>
          ${slot}
        </div>
      </div>
      <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"
        integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p"
        crossorigin="anonymous"></script>
    </html>`, {
    status: status,
    headers: {
      "Content-Type": "text/html"
    }
  });
}

d2b5ca33bd20231210170331

  1. 返回 “设置” 选项卡,在 “环境变量” 中,添加以下环境变量:
    • CRON_PATH:手动触发定时任务的路径,例如 /cron。如果不需要手动触发定时任务,可以不设置。
    • TGBOT_TOKEN:用于发送通知的 Telegram 机器人 Token。可以通过 BotFather 创建。如果不需要接收通知,可以不设置。
    • TGBOT_CHAT_ID:通知接收者的 Telegram 聊天 ID。可以通过 userinfobot 获取。如果不需要接收通知,可以不设置。
    • MS_CLIENT_ID:微软应用的 Client ID。可以在下面的步骤中获取。
    • MS_CLIENT_SECRET:微软应用的 Client Secret。可以在下面的步骤中获取。
    • MS_REDIRECT_URI:微软应用的 Redirect URI。应该形如 https://welcome.developers.workers.dev/callback,其中 welcome 是你的 Workers 服务的名称,developers 是你的子域名称。

d2b5ca33bd20231210170433

  1. 在 “KV 命名空间绑定” 中,添加如下内容:
    • 变量名称: Token
    • KV 命名空间:步骤 1 中创建的 KV 命名空间。
  2. 选择 “触发器” 选项卡,在 “Cron 触发器” 中,点击 “添加 Cron 触发器” 按钮,选择你需要的频率,然后点击 “添加触发器” 按钮。

Microsoft Azure AD

  1. 登录 Azure Portal 并从菜单中选择 “Azure Active Directory”。
  2. 选择 “应用注册”,然后点击 “新注册” 按钮。
  3. 名称中填入任意名称。受支持的帐户类型选择 任何组织目录(任何 Azure AD 目录 - 多租户)中的帐户和个人 Microsoft 帐户(例如,Skype、Xbox)
  4. 重定向 URI 中,平台选择 Web ,地址填入 https://welcome.developers.workers.dev/callback,其中 welcome 是你的 Workers 服务的名称,developers 是你的子域名称。
  5. 点击 “注册” 按钮。
  6. 在 “证书和密码” 选项卡中,点击 “新客户端密码” 按钮,填入任意说明并选择截止期限。点击 “添加” 按钮。密码会显示在 “值” 中。复制生成的密码,将其用于workers服务中的 MS_CLIENT_SECRET 环境变量。
  7. 在 “概述” 选项卡中,复制 “应用程序 (客户端) ID”,将其用于workers服务中的 MS_CLIENT_ID 环境变量。

授权

访问形如 https://welcome.developers.workers.dev/login 的 Cloudflare Workers 默认路由登录。点击 “Authorize” 按钮,然后你会被重定向到微软登录页面。点击 “同意” 按钮,然后你会被重定向到 Cloudflare Workers 页面。如果一切正常,你会看到 “Successfully logged in” 的提示。尽情享受吧!

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
评论 抢沙发

请登录后发表评论

    请登录后查看评论内容