update tg.js gd.js by @iwestlin

This commit is contained in:
liaojack8 2020-07-04 01:12:20 +08:00
parent f73cc04880
commit ba07307030
3 changed files with 211 additions and 104 deletions

150
src/gd.js
View File

@ -12,19 +12,28 @@ const { AUTH, RETRY_LIMIT, PARALLEL_LIMIT, TIMEOUT_BASE, TIMEOUT_MAX, LOG_DELAY,
const { db } = require('../db')
const { make_table, make_tg_table, make_html, summary } = require('./summary')
const FILE_EXCEED_MSG = '您的团队盘文件数已超限(40万),停止复制'
const FOLDER_TYPE = 'application/vnd.google-apps.folder'
const { https_proxy } = process.env
const axins = axios.create(https_proxy ? { httpsAgent: new HttpsProxyAgent(https_proxy) } : {})
const SA_BATCH_SIZE = 1000
const SA_FILES = fs.readdirSync(path.join(__dirname, '../sa')).filter(v => v.endsWith('.json'))
SA_FILES.flag = 0
let SA_TOKENS = get_sa_batch()
let SA_TOKENS = SA_FILES.map(filename => {
const gtoken = new GoogleToken({
keyFile: path.join(__dirname, '../sa', filename),
scope: ['https://www.googleapis.com/auth/drive']
function get_sa_batch () {
const new_flag = SA_FILES.flag + SA_BATCH_SIZE
const files = SA_FILES.slice(SA_FILES.flag, new_flag)
SA_FILES.flag = new_flag
return files.map(filename => {
const gtoken = new GoogleToken({
keyFile: path.join(__dirname, '../sa', filename),
scope: ['https://www.googleapis.com/auth/drive']
})
return { gtoken, expires: 0 }
})
return { gtoken, expires: 0 }
})
}
handle_exit(() => {
// console.log('handle_exit running')
@ -38,11 +47,13 @@ handle_exit(() => {
async function gen_count_body ({ fid, type, update, service_account }) {
async function update_info () {
const info = await walk_and_save({ fid, update, service_account }) // 这一步已经将fid记录存入数据库中了
const { summary } = db.prepare('SELECT summary from gd WHERE fid=?').get(fid)
return [info, JSON.parse(summary)]
const row = db.prepare('SELECT summary from gd WHERE fid=?').get(fid)
if (!row) return []
return [info, JSON.parse(row.summary)]
}
function render_smy (smy, type) {
if (!smy) return
if (['html', 'curl', 'tg'].includes(type)) {
smy = (typeof smy === 'object') ? smy : JSON.parse(smy)
const type_func = {
@ -66,7 +77,7 @@ async function gen_count_body ({ fid, type, update, service_account }) {
if (!info) { // 说明上次统计过程中断了
[info] = await update_info()
}
return JSON.stringify(info)
return info && JSON.stringify(info)
}
if (smy) return render_smy(smy, type)
if (record && record.summary) return render_smy(record.summary, type)
@ -154,7 +165,7 @@ async function walk_and_save ({ fid, not_teamdrive, update, service_account }) {
const loop = setInterval(() => {
const now = dayjs().format('HH:mm:ss')
const message = `${now} | 已获取对象 ${result.length} | 排队等候的网络请求 ${limit.pendingCount}`
const message = `${now} | 已获取对象 ${result.length} | 网络请求 进行中${limit.activeCount}/排队${limit.pendingCount}`
print_progress(message)
}, 1000)
@ -248,7 +259,7 @@ async function ls_folder ({ fid, not_teamdrive, service_account }) {
}
async function gen_headers (use_sa) {
use_sa = use_sa && SA_TOKENS.length
// use_sa = use_sa && SA_TOKENS.length
const access_token = use_sa ? (await get_sa_token()).access_token : (await get_access_token())
return { authorization: 'Bearer ' + access_token }
}
@ -276,15 +287,17 @@ async function get_access_token () {
return data.access_token
}
// get_sa_token().catch(console.error)
async function get_sa_token () {
let tk
if (!SA_TOKENS.length) SA_TOKENS = get_sa_batch()
while (SA_TOKENS.length) {
tk = get_random_element(SA_TOKENS)
const tk = get_random_element(SA_TOKENS)
try {
return await real_get_sa_token(tk)
} catch (e) {
console.log(e)
SA_TOKENS = SA_TOKENS.filter(v => v.gtoken !== tk.gtoken)
if (!SA_TOKENS.length) SA_TOKENS = get_sa_batch()
}
}
throw new Error('没有可用的SA帐号')
@ -302,7 +315,7 @@ function real_get_sa_token (el) {
// console.log('got sa token', tokens)
const { access_token, expires_in } = tokens
el.value = access_token
el.expires = Date.now() + 1000 * expires_in
el.expires = Date.now() + 1000 * (expires_in - 60 * 5) // 提前5分钟判定为过期
resolve({ access_token, gtoken })
}
})
@ -323,7 +336,7 @@ function validate_fid (fid) {
return fid.match(reg)
}
async function create_folder (name, parent, use_sa) {
async function create_folder (name, parent, use_sa, limit) {
let url = `https://www.googleapis.com/drive/v3/files`
const params = { supportsAllDrives: true }
url += '?' + params_to_query(params)
@ -333,18 +346,34 @@ async function create_folder (name, parent, use_sa) {
parents: [parent]
}
let retry = 0
let data
while (!data && (retry < RETRY_LIMIT)) {
let err_message
while (retry < RETRY_LIMIT) {
try {
const headers = await gen_headers(use_sa)
data = (await axins.post(url, post_data, { headers })).data
return (await axins.post(url, post_data, { headers })).data
} catch (err) {
err_message = err.message
retry++
handle_error(err)
const data = err && err.response && err.response.data
const message = data && data.error && data.error.message
if (message && message.toLowerCase().includes('file limit')) {
if (limit) limit.clearQueue()
throw new Error(FILE_EXCEED_MSG)
}
console.log('创建目录重试中:', name, '重试次数:', retry)
}
}
return data
throw new Error(err_message + ' 目录名:' + name)
}
async function get_name_by_id (fid) {
try {
const { name } = await get_info_by_id(fid, true)
return name
} catch (e) {
return fid
}
}
async function get_info_by_id (fid, use_sa) {
@ -353,7 +382,7 @@ async function get_info_by_id (fid, use_sa) {
includeItemsFromAllDrives: true,
supportsAllDrives: true,
corpora: 'allDrives',
fields: 'id,name,owners'
fields: 'id,name'
}
url += '?' + params_to_query(params)
const headers = await gen_headers(use_sa)
@ -376,7 +405,7 @@ async function user_choose () {
return answer.value
}
async function copy ({ source, target, name, min_size, update, not_teamdrive, service_account, is_server }) {
async function copy ({ source, target, name, min_size, update, not_teamdrive, service_account, dncnr, is_server }) {
target = target || DEFAULT_TARGET
if (!target) throw new Error('目标位置不能为空')
@ -384,7 +413,7 @@ async function copy ({ source, target, name, min_size, update, not_teamdrive, se
if (record && record.status === 'copying') return console.log('已有相同源和目的地的任务正在运行,强制退出')
try {
return await real_copy({ source, target, name, min_size, update, not_teamdrive, service_account, is_server })
return await real_copy({ source, target, name, min_size, update, dncnr, not_teamdrive, service_account, is_server })
} catch (err) {
console.error('复制文件夹出错', err)
const record = db.prepare('select id, status from task where source=? and target=?').get(source, target)
@ -393,8 +422,9 @@ async function copy ({ source, target, name, min_size, update, not_teamdrive, se
}
// 待解决如果用户手动ctrl+c中断进程那么已经发出的请求就算完成了也不会记录到本地数据库中所以可能产生重复文件
async function real_copy ({ source, target, name, min_size, update, not_teamdrive, service_account, is_server }) {
async function real_copy ({ source, target, name, min_size, update, dncnr, not_teamdrive, service_account, is_server }) {
async function get_new_root () {
if (dncnr) return { id: target }
if (name) {
return create_folder(name, target, service_account)
} else {
@ -432,12 +462,11 @@ async function real_copy ({ source, target, name, min_size, update, not_teamdriv
root,
task_id: record.id
})
await copy_files({ files, mapping: all_mapping, root, task_id: record.id })
await copy_files({ files, service_account, root, mapping: all_mapping, task_id: record.id })
db.prepare('update task set status=?, ftime=? where id=?').run('finished', Date.now(), record.id)
return { id: root }
return { id: root, task_id: record.id }
} else if (choice === 'restart') {
const new_root = await get_new_root()
if (!new_root) throw new Error('创建目录失败,请检查您的帐号是否有相应的权限')
const root_mapping = source + ' ' + new_root.id + '\n'
db.prepare('update task set status=?, copied=?, mapping=? where id=?')
.run('copying', '', root_mapping, record.id)
@ -454,16 +483,15 @@ async function real_copy ({ source, target, name, min_size, update, not_teamdriv
root: new_root.id,
task_id: record.id
})
await copy_files({ files, mapping, root: new_root.id, task_id: record.id })
await copy_files({ files, mapping, service_account, root: new_root.id, task_id: record.id })
db.prepare('update task set status=?, ftime=? where id=?').run('finished', Date.now(), record.id)
return new_root
return { id: new_root.id, task_id: record.id }
} else {
// ctrl+c 退出
return console.log('退出程序')
}
} else {
const new_root = await get_new_root()
if (!new_root) throw new Error('创建目录失败,请检查您的帐号是否有相应的权限')
const root_mapping = source + ' ' + new_root.id + '\n'
const { lastInsertRowid } = db.prepare('insert into task (source, target, status, mapping, ctime) values (?, ?, ?, ?, ?)').run(source, target, 'copying', root_mapping, Date.now())
const arr = await walk_and_save({ fid: source, update, not_teamdrive, service_account })
@ -479,39 +507,33 @@ async function real_copy ({ source, target, name, min_size, update, not_teamdriv
root: new_root.id,
task_id: lastInsertRowid
})
await copy_files({ files, mapping, root: new_root.id, task_id: lastInsertRowid })
await copy_files({ files, mapping, service_account, root: new_root.id, task_id: lastInsertRowid })
db.prepare('update task set status=?, ftime=? where id=?').run('finished', Date.now(), lastInsertRowid)
return new_root
return { id: new_root.id, task_id: lastInsertRowid }
}
}
async function copy_files ({ files, mapping, root, task_id }) {
async function copy_files ({ files, mapping, service_account, root, task_id }) {
console.log('\n开始复制文件总数', files.length)
const limit = pLimit(PARALLEL_LIMIT)
let count = 0
const loop = setInterval(() => {
const now = dayjs().format('HH:mm:ss')
const message = `${now} | 已复制文件数 ${count} | 排队等候的网络请求 ${limit.pendingCount}`
const message = `${now} | 已复制文件数 ${count} | 网络请求 进行中${limit.activeCount}/排队${limit.pendingCount}`
print_progress(message)
}, 1000)
await Promise.all(files.map(async file => {
try {
const { id, parent } = file
const target = mapping[parent] || root
const new_file = await limit(() => copy_file(id, target))
if (new_file) {
db.prepare('update task set status=?, copied = copied || ? where id=?')
.run('copying', id + '\n', task_id)
}
return Promise.all(files.map(async file => {
const { id, parent } = file
const target = mapping[parent] || root
const new_file = await limit(() => copy_file(id, target, service_account, limit))
if (new_file) {
count++
} catch (e) {
console.error(e)
db.prepare('update task set status=?, copied = copied || ? where id=?').run('copying', id + '\n', task_id)
}
}))
clearInterval(loop)
})).finally(() => clearInterval(loop))
}
async function copy_file (id, parent) {
async function copy_file (id, parent, use_sa, limit) {
let url = `https://www.googleapis.com/drive/v3/files/${id}/copy`
let params = { supportsAllDrives: true }
url += '?' + params_to_query(params)
@ -519,7 +541,7 @@ async function copy_file (id, parent) {
let retry = 0
while (retry < RETRY_LIMIT) {
let gtoken
if (SA_TOKENS.length) { // 如果有sa文件则优先使用
if (use_sa) {
const temp = await get_sa_token()
gtoken = temp.gtoken
config.headers = { authorization: 'Bearer ' + temp.access_token }
@ -534,13 +556,19 @@ async function copy_file (id, parent) {
handle_error(err)
const data = err && err.response && err.response.data
const message = data && data.error && data.error.message
if (message && message.toLowerCase().includes('file limit')) {
if (limit) limit.clearQueue()
throw new Error('您的团队盘文件数已超限,停止复制')
}
if (message && message.toLowerCase().includes('rate limit')) {
SA_TOKENS = SA_TOKENS.filter(v => v.gtoken !== gtoken)
if (!SA_TOKENS.length) SA_TOKENS = get_sa_batch()
console.log('此帐号触发使用限额剩余可用service account帐号数量', SA_TOKENS.length)
}
}
}
if (!SA_TOKENS.length) {
if (limit) limit.clearQueue()
throw new Error('所有SA帐号流量已用完')
} else {
console.warn('复制文件失败文件id: ' + id)
@ -560,7 +588,7 @@ async function create_folders ({ source, old_mapping, folders, root, task_id, se
const loop = setInterval(() => {
const now = dayjs().format('HH:mm:ss')
const message = `${now} | 已创建目录数 ${count} | 排队等候的网络请求 ${limit.pendingCount}`
const message = `${now} | 已创建目录数 ${count} | 网络请求 进行中${limit.activeCount}/排队${limit.pendingCount}`
print_progress(message)
}, 1000)
@ -569,14 +597,17 @@ async function create_folders ({ source, old_mapping, folders, root, task_id, se
try {
const { name, id, parent } = v
const target = mapping[parent] || root
const new_folder = await limit(() => create_folder(name, target, service_account))
if (!new_folder) throw new Error(name + '创建失败')
const new_folder = await limit(() => create_folder(name, target, service_account, limit))
count++
mapping[id] = new_folder.id
const mapping_record = id + ' ' + new_folder.id + '\n'
db.prepare('update task set status=?, mapping = mapping || ? where id=?').run('copying', mapping_record, task_id)
} catch (e) {
console.error('创建目录出错:', v, e)
if (e.message === FILE_EXCEED_MSG) {
clearInterval(loop)
throw new Error(FILE_EXCEED_MSG)
}
console.error('创建目录出错:', e.message)
}
}))
folders = folders.filter(v => !mapping[v.id])
@ -627,7 +658,7 @@ async function confirm_dedupe ({ file_number, folder_number }) {
const answer = await prompts({
type: 'select',
name: 'value',
message: `检测到重复文件${file_number}个,重复目录${folder_number}个,是否删除?`,
message: `检测到同位置下重复文件${file_number}个,重复目录${folder_number}个,是否删除?`,
choices: [
{ title: 'Yes', description: '确认删除', value: 'yes' },
{ title: 'No', description: '先不删除', value: 'no' }
@ -637,7 +668,14 @@ async function confirm_dedupe ({ file_number, folder_number }) {
return answer.value
}
// 可以删除文件或文件夹,似乎不会进入回收站
// 将文件或文件夹移入回收站,需要 sa 为 content manager 权限及以上
async function trash_file ({ fid, service_account }) {
const url = `https://www.googleapis.com/drive/v3/files/${fid}?supportsAllDrives=true`
const headers = await gen_headers(service_account)
return axins.patch(url, { trashed: true }, { headers })
}
// 直接删除文件或文件夹,不会进入回收站,需要 sa 为 manager 权限
async function rm_file ({ fid, service_account }) {
const headers = await gen_headers(service_account)
let retry = 0
@ -677,7 +715,7 @@ async function dedupe ({ fid, update, service_account }) {
let file_count = 0
await Promise.all(dupes.map(async v => {
try {
await limit(() => rm_file({ fid: v.id, service_account }))
await limit(() => trash_file({ fid: v.id, service_account }))
if (v.mimeType === FOLDER_TYPE) {
console.log('成功删除文件夹', v.name)
folder_count++
@ -710,4 +748,4 @@ function print_progress (msg) {
}
}
module.exports = { ls_folder, count, validate_fid, copy, dedupe, copy_file, gen_count_body, real_copy }
module.exports = { ls_folder, count, validate_fid, copy, dedupe, copy_file, gen_count_body, real_copy, get_name_by_id }

View File

@ -2,21 +2,25 @@ const Router = require('@koa/router')
const { db } = require('../db')
const { validate_fid, gen_count_body } = require('./gd')
const { send_count, send_help, send_choice, send_task_info, sm, extract_fid, reply_cb_query, tg_copy, send_all_tasks } = require('./tg')
const { send_count, send_help, send_choice, send_task_info, sm, extract_fid, extract_from_text, reply_cb_query, tg_copy, send_all_tasks } = require('./tg')
const { AUTH } = require('../config')
const { AUTH, ROUTER_PASSKEY, TG_IPLIST } = require('../config')
const { tg_whitelist } = AUTH
const counting = {}
const router = new Router()
router.get('/api/gdurl/count', async ctx => {
if (!ROUTER_PASSKEY) return ctx.body = 'gd-utils 成功启动'
const { query, headers } = ctx.request
let { fid, type, update } = query
let { fid, type, update, passkey } = query
if (passkey !== ROUTER_PASSKEY) return ctx.body = 'invalid passkey'
if (!validate_fid(fid)) throw new Error('无效的分享ID')
let ua = headers['user-agent'] || ''
ua = ua.toLowerCase()
type = (type || '').toLowerCase()
// todo type=tree
if (!type) {
if (ua.includes('curl')) {
type = 'curl'
@ -38,6 +42,7 @@ router.post('/api/gdurl/tgbot', async ctx => {
const { body } = ctx.request
console.log('ctx.ip', ctx.ip) // 可以只允许tg服务器的ip
console.log('tg message:', body)
if (TG_IPLIST && !TG_IPLIST.includes(ctx.ip)) return ctx.body = 'invalid ip'
ctx.body = '' // 早点释放连接
const message = body.message || body.edited_message
@ -65,23 +70,27 @@ router.post('/api/gdurl/tgbot', async ctx => {
const chat_id = message && message.chat && message.chat.id
const text = message && message.text && message.text.trim()
const username = message && message.from && message.from.username
if (!chat_id || !text || !tg_whitelist.includes(username)) return console.warn('异常请求')
let username = message && message.from && message.from.username
username = username && String(username).toLowerCase()
let user_id = message && message.from && message.from.id
user_id = user_id && String(user_id).toLowerCase()
if (!chat_id || !text || !tg_whitelist.some(v => {
v = String(v).toLowerCase()
return v === username || v === user_id
})) return console.warn('异常请求')
const fid = extract_fid(text)
const fid = extract_fid(text) || extract_from_text(text)
const no_fid_commands = ['/task', '/help']
if (!no_fid_commands.some(cmd => text.startsWith(cmd)) && !validate_fid(fid)) {
return sm({ chat_id, text: '未识别出分享ID' })
}
if (text.startsWith('/help')) return send_help(chat_id)
if (text.startsWith('https://drive.google.com/')) {
return send_choice({ fid, chat_id }).catch(console.error)
}
if (text.startsWith('/count')) {
if (counting[fid]) return sm({ chat_id, text: fid + ' 正在统计,请稍等片刻' })
try {
counting[fid] = true
await send_count({ fid, chat_id })
const update = text.endsWith(' -u')
await send_count({ fid, chat_id, update })
} catch (err) {
console.error(err)
sm({ chat_id, text: fid + ' 统计失败:' + err.message })
@ -89,9 +98,10 @@ router.post('/api/gdurl/tgbot', async ctx => {
delete counting[fid]
}
} else if (text.startsWith('/copy')) {
const target = text.replace('/copy', '').trim().split(' ').map(v => v.trim())[1]
const target = text.replace('/copy', '').replace(' -u', '').trim().split(' ').map(v => v.trim())[1]
if (target && !validate_fid(target)) return sm({ chat_id, text: `目标ID ${target} 格式不正确` })
tg_copy({ fid, target, chat_id }).then(task_id => {
const update = text.endsWith(' -u')
tg_copy({ fid, target, chat_id, update }).then(task_id => {
task_id && sm({ chat_id, text: `开始复制任务ID: ${task_id} 可输入 /task ${task_id} 查询进度` })
})
} else if (text.startsWith('/task')) {
@ -106,6 +116,8 @@ router.post('/api/gdurl/tgbot', async ctx => {
return running_tasks.forEach(v => send_task_info({ chat_id, task_id: v.id }).catch(console.error))
}
send_task_info({ task_id, chat_id }).catch(console.error)
} else if (text.includes('drive.google.com/') || validate_fid(text)) {
return send_choice({ fid: fid || text, chat_id }).catch(console.error)
} else {
sm({ chat_id, text: '暂不支持此命令' })
}

129
src/tg.js
View File

@ -4,15 +4,25 @@ const axios = require('@viegg/axios')
const HttpsProxyAgent = require('https-proxy-agent')
const { db } = require('../db')
const { gen_count_body, validate_fid, real_copy } = require('./gd')
const { gen_count_body, validate_fid, real_copy, get_name_by_id } = require('./gd')
const { AUTH, DEFAULT_TARGET } = require('../config')
const { tg_token } = AUTH
const gen_link = (fid, text) => `<a href="https://drive.google.com/drive/folders/${fid}">${text || fid}</a>`
if (!tg_token) throw new Error('请先在auth.js里设置tg_token')
const { https_proxy } = process.env
const axins = axios.create(https_proxy ? { httpsAgent: new HttpsProxyAgent(https_proxy) } : {})
module.exports = { send_count, send_help, sm, extract_fid, reply_cb_query, send_choice, send_task_info, send_all_tasks, tg_copy }
const FID_TO_NAME = {}
async function get_folder_name (fid) {
let name = FID_TO_NAME[fid]
if (name) return name
name = await get_name_by_id(fid)
return FID_TO_NAME[fid] = name
}
module.exports = { send_count, send_help, sm, extract_fid, reply_cb_query, send_choice, send_task_info, send_all_tasks, tg_copy, extract_from_text }
function send_help (chat_id) {
const text = `<pre>[使用帮助]
@ -20,9 +30,9 @@ function send_help (chat_id) {
/help | 使
/count shareID | sourceID, sourceIDgoogle driveID
/count shareID [-u] | sourceID, sourceIDgoogle driveID -u线
/copy sourceID targetID | sourceIDtargetIDtargetIDconfig.jstaskID
/copy sourceID targetID [-u] | sourceIDtargetIDtargetIDconfig.js -u线taskID
/task taskID | all
</pre>`
@ -60,9 +70,10 @@ async function send_all_tasks (chat_id) {
chat_id,
parse_mode: 'HTML',
text: `所有拷贝任务:\n<pre>${text}</pre>`
}).catch(async err => {
const description = err.response && err.response.data && err.response.data.description
if (description && description.includes('message is too long')) {
}).catch(err => {
// const description = err.response && err.response.data && err.response.data.description
// if (description && description.includes('message is too long')) {
if (true) {
const text = [headers].concat(records).map(v => v.join('\t')).join('\n')
return sm({ chat_id, parse_mode: 'HTML', text: `所有拷贝任务:\n<pre>${text}</pre>` })
}
@ -70,29 +81,53 @@ async function send_all_tasks (chat_id) {
})
}
async function send_task_info ({ task_id, chat_id }) {
async function get_task_info (task_id) {
const record = db.prepare('select * from task where id=?').get(task_id)
if (!record) return sm({ chat_id, text: '数据库不存在此任务ID' + task_id })
const gen_link = fid => `<a href="https://drive.google.com/drive/folders/${fid}">${fid}</a>`
if (!record) return {}
const { source, target, status, copied, mapping, ctime, ftime } = record
const folder_mapping = mapping && mapping.trim().split('\n')
const new_folder = folder_mapping && folder_mapping[0].split(' ')[1]
const { summary } = db.prepare('select summary from gd where fid=?').get(source) || {}
const { file_count, folder_count, total_size } = summary ? JSON.parse(summary) : {}
const copied_files = copied ? copied.trim().split('\n').length : 0
const copied_folders = mapping ? (mapping.trim().split('\n').length - 1) : 0
const copied_folders = folder_mapping ? (folder_mapping.length - 1) : 0
let text = '任务编号:' + task_id + '\n'
text += '源ID' + gen_link(source) + '\n'
text += '目的ID' + gen_link(target) + '\n'
const folder_name = await get_folder_name(source)
text += '源文件夹:' + gen_link(source, folder_name) + '\n'
text += '目的位置:' + gen_link(target) + '\n'
text += '新文件夹:' + (new_folder ? gen_link(new_folder) : '暂未创建') + '\n'
text += '任务状态:' + status + '\n'
text += '创建时间:' + dayjs(ctime).format('YYYY-MM-DD HH:mm:ss') + '\n'
text += '完成时间:' + (ftime ? dayjs(ftime).format('YYYY-MM-DD HH:mm:ss') : '未完成') + '\n'
text += '目录进度:' + copied_folders + '/' + (folder_count === undefined ? '未知数量' : folder_count) + '\n'
text += '文件进度:' + copied_files + '/' + (file_count === undefined ? '未知数量' : file_count) + '\n'
text += '总大小:' + (total_size || '未知大小')
return sm({ chat_id, text, parse_mode: 'HTML' })
text += '合计大小:' + (total_size || '未知大小')
const total_count = (folder_count || 0) + (file_count || 0)
return { text, status, total_count }
}
async function tg_copy ({ fid, target, chat_id }) { // return task_id
async function send_task_info ({ task_id, chat_id }) {
const { text, status, total_count } = await get_task_info(task_id)
if (!text) return sm({ chat_id, text: '数据库不存在此任务ID' + task_id })
const url = `https://api.telegram.org/bot${tg_token}/sendMessage`
let message_id
try {
const { data } = await axins.post(url, { chat_id, text, parse_mode: 'HTML' })
message_id = data && data.result && data.result.message_id
} catch (e) {
console.log('fail to send message to tg', e.message)
}
// get_task_info 在task文件数超大时比较吃cpu如果超5万就不每10秒更新了
if (!message_id || status !== 'copying' || total_count > 50000) return
const loop = setInterval(async () => {
const url = `https://api.telegram.org/bot${tg_token}/editMessageText`
const { text, status } = await get_task_info(task_id)
if (status !== 'copying') clearInterval(loop)
axins.post(url, { chat_id, message_id, text, parse_mode: 'HTML' }).catch(e => console.error(e.message))
}, 10 * 1000)
}
async function tg_copy ({ fid, target, chat_id, update }) { // return task_id
target = target || DEFAULT_TARGET
if (!target) {
sm({ chat_id, text: '请输入目的地ID或先在config.js里设置默认复制目的地ID(DEFAULT_TARGET)' })
@ -105,17 +140,28 @@ async function tg_copy ({ fid, target, chat_id }) { // return task_id
sm({ chat_id, text: '已有相同源ID和目的ID的任务正在进行查询进度可输入 /task ' + record.id })
return
} else if (record.status === 'finished') {
sm({ chat_id, text: '有相同源ID和目的ID的任务已复制完成如需重新复制请更换目的地' })
return
sm({ chat_id, text: `检测到已存在的任务 ${record.id},开始继续拷贝` })
}
}
real_copy({ source: fid, target, not_teamdrive: true, service_account: true, is_server: true })
.then(folder => {
real_copy({ source: fid, update, target, not_teamdrive: true, service_account: true, is_server: true })
.then(async info => {
if (!record) record = {} // 防止无限循环
if (!folder) return
const link = 'https://drive.google.com/drive/folders/' + folder.id
sm({ chat_id, text: `${fid} 复制完成,新文件夹链接:${link}` })
if (!info) return
const { task_id } = info
const row = db.prepare('select * from task where id=?').get(task_id)
const { source, target, status, copied, mapping, ctime, ftime } = row
const { summary } = db.prepare('select summary from gd where fid=?').get(source) || {}
const { file_count, folder_count, total_size } = summary ? JSON.parse(summary) : {}
const copied_files = copied ? copied.trim().split('\n').length : 0
const copied_folders = mapping ? (mapping.trim().split('\n').length - 1) : 0
let text = `任务 ${task_id} 复制完成\n`
const name = await get_folder_name(source)
text += '源文件夹:' + gen_link(source, name) + '\n'
text += '目录完成数:' + copied_folders + '/' + folder_count + '\n'
text += '文件完成数:' + copied_files + '/' + file_count + '\n'
sm({ chat_id, text, parse_mode: 'HTML' })
})
.catch(err => {
if (!record) record = {}
@ -145,26 +191,31 @@ function reply_cb_query ({ id, data }) {
})
}
async function send_count ({ fid, chat_id }) {
const table = await gen_count_body({ fid, type: 'tg', service_account: true })
async function send_count ({ fid, chat_id, update }) {
const table = await gen_count_body({ fid, update, type: 'tg', service_account: true })
if (!table) return sm({ chat_id, parse_mode: 'HTML', text: gen_link(fid) + ' 信息获取失败' })
const url = `https://api.telegram.org/bot${tg_token}/sendMessage`
const gd_link = `https://drive.google.com/drive/folders/${fid}`
const name = await get_folder_name(fid)
return axins.post(url, {
chat_id,
parse_mode: 'HTML',
// todo 输出文件名
text: `<pre>${gd_link}
text: `<pre>源文件夹名称:${name}
源链接${gd_link}
${table}</pre>`
}).catch(async err => {
const description = err.response && err.response.data && err.response.data.description
if (description && description.includes('message is too long')) {
// const description = err.response && err.response.data && err.response.data.description
// const too_long_msgs = ['request entity too large', 'message is too long']
// if (description && too_long_msgs.some(v => description.toLowerCase().includes(v))) {
if (true) {
const smy = await gen_count_body({ fid, type: 'json', service_account: true })
const { file_count, folder_count, total_size } = JSON.parse(smy)
return sm({
chat_id,
parse_mode: 'HTML',
text: `文件统计<a href="https://drive.google.com/drive/folders/${fid}">${fid}</a>\n<pre>
text: `链接<a href="https://drive.google.com/drive/folders/${fid}">${fid}</a>\n<pre>
表格太长超出telegram消息限制只显示概要
目录名称${name}
文件总数${file_count}
目录总数${folder_count}
合计大小${total_size}
@ -178,25 +229,31 @@ ${table}</pre>`
function sm (data) {
const url = `https://api.telegram.org/bot${tg_token}/sendMessage`
return axins.post(url, data).catch(err => {
console.error('fail to post', url, data)
console.error(err)
// console.error('fail to post', url, data)
console.error('fail to send message to tg:', err.message)
})
}
function extract_fid (text) {
text = text.replace(/^\/count/, '').replace(/^\/copy/, '').trim()
text = text.replace(/^\/count/, '').replace(/^\/copy/, '').replace(/\\/g, '').trim()
const [source, target] = text.split(' ').map(v => v.trim())
if (validate_fid(source)) return source
try {
if (!text.startsWith('http')) text = 'https://' + text
const u = new URL(text)
if (u.pathname.includes('/folders/')) {
const reg = /\/folders\/([a-zA-Z0-9_-]{10,100})/
const reg = /[^\/?]+$/
const match = u.pathname.match(reg)
return match && match[1]
return match && match[0]
}
return u.searchParams.get('id')
} catch (e) {
return ''
}
}
function extract_from_text (text) {
const reg = /https?:\/\/drive.google.com\/[^\s]+/g
const m = text.match(reg)
return m && extract_fid(m[0])
}