功能
使用 Cloudflare Workers 随机调用 Microsoft Graph API 续期 E5 订阅。不保证续期成功。
部署
Cloudflare Workers
- 创建一个新的 Workers KV 命名空间。
- 创建一个新的 Cloudflare Workers 服务 并点击 “快速编辑” 按钮。
- 将 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"
}
});
}
- 返回 “设置” 选项卡,在 “环境变量” 中,添加以下环境变量:
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
是你的子域名称。
- 在 “KV 命名空间绑定” 中,添加如下内容:
变量名称
:Token
KV 命名空间
:步骤 1 中创建的 KV 命名空间。
- 选择 “触发器” 选项卡,在 “Cron 触发器” 中,点击 “添加 Cron 触发器” 按钮,选择你需要的频率,然后点击 “添加触发器” 按钮。
Microsoft Azure AD
- 登录 Azure Portal 并从菜单中选择 “Azure Active Directory”。
- 选择 “应用注册”,然后点击 “新注册” 按钮。
- 名称中填入任意名称。受支持的帐户类型选择
任何组织目录(任何 Azure AD 目录 - 多租户)中的帐户和个人 Microsoft 帐户(例如,Skype、Xbox)
。 - 重定向 URI 中,平台选择
Web
,地址填入https://welcome.developers.workers.dev/callback
,其中welcome
是你的 Workers 服务的名称,developers
是你的子域名称。 - 点击 “注册” 按钮。
- 在 “证书和密码” 选项卡中,点击 “新客户端密码” 按钮,填入任意说明并选择截止期限。点击 “添加” 按钮。密码会显示在 “值” 中。复制生成的密码,将其用于workers服务中的
MS_CLIENT_SECRET
环境变量。 - 在 “概述” 选项卡中,复制 “应用程序 (客户端) ID”,将其用于workers服务中的
MS_CLIENT_ID
环境变量。
授权
访问形如 https://welcome.developers.workers.dev/login
的 Cloudflare Workers 默认路由登录。点击 “Authorize” 按钮,然后你会被重定向到微软登录页面。点击 “同意” 按钮,然后你会被重定向到 Cloudflare Workers 页面。如果一切正常,你会看到 “Successfully logged in” 的提示。尽情享受吧!
© 版权声明
本站网络名称:
小怪兽
本站永久网址:
https://77il.cn
网站侵权说明:
本网站的文章部分内容可能来源于网络,仅供大家学习与参考,如有侵权,请联系站长QQ3031379629删除处理。
1 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
2 本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
3 本站资源大多存储在云盘,如发现链接失效,请联系我们我们会第一时间更新。
1 本站一切资源不代表本站立场,并不代表本站赞同其观点和对其真实性负责。
2 本站一律禁止以任何方式发布或转载任何违法的相关信息,访客发现请向站长举报
3 本站资源大多存储在云盘,如发现链接失效,请联系我们我们会第一时间更新。
THE END
请登录后查看评论内容