From 2730551f8a58c481c85888a8beb7bd4311c03f55 Mon Sep 17 00:00:00 2001 From: John Howe <89397553+timerring@users.noreply.github.com> Date: Fri, 30 May 2025 14:56:55 +0800 Subject: [PATCH] feat: update workers demo --- cloudflareworkers/README.md | 14 ++ cloudflareworkers/src/nodeseek.js | 154 ++++++++++++++++ cloudflareworkers/src/v2ex.js | 268 ++++++++++++++++++++++++++++ cloudflareworkers/wranglernode.toml | 21 +++ cloudflareworkers/wranglerv2ex.toml | 15 ++ 5 files changed, 472 insertions(+) create mode 100644 cloudflareworkers/README.md create mode 100644 cloudflareworkers/src/nodeseek.js create mode 100644 cloudflareworkers/src/v2ex.js create mode 100644 cloudflareworkers/wranglernode.toml create mode 100644 cloudflareworkers/wranglerv2ex.toml diff --git a/cloudflareworkers/README.md b/cloudflareworkers/README.md new file mode 100644 index 0000000..f1c5125 --- /dev/null +++ b/cloudflareworkers/README.md @@ -0,0 +1,14 @@ +# Cloudflare workers Example + +## How to Run + +First ensure that your Wrangler version is up to date. + +```bash +$ wrangler -v +``` + +Now, if you run `wrangler dev` within this directory, it should use the config +in `wrangler.toml` to run the example. + +You can also run `wrangler deploy` to deploy the example. \ No newline at end of file diff --git a/cloudflareworkers/src/nodeseek.js b/cloudflareworkers/src/nodeseek.js new file mode 100644 index 0000000..52e44b1 --- /dev/null +++ b/cloudflareworkers/src/nodeseek.js @@ -0,0 +1,154 @@ +let message = ""; + +// Cloudflare Worker +export default { + async fetch(request, env, ctx) { + console.log("🚀 NodeSeek Worker started - fetch handler called"); + + const url = new URL(request.url); + + if (url.pathname === '/checkin') { + return await handleNodeSeekCheckin(env); + } + + // Other paths return simple information + return new Response( + JSON.stringify({ message: "NodeSeek Worker is running. Visit /checkin to trigger check-in." }), + { headers: { "Content-Type": "application/json" } } + ); + }, + + async scheduled(event, env, ctx) { + console.log("⏰ NodeSeek Scheduled handler called"); + const result = await handleNodeSeekCheckin(env); + const resultText = await result.text(); + console.log(`📊 NodeSeek Scheduled check-in result: ${resultText}`); + } +}; + +async function handleNodeSeekCheckin(env) { + console.log("🔧 Starting NodeSeek handleCheckin"); + + // Get cookie from environment variable + const cookie = env.NODESEEK_COOKIE; + + if (!cookie) { + console.log("❌ No NODESEEK_COOKIE found in environment"); + return new Response( + JSON.stringify({ error: "Environment variable NODESEEK_COOKIE is not set" }), + { + status: 400, + headers: { "Content-Type": "application/json" } + } + ); + } + + console.log(`✅ cookie found, length: ${cookie.length}`); + + // Initialize message time (UTC+8) + const now = new Date(); + const utc8Time = new Date(now.getTime() + 8 * 60 * 60 * 1000); + message = utc8Time.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }) + " from NodeSeek \n"; + + console.log(`🕐 Current time (UTC+8): ${utc8Time.toLocaleString('zh-CN')}`); + + console.log(`🔄 Processing single account...`); + + const result = await checkInAccount(cookie); + message += `Account: ${result.message}\n`; + + console.log(`🎉 NodeSeek check-in completed: ${result.success ? 'successful' : 'failed'}`); + + return new Response( + JSON.stringify({ + success: result.success, + message: message, + result: result, + summary: `Account checked in ${result.success ? 'successfully' : 'with failure'}` + }), + { + headers: { "Content-Type": "application/json" } + } + ); +} + +async function checkInAccount(cookie) { + const headers = { + 'Accept': '*/*', + 'Accept-Encoding': 'gzip, deflate, br, zstd', + 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', + 'Content-Length': '0', + 'Origin': 'https://www.nodeseek.com', + 'Referer': 'https://www.nodeseek.com/board', + 'Sec-CH-UA': '"Chromium";v="134", "Not:A-Brand";v="24", "Google Chrome";v="134"', + 'Sec-CH-UA-Mobile': '?0', + 'Sec-CH-UA-Platform': '"Windows"', + 'Sec-Fetch-Dest': 'empty', + 'Sec-Fetch-Mode': 'cors', + 'Sec-Fetch-Site': 'same-origin', + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36', + 'Cookie': cookie + }; + + try { + // random=true means get random reward + const url = 'https://www.nodeseek.com/api/attendance?random=true'; + console.log(`🌐 Account checking in at: ${url}`); + + const response = await fetch(url, { + method: 'POST', + headers: headers, + cf: { + // Force IPv4 + resolveOverride: 'ipv4' + } + }); + + console.log(`📡 Account Status Code: ${response.status}`); + const responseText = await response.text(); + console.log(`📄 Account Response Content: ${responseText}`); + + // Check if the check-in is successful + if (response.status === 200) { + const successMessage = `✅ Account check-in successful`; + console.log(successMessage); + + // Try to parse the response content to get more information + try { + const responseData = JSON.parse(responseText); + let detailMessage = successMessage; + if (responseData.message) { + detailMessage += ` - ${responseData.message}`; + } + if (responseData.data && responseData.data.reward) { + detailMessage += ` - Reward: ${responseData.data.reward}`; + } + return { success: true, message: detailMessage, response: responseData }; + } catch (parseError) { + return { success: true, message: successMessage, response: responseText }; + } + } else { + const failMessage = `❌ Account check-in failed, status: ${response.status}, response: ${responseText}`; + console.log(failMessage); + return { success: false, message: failMessage, response: responseText }; + } + + } catch (error) { + const errorMessage = `💥 Account check-in process error: ${error.message}`; + console.log(errorMessage); + return { success: false, message: errorMessage, error: error.message }; + } +} + +// Helper function: delay execution +function sleep(ms) { + return new Promise(resolve => setTimeout(resolve, ms)); +} \ No newline at end of file diff --git a/cloudflareworkers/src/v2ex.js b/cloudflareworkers/src/v2ex.js new file mode 100644 index 0000000..531a82b --- /dev/null +++ b/cloudflareworkers/src/v2ex.js @@ -0,0 +1,268 @@ +// V2EX cloudcheckin +let message = ""; + +// Cloudflare Worker +export default { + // Two ways to trigger the worker + // 1. Visit /checkin + async fetch(request, env, ctx) { + console.log("🚀 Worker started - fetch handler called"); + + const url = new URL(request.url); + + // Ignore favicon and Chrome DevTools requests + if (url.pathname === '/favicon.ico' || + url.pathname.startsWith('/.well-known/') || + url.pathname.includes('devtools')) { + return new Response(null, { status: 204 }); + } + + // Only process /checkin endpoint + if (url.pathname === '/checkin') { + return await handleCheckin(env); + } + + // Default response for other paths + return new Response( + JSON.stringify({ + message: "V2EX Worker is running. Visit /checkin to trigger check-in.", + endpoints: { + checkin: "/checkin" + } + }), + { + headers: { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*" + } + } + ); + }, + // 2. Scheduled at a specific time + async scheduled(event, env, ctx) { + console.log("Scheduled handler called"); + const result = await handleCheckin(env); + const resultText = await result.text(); + console.log(`Scheduled check-in result: ${resultText}`); + } +}; + +async function handleCheckin(env) { + console.log("🔧 Starting handleCheckin"); + + // Get cookie from environment variable + const cookie = env.V2EX_COOKIE; + + if (!cookie) { + console.log("No V2EX_COOKIE found in environment"); + return new Response( + JSON.stringify({ error: "Environment variable V2EX_COOKIE is not set" }), + { + status: 400, + headers: { "Content-Type": "application/json" } + } + ); + } + + console.log(`Cookie found, length: ${cookie.length}`); + + // Initialize message time (UTC+8) + const now = new Date(); + const utc8Time = new Date(now.getTime()); + message = utc8Time.toLocaleString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + second: '2-digit', + hour12: false + }).replace(/\//g, '/') + " from V2EX \n"; + + console.log(`Current time (UTC+8): ${utc8Time.toLocaleString('zh-CN')}`); + + const headers = { + "Referer": "https://www.v2ex.com/mission/daily", + "Host": "www.v2ex.com", + "User-Agent": "Mozilla/5.0 (Linux; Android 10; Redmi K30) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.83 Mobile Safari/537.36", + "Cookie": cookie + }; + + try { + console.log("Getting once number and sign status..."); + // Get once number and sign status + const { once, signed } = await getOnce(headers); + console.log(`Signed: ${signed}`); + // Check sign in + if (once && !signed) { + // const success = await checkIn(once, headers); + // if (!success) { + // console.log("Check in failed"); + // throw new Error("Fail to check in"); + // } + + const { time: timeStr, balance: balanceStr } = await getBalance(headers); + if (!timeStr || !balanceStr) { + console.log("Failed to get balance"); + throw new Error("Fail to get balance"); + } + + message += `Balance check time: ${timeStr}, Balance: ${balanceStr}\n`; + // console.log(`Your balance: ${balanceStr} at ${timeStr}`); + } else if (signed) { + console.log("Already signed today, checking balance..."); + message += "Already signed today, checking balance...\n"; + const { time: timeStr, balance: balanceStr } = await getBalance(headers); + if (timeStr && balanceStr) { + message += `Balance check time: ${timeStr}, Balance: ${balanceStr}\n`; + console.log(`💰 Current balance: ${balanceStr} at ${timeStr}`); + } + } else { + console.log("Failed to get once number"); + message += "FAIL.\n"; + throw new Error("Fail to check in"); + } + return new Response( + JSON.stringify({ success: true, message: message }), + { + headers: { "Content-Type": "application/json" } + } + ); + } catch (err) { + console.log(`Error occurred: ${err.message}`); + return new Response( + JSON.stringify({ + success: false, + error: err.message, + message: message + }), + { + status: 500, + headers: { "Content-Type": "application/json" } + } + ); + } +} + +async function getOnce(headers) { + const url = "https://www.v2ex.com/mission/daily"; + console.log(`Fetching: ${url}`); + + try { + const response = await fetch(url, { + method: "GET", + headers: headers, + cf: { + // Force IPv4 + resolveOverride: 'ipv4' + } + }); + + const content = await response.text(); + // console.log(`Response: ${content}`); + + // Check if need to login + if (content.includes("需要先登录")) { + console.log("Cookie is outdated - need to login"); + message += "The cookie is outdated. Please update the cookie."; + return { once: null, signed: false }; + } + + // Check if already signed + if (content.includes("每日登录奖励已领取")) { + console.log("Already signed today"); + message += "You have already signed today.\n"; + return { once: null, signed: true }; + } + + // Extract once number + const onceMatch = content.match(/redeem\?once=([^']+)'/); + if (onceMatch) { + const once = onceMatch[1]; + console.log(`Successfully got once: ${once}`); + message += `Successfully get once ${once}\n`; + return { once: once, signed: false }; + } else { + console.log("Failed to get once number from page"); + message += "Have not signed, but fail to get once\n"; + return { once: null, signed: false }; + } + + } catch (error) { + console.log(`Error in getOnce: ${error.message}`); + message += `Error in getOnce: ${error.message}\n`; + return { once: null, signed: false }; + } +} + +async function checkIn(once, headers) { + const url = `https://www.v2ex.com/mission/daily/redeem?once=${once}`; + console.log(`Check-in URL: ${url}`); + + try { + const response = await fetch(url, { + method: "GET", + headers: headers, + cf: { + // Force IPv4 + resolveOverride: 'ipv4' + } + }); + + const content = await response.text(); + // console.log(`Check-in response: ${content}`); + if (content.includes("已成功领取每日登录奖励")) { + console.log("Check in successful!"); + message += "Check in successfully\n"; + return true; + } else { + console.log("Check in failed - success message not found"); + message += "Fail to check in\n"; + return false; + } + + } catch (error) { + console.log(`Error in checkIn: ${error.message}`); + message += `Error in checkIn: ${error.message}\n`; + return false; + } +} + +async function getBalance(headers) { + const url = "https://www.v2ex.com/balance"; + console.log(`Fetching balance: ${url}`); + + try { + const response = await fetch(url, { + method: "GET", + headers: headers, + cf: { + // Force IPv4 + resolveOverride: 'ipv4' + } + }); + + const content = await response.text(); + + // Using the regex to extract the balance info + const pattern = /每日登录奖励.*?(.*?)<\/small>.*?.*?<\/td>.*?(.*?)<\/td>/s; + const match = content.match(pattern); + + if (match) { + const time = match[1].trim(); + const balance = match[2].trim(); + console.log(`Balance info found - Time: ${time}, Balance: ${balance}`); + message += "Successfully got balance info\n"; + return { time: time, balance: balance }; + } else { + console.log("Failed to parse balance info from page"); + message += "Failed to get balance info\n"; + return { time: null, balance: null }; + } + + } catch (error) { + console.log(`Error in getBalance: ${error.message}`); + message += `Error in getBalance: ${error.message}\n`; + return { time: null, balance: null }; + } +} \ No newline at end of file diff --git a/cloudflareworkers/wranglernode.toml b/cloudflareworkers/wranglernode.toml new file mode 100644 index 0000000..e588a1b --- /dev/null +++ b/cloudflareworkers/wranglernode.toml @@ -0,0 +1,21 @@ +name = "nodeseek-checkin-worker" +main = "src/nodeseek.js" +compatibility_date = "2024-03-29" + +# Disable IPv6 and force IPv4 only +compatibility_flags = ["no_global_navigator"] + +# Force IPv4 only for outbound requests +[env.production] +compatibility_flags = ["no_global_navigator"] + +[vars] +# Multiple cookies separated by & +NODESEEK_COOKIE = """"your cookies"""" + +[observability.logs] +enabled = true + +# Enable daily auto check-in at 8:00 AM Beijing time (UTC 0:00) +[triggers] +crons = ["0 0 * * *"] \ No newline at end of file diff --git a/cloudflareworkers/wranglerv2ex.toml b/cloudflareworkers/wranglerv2ex.toml new file mode 100644 index 0000000..a0dd3fc --- /dev/null +++ b/cloudflareworkers/wranglerv2ex.toml @@ -0,0 +1,15 @@ +name = "v2ex-checkin-worker" +main = "src/v2ex.js" +compatibility_date = "2024-03-29" + +# Enable logging +[logs] +# Send logs to Cloudflare +production = true + +[vars] +V2EX_COOKIE = """""" + +# Uncomment to enable daily auto check-in at 8:00 AM Beijing time +# [triggers] +# crons = ["0 0 * * *"]