Initial commit (by create-cloudflare CLI)
Details: C3 = create-cloudflare@2.50.2 project name = gdrive-upload-robot package manager = npm@11.2.0 wrangler = wrangler@4.22.0 git = 2.45.2.windows.1
This commit is contained in:
commit
b712232b5e
8 files changed
+3425
No files matched your search
@@ -0,0 +1,12 @@
|
|||||||
|
# http://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = tab
|
||||||
|
end_of_line = lf
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.yml]
|
||||||
|
indent_style = space
|
||||||
+172
@@ -0,0 +1,172 @@
|
|||||||
|
# Logs
|
||||||
|
|
||||||
|
logs
|
||||||
|
_.log
|
||||||
|
npm-debug.log_
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
lerna-debug.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||||
|
|
||||||
|
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
|
||||||
|
pids
|
||||||
|
_.pid
|
||||||
|
_.seed
|
||||||
|
\*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
|
||||||
|
coverage
|
||||||
|
\*.lcov
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# Snowpack dependency directory (https://snowpack.dev/)
|
||||||
|
|
||||||
|
web_modules/
|
||||||
|
|
||||||
|
# TypeScript cache
|
||||||
|
|
||||||
|
\*.tsbuildinfo
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional stylelint cache
|
||||||
|
|
||||||
|
.stylelintcache
|
||||||
|
|
||||||
|
# Microbundle cache
|
||||||
|
|
||||||
|
.rpt2_cache/
|
||||||
|
.rts2_cache_cjs/
|
||||||
|
.rts2_cache_es/
|
||||||
|
.rts2_cache_umd/
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
|
||||||
|
\*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variable files
|
||||||
|
|
||||||
|
.env
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
.env.local
|
||||||
|
|
||||||
|
# parcel-bundler cache (https://parceljs.org/)
|
||||||
|
|
||||||
|
.cache
|
||||||
|
.parcel-cache
|
||||||
|
|
||||||
|
# Next.js build output
|
||||||
|
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
|
||||||
|
# Nuxt.js build / generate output
|
||||||
|
|
||||||
|
.nuxt
|
||||||
|
dist
|
||||||
|
|
||||||
|
# Gatsby files
|
||||||
|
|
||||||
|
.cache/
|
||||||
|
|
||||||
|
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||||
|
|
||||||
|
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||||
|
|
||||||
|
# public
|
||||||
|
|
||||||
|
# vuepress build output
|
||||||
|
|
||||||
|
.vuepress/dist
|
||||||
|
|
||||||
|
# vuepress v2.x temp and cache directory
|
||||||
|
|
||||||
|
.temp
|
||||||
|
.cache
|
||||||
|
|
||||||
|
# Docusaurus cache and generated files
|
||||||
|
|
||||||
|
.docusaurus
|
||||||
|
|
||||||
|
# Serverless directories
|
||||||
|
|
||||||
|
.serverless/
|
||||||
|
|
||||||
|
# FuseBox cache
|
||||||
|
|
||||||
|
.fusebox/
|
||||||
|
|
||||||
|
# DynamoDB Local files
|
||||||
|
|
||||||
|
.dynamodb/
|
||||||
|
|
||||||
|
# TernJS port file
|
||||||
|
|
||||||
|
.tern-port
|
||||||
|
|
||||||
|
# Stores VSCode versions used for testing VSCode extensions
|
||||||
|
|
||||||
|
.vscode-test
|
||||||
|
|
||||||
|
# yarn v2
|
||||||
|
|
||||||
|
.yarn/cache
|
||||||
|
.yarn/unplugged
|
||||||
|
.yarn/build-state.yml
|
||||||
|
.yarn/install-state.gz
|
||||||
|
.pnp.\*
|
||||||
|
|
||||||
|
# wrangler project
|
||||||
|
|
||||||
|
.dev.vars
|
||||||
|
.wrangler/
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"printWidth": 140,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"useTabs": true
|
||||||
|
}
|
||||||
Generated
+3032
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"devDependencies": {
|
||||||
|
"@cloudflare/vitest-pool-workers": "^0.8.19",
|
||||||
|
"vitest": "~3.2.0",
|
||||||
|
"wrangler": "^4.23.0"
|
||||||
|
},
|
||||||
|
"name": "gdrive-upload-robot",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"deploy": "wrangler deploy",
|
||||||
|
"dev": "wrangler dev",
|
||||||
|
"start": "wrangler dev",
|
||||||
|
"test": "vitest"
|
||||||
|
},
|
||||||
|
"version": "0.0.0"
|
||||||
|
}
|
||||||
+133
@@ -0,0 +1,133 @@
|
|||||||
|
export default {
|
||||||
|
async fetch(request, env) {
|
||||||
|
if (request.method === 'POST') {
|
||||||
|
const payload = await request.json();
|
||||||
|
if (payload.message && payload.message.document) {
|
||||||
|
await handleDocument(payload.message, env);
|
||||||
|
} else if (payload.message && payload.message.photo) {
|
||||||
|
await handlePhoto(payload.message, env);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Response('OK');
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
async function handleDocument(message, env) {
|
||||||
|
const fileId = message.document.file_id;
|
||||||
|
const fileName = message.document.file_name;
|
||||||
|
await uploadToDrive(fileId, fileName, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePhoto(message, env) {
|
||||||
|
const photo = message.photo[message.photo.length - 1]; // Get the highest resolution photo
|
||||||
|
const fileId = photo.file_id;
|
||||||
|
const fileName = `${fileId}.jpg`;
|
||||||
|
await uploadToDrive(fileId, fileName, env);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function uploadToDrive(fileId, fileName, env) {
|
||||||
|
const botToken = env.TELEGRAM_BOT_TOKEN;
|
||||||
|
const driveFolderId = env.GOOGLE_DRIVE_FOLDER_ID;
|
||||||
|
const adminChatId = env.ADMIN_CHAT_ID;
|
||||||
|
const googleCredentials = JSON.parse(env.GOOGLE_CREDENTIALS);
|
||||||
|
|
||||||
|
// Get file path from Telegram
|
||||||
|
const fileDetailsUrl = `https://api.telegram.org/bot${botToken}/getFile?file_id=${fileId}`;
|
||||||
|
const fileDetailsResponse = await fetch(fileDetailsUrl);
|
||||||
|
const fileDetails = await fileDetailsResponse.json();
|
||||||
|
if (!fileDetails.ok) {
|
||||||
|
await sendMessage(adminChatId, `Error getting file details: ${fileDetails.description}`, botToken);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const filePath = fileDetails.result.file_path;
|
||||||
|
const fileUrl = `https://api.telegram.org/file/bot${botToken}/${filePath}`;
|
||||||
|
|
||||||
|
// Download file from Telegram
|
||||||
|
const fileResponse = await fetch(fileUrl);
|
||||||
|
const fileData = await fileResponse.arrayBuffer();
|
||||||
|
|
||||||
|
// Authenticate with Google Drive
|
||||||
|
const jwtToken = await getGoogleAuthToken(googleCredentials);
|
||||||
|
|
||||||
|
// Upload file to Google Drive
|
||||||
|
const uploadUrl = 'https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart';
|
||||||
|
const metadata = {
|
||||||
|
name: fileName,
|
||||||
|
parents: [driveFolderId],
|
||||||
|
};
|
||||||
|
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('metadata', new Blob([JSON.stringify(metadata)], { type: 'application/json' }));
|
||||||
|
formData.append('file', new Blob([fileData]));
|
||||||
|
|
||||||
|
const uploadResponse = await fetch(uploadUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${jwtToken}`,
|
||||||
|
},
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
const uploadResult = await uploadResponse.json();
|
||||||
|
if (uploadResult.id) {
|
||||||
|
await sendMessage(adminChatId, `File "${fileName}" uploaded to Google Drive successfully!`, botToken);
|
||||||
|
} else {
|
||||||
|
await sendMessage(adminChatId, `Error uploading file: ${JSON.stringify(uploadResult.error)}`, botToken);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getGoogleAuthToken(credentials) {
|
||||||
|
const header = { alg: 'RS256', typ: 'JWT' };
|
||||||
|
const now = Math.floor(Date.now() / 1000);
|
||||||
|
const payload = {
|
||||||
|
iss: credentials.client_email,
|
||||||
|
scope: 'https://www.googleapis.com/auth/drive',
|
||||||
|
aud: 'https://oauth2.googleapis.com/token',
|
||||||
|
exp: now + 3600,
|
||||||
|
iat: now,
|
||||||
|
};
|
||||||
|
|
||||||
|
const encodedHeader = btoa(JSON.stringify(header)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||||
|
const encodedPayload = btoa(JSON.stringify(payload)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||||
|
const dataToSign = `${encodedHeader}.${encodedPayload}`;
|
||||||
|
|
||||||
|
const privateKey = await crypto.subtle.importKey(
|
||||||
|
'pkcs8',
|
||||||
|
str2ab(atob(credentials.private_key.replace('-----BEGIN PRIVATE KEY-----\n', '').replace('\n-----END PRIVATE KEY-----\n', ''))),
|
||||||
|
{ name: 'RSASSA-PKCS1-v1_5', hash: 'SHA-256' },
|
||||||
|
false,
|
||||||
|
['sign']
|
||||||
|
);
|
||||||
|
|
||||||
|
const signature = await crypto.subtle.sign('RSASSA-PKCS1-v1_5', privateKey, str2ab(dataToSign));
|
||||||
|
const encodedSignature = btoa(ab2str(signature)).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
||||||
|
|
||||||
|
const jwt = `${dataToSign}.${encodedSignature}`;
|
||||||
|
|
||||||
|
const tokenResponse = await fetch('https://oauth2.googleapis.com/token', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
body: `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=${jwt}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tokenData = await tokenResponse.json();
|
||||||
|
return tokenData.access_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function sendMessage(chatId, text, botToken) {
|
||||||
|
const url = `https://api.telegram.org/bot${botToken}/sendMessage?chat_id=${chatId}&text=${encodeURIComponent(text)}`;
|
||||||
|
await fetch(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function str2ab(str) {
|
||||||
|
const buf = new ArrayBuffer(str.length);
|
||||||
|
const bufView = new Uint8Array(buf);
|
||||||
|
for (let i = 0, strLen = str.length; i < strLen; i++) {
|
||||||
|
bufView[i] = str.charCodeAt(i);
|
||||||
|
}
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ab2str(buf) {
|
||||||
|
return String.fromCharCode.apply(null, new Uint8Array(buf));
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { defineWorkersConfig } from '@cloudflare/vitest-pool-workers/config';
|
||||||
|
|
||||||
|
export default defineWorkersConfig({
|
||||||
|
test: {
|
||||||
|
poolOptions: {
|
||||||
|
workers: {
|
||||||
|
wrangler: { configPath: './wrangler.jsonc' },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* For more details on how to configure Wrangler, refer to:
|
||||||
|
* https://developers.cloudflare.com/workers/wrangler/configuration/
|
||||||
|
*/
|
||||||
|
{
|
||||||
|
"$schema": "node_modules/wrangler/config-schema.json",
|
||||||
|
"compatibility_date": "2025-07-03",
|
||||||
|
"main": "src/index.js",
|
||||||
|
"name": "gdrive-upload-robot",
|
||||||
|
"observability": {
|
||||||
|
"enabled": true
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Smart Placement
|
||||||
|
* Docs: https://developers.cloudflare.com/workers/configuration/smart-placement/#smart-placement
|
||||||
|
*/
|
||||||
|
// "placement": { "mode": "smart" },
|
||||||
|
/**
|
||||||
|
* Bindings
|
||||||
|
* Bindings allow your Worker to interact with resources on the Cloudflare Developer Platform, including
|
||||||
|
* databases, object storage, AI inference, real-time communication and more.
|
||||||
|
* https://developers.cloudflare.com/workers/runtime-apis/bindings/
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Environment Variables
|
||||||
|
* https://developers.cloudflare.com/workers/wrangler/configuration/#environment-variables
|
||||||
|
*/
|
||||||
|
// "vars": { "MY_VARIABLE": "production_value" },
|
||||||
|
/**
|
||||||
|
* Note: Use secrets to store sensitive data.
|
||||||
|
* https://developers.cloudflare.com/workers/configuration/secrets/
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Static Assets
|
||||||
|
* https://developers.cloudflare.com/workers/static-assets/binding/
|
||||||
|
*/
|
||||||
|
// "assets": { "directory": "./public/", "binding": "ASSETS" },
|
||||||
|
/**
|
||||||
|
* Service Bindings (communicate between multiple Workers)
|
||||||
|
* https://developers.cloudflare.com/workers/wrangler/configuration/#service-bindings
|
||||||
|
*/
|
||||||
|
// "services": [{ "binding": "MY_SERVICE", "service": "my-service" }]
|
||||||
|
}
|
||||||
Reference in new issue
Block a user