update tg.js gd.js by @iwestlin
This commit is contained in:
parent
f73cc04880
commit
ba07307030
150
src/gd.js
150
src/gd.js
|
@ -12,19 +12,28 @@ const { AUTH, RETRY_LIMIT, PARALLEL_LIMIT, TIMEOUT_BASE, TIMEOUT_MAX, LOG_DELAY,
|
||||||
const { db } = require('../db')
|
const { db } = require('../db')
|
||||||
const { make_table, make_tg_table, make_html, summary } = require('./summary')
|
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 FOLDER_TYPE = 'application/vnd.google-apps.folder'
|
||||||
const { https_proxy } = process.env
|
const { https_proxy } = process.env
|
||||||
const axins = axios.create(https_proxy ? { httpsAgent: new HttpsProxyAgent(https_proxy) } : {})
|
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'))
|
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 => {
|
function get_sa_batch () {
|
||||||
const gtoken = new GoogleToken({
|
const new_flag = SA_FILES.flag + SA_BATCH_SIZE
|
||||||
keyFile: path.join(__dirname, '../sa', filename),
|
const files = SA_FILES.slice(SA_FILES.flag, new_flag)
|
||||||
scope: ['https://www.googleapis.com/auth/drive']
|
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(() => {
|
handle_exit(() => {
|
||||||
// console.log('handle_exit running')
|
// console.log('handle_exit running')
|
||||||
|
@ -38,11 +47,13 @@ handle_exit(() => {
|
||||||
async function gen_count_body ({ fid, type, update, service_account }) {
|
async function gen_count_body ({ fid, type, update, service_account }) {
|
||||||
async function update_info () {
|
async function update_info () {
|
||||||
const info = await walk_and_save({ fid, update, service_account }) // 这一步已经将fid记录存入数据库中了
|
const info = await walk_and_save({ fid, update, service_account }) // 这一步已经将fid记录存入数据库中了
|
||||||
const { summary } = db.prepare('SELECT summary from gd WHERE fid=?').get(fid)
|
const row = db.prepare('SELECT summary from gd WHERE fid=?').get(fid)
|
||||||
return [info, JSON.parse(summary)]
|
if (!row) return []
|
||||||
|
return [info, JSON.parse(row.summary)]
|
||||||
}
|
}
|
||||||
|
|
||||||
function render_smy (smy, type) {
|
function render_smy (smy, type) {
|
||||||
|
if (!smy) return
|
||||||
if (['html', 'curl', 'tg'].includes(type)) {
|
if (['html', 'curl', 'tg'].includes(type)) {
|
||||||
smy = (typeof smy === 'object') ? smy : JSON.parse(smy)
|
smy = (typeof smy === 'object') ? smy : JSON.parse(smy)
|
||||||
const type_func = {
|
const type_func = {
|
||||||
|
@ -66,7 +77,7 @@ async function gen_count_body ({ fid, type, update, service_account }) {
|
||||||
if (!info) { // 说明上次统计过程中断了
|
if (!info) { // 说明上次统计过程中断了
|
||||||
[info] = await update_info()
|
[info] = await update_info()
|
||||||
}
|
}
|
||||||
return JSON.stringify(info)
|
return info && JSON.stringify(info)
|
||||||
}
|
}
|
||||||
if (smy) return render_smy(smy, type)
|
if (smy) return render_smy(smy, type)
|
||||||
if (record && record.summary) return render_smy(record.summary, 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 loop = setInterval(() => {
|
||||||
const now = dayjs().format('HH:mm:ss')
|
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)
|
print_progress(message)
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
|
@ -248,7 +259,7 @@ async function ls_folder ({ fid, not_teamdrive, service_account }) {
|
||||||
}
|
}
|
||||||
|
|
||||||
async function gen_headers (use_sa) {
|
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())
|
const access_token = use_sa ? (await get_sa_token()).access_token : (await get_access_token())
|
||||||
return { authorization: 'Bearer ' + access_token }
|
return { authorization: 'Bearer ' + access_token }
|
||||||
}
|
}
|
||||||
|
@ -276,15 +287,17 @@ async function get_access_token () {
|
||||||
return data.access_token
|
return data.access_token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// get_sa_token().catch(console.error)
|
||||||
async function get_sa_token () {
|
async function get_sa_token () {
|
||||||
let tk
|
if (!SA_TOKENS.length) SA_TOKENS = get_sa_batch()
|
||||||
while (SA_TOKENS.length) {
|
while (SA_TOKENS.length) {
|
||||||
tk = get_random_element(SA_TOKENS)
|
const tk = get_random_element(SA_TOKENS)
|
||||||
try {
|
try {
|
||||||
return await real_get_sa_token(tk)
|
return await real_get_sa_token(tk)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
SA_TOKENS = SA_TOKENS.filter(v => v.gtoken !== tk.gtoken)
|
SA_TOKENS = SA_TOKENS.filter(v => v.gtoken !== tk.gtoken)
|
||||||
|
if (!SA_TOKENS.length) SA_TOKENS = get_sa_batch()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new Error('没有可用的SA帐号')
|
throw new Error('没有可用的SA帐号')
|
||||||
|
@ -302,7 +315,7 @@ function real_get_sa_token (el) {
|
||||||
// console.log('got sa token', tokens)
|
// console.log('got sa token', tokens)
|
||||||
const { access_token, expires_in } = tokens
|
const { access_token, expires_in } = tokens
|
||||||
el.value = access_token
|
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 })
|
resolve({ access_token, gtoken })
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -323,7 +336,7 @@ function validate_fid (fid) {
|
||||||
return fid.match(reg)
|
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`
|
let url = `https://www.googleapis.com/drive/v3/files`
|
||||||
const params = { supportsAllDrives: true }
|
const params = { supportsAllDrives: true }
|
||||||
url += '?' + params_to_query(params)
|
url += '?' + params_to_query(params)
|
||||||
|
@ -333,18 +346,34 @@ async function create_folder (name, parent, use_sa) {
|
||||||
parents: [parent]
|
parents: [parent]
|
||||||
}
|
}
|
||||||
let retry = 0
|
let retry = 0
|
||||||
let data
|
let err_message
|
||||||
while (!data && (retry < RETRY_LIMIT)) {
|
while (retry < RETRY_LIMIT) {
|
||||||
try {
|
try {
|
||||||
const headers = await gen_headers(use_sa)
|
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) {
|
} catch (err) {
|
||||||
|
err_message = err.message
|
||||||
retry++
|
retry++
|
||||||
handle_error(err)
|
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)
|
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) {
|
async function get_info_by_id (fid, use_sa) {
|
||||||
|
@ -353,7 +382,7 @@ async function get_info_by_id (fid, use_sa) {
|
||||||
includeItemsFromAllDrives: true,
|
includeItemsFromAllDrives: true,
|
||||||
supportsAllDrives: true,
|
supportsAllDrives: true,
|
||||||
corpora: 'allDrives',
|
corpora: 'allDrives',
|
||||||
fields: 'id,name,owners'
|
fields: 'id,name'
|
||||||
}
|
}
|
||||||
url += '?' + params_to_query(params)
|
url += '?' + params_to_query(params)
|
||||||
const headers = await gen_headers(use_sa)
|
const headers = await gen_headers(use_sa)
|
||||||
|
@ -376,7 +405,7 @@ async function user_choose () {
|
||||||
return answer.value
|
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
|
target = target || DEFAULT_TARGET
|
||||||
if (!target) throw new Error('目标位置不能为空')
|
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('已有相同源和目的地的任务正在运行,强制退出')
|
if (record && record.status === 'copying') return console.log('已有相同源和目的地的任务正在运行,强制退出')
|
||||||
|
|
||||||
try {
|
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) {
|
} catch (err) {
|
||||||
console.error('复制文件夹出错', err)
|
console.error('复制文件夹出错', err)
|
||||||
const record = db.prepare('select id, status from task where source=? and target=?').get(source, target)
|
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中断进程,那么已经发出的请求,就算完成了也不会记录到本地数据库中,所以可能产生重复文件(夹)
|
// 待解决:如果用户手动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 () {
|
async function get_new_root () {
|
||||||
|
if (dncnr) return { id: target }
|
||||||
if (name) {
|
if (name) {
|
||||||
return create_folder(name, target, service_account)
|
return create_folder(name, target, service_account)
|
||||||
} else {
|
} else {
|
||||||
|
@ -432,12 +462,11 @@ async function real_copy ({ source, target, name, min_size, update, not_teamdriv
|
||||||
root,
|
root,
|
||||||
task_id: record.id
|
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)
|
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') {
|
} else if (choice === 'restart') {
|
||||||
const new_root = await get_new_root()
|
const new_root = await get_new_root()
|
||||||
if (!new_root) throw new Error('创建目录失败,请检查您的帐号是否有相应的权限')
|
|
||||||
const root_mapping = source + ' ' + new_root.id + '\n'
|
const root_mapping = source + ' ' + new_root.id + '\n'
|
||||||
db.prepare('update task set status=?, copied=?, mapping=? where id=?')
|
db.prepare('update task set status=?, copied=?, mapping=? where id=?')
|
||||||
.run('copying', '', root_mapping, record.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,
|
root: new_root.id,
|
||||||
task_id: record.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)
|
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 {
|
} else {
|
||||||
// ctrl+c 退出
|
// ctrl+c 退出
|
||||||
return console.log('退出程序')
|
return console.log('退出程序')
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const new_root = await get_new_root()
|
const new_root = await get_new_root()
|
||||||
if (!new_root) throw new Error('创建目录失败,请检查您的帐号是否有相应的权限')
|
|
||||||
const root_mapping = source + ' ' + new_root.id + '\n'
|
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 { 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 })
|
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,
|
root: new_root.id,
|
||||||
task_id: lastInsertRowid
|
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)
|
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)
|
console.log('\n开始复制文件,总数:', files.length)
|
||||||
const limit = pLimit(PARALLEL_LIMIT)
|
const limit = pLimit(PARALLEL_LIMIT)
|
||||||
let count = 0
|
let count = 0
|
||||||
const loop = setInterval(() => {
|
const loop = setInterval(() => {
|
||||||
const now = dayjs().format('HH:mm:ss')
|
const now = dayjs().format('HH:mm:ss')
|
||||||
const message = `${now} | 已复制文件数 ${count} | 排队等候的网络请求 ${limit.pendingCount}`
|
const message = `${now} | 已复制文件数 ${count} | 网络请求 进行中${limit.activeCount}/排队${limit.pendingCount}`
|
||||||
print_progress(message)
|
print_progress(message)
|
||||||
}, 1000)
|
}, 1000)
|
||||||
await Promise.all(files.map(async file => {
|
return Promise.all(files.map(async file => {
|
||||||
try {
|
const { id, parent } = file
|
||||||
const { id, parent } = file
|
const target = mapping[parent] || root
|
||||||
const target = mapping[parent] || root
|
const new_file = await limit(() => copy_file(id, target, service_account, limit))
|
||||||
const new_file = await limit(() => copy_file(id, target))
|
if (new_file) {
|
||||||
if (new_file) {
|
|
||||||
db.prepare('update task set status=?, copied = copied || ? where id=?')
|
|
||||||
.run('copying', id + '\n', task_id)
|
|
||||||
}
|
|
||||||
count++
|
count++
|
||||||
} catch (e) {
|
db.prepare('update task set status=?, copied = copied || ? where id=?').run('copying', id + '\n', task_id)
|
||||||
console.error(e)
|
|
||||||
}
|
}
|
||||||
}))
|
})).finally(() => clearInterval(loop))
|
||||||
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 url = `https://www.googleapis.com/drive/v3/files/${id}/copy`
|
||||||
let params = { supportsAllDrives: true }
|
let params = { supportsAllDrives: true }
|
||||||
url += '?' + params_to_query(params)
|
url += '?' + params_to_query(params)
|
||||||
|
@ -519,7 +541,7 @@ async function copy_file (id, parent) {
|
||||||
let retry = 0
|
let retry = 0
|
||||||
while (retry < RETRY_LIMIT) {
|
while (retry < RETRY_LIMIT) {
|
||||||
let gtoken
|
let gtoken
|
||||||
if (SA_TOKENS.length) { // 如果有sa文件则优先使用
|
if (use_sa) {
|
||||||
const temp = await get_sa_token()
|
const temp = await get_sa_token()
|
||||||
gtoken = temp.gtoken
|
gtoken = temp.gtoken
|
||||||
config.headers = { authorization: 'Bearer ' + temp.access_token }
|
config.headers = { authorization: 'Bearer ' + temp.access_token }
|
||||||
|
@ -534,13 +556,19 @@ async function copy_file (id, parent) {
|
||||||
handle_error(err)
|
handle_error(err)
|
||||||
const data = err && err.response && err.response.data
|
const data = err && err.response && err.response.data
|
||||||
const message = data && data.error && data.error.message
|
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')) {
|
if (message && message.toLowerCase().includes('rate limit')) {
|
||||||
SA_TOKENS = SA_TOKENS.filter(v => v.gtoken !== gtoken)
|
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)
|
console.log('此帐号触发使用限额,剩余可用service account帐号数量:', SA_TOKENS.length)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!SA_TOKENS.length) {
|
if (!SA_TOKENS.length) {
|
||||||
|
if (limit) limit.clearQueue()
|
||||||
throw new Error('所有SA帐号流量已用完')
|
throw new Error('所有SA帐号流量已用完')
|
||||||
} else {
|
} else {
|
||||||
console.warn('复制文件失败,文件id: ' + id)
|
console.warn('复制文件失败,文件id: ' + id)
|
||||||
|
@ -560,7 +588,7 @@ async function create_folders ({ source, old_mapping, folders, root, task_id, se
|
||||||
|
|
||||||
const loop = setInterval(() => {
|
const loop = setInterval(() => {
|
||||||
const now = dayjs().format('HH:mm:ss')
|
const now = dayjs().format('HH:mm:ss')
|
||||||
const message = `${now} | 已创建目录数 ${count} | 排队等候的网络请求 ${limit.pendingCount}`
|
const message = `${now} | 已创建目录数 ${count} | 网络请求 进行中${limit.activeCount}/排队${limit.pendingCount}`
|
||||||
print_progress(message)
|
print_progress(message)
|
||||||
}, 1000)
|
}, 1000)
|
||||||
|
|
||||||
|
@ -569,14 +597,17 @@ async function create_folders ({ source, old_mapping, folders, root, task_id, se
|
||||||
try {
|
try {
|
||||||
const { name, id, parent } = v
|
const { name, id, parent } = v
|
||||||
const target = mapping[parent] || root
|
const target = mapping[parent] || root
|
||||||
const new_folder = await limit(() => create_folder(name, target, service_account))
|
const new_folder = await limit(() => create_folder(name, target, service_account, limit))
|
||||||
if (!new_folder) throw new Error(name + '创建失败')
|
|
||||||
count++
|
count++
|
||||||
mapping[id] = new_folder.id
|
mapping[id] = new_folder.id
|
||||||
const mapping_record = id + ' ' + new_folder.id + '\n'
|
const mapping_record = id + ' ' + new_folder.id + '\n'
|
||||||
db.prepare('update task set status=?, mapping = mapping || ? where id=?').run('copying', mapping_record, task_id)
|
db.prepare('update task set status=?, mapping = mapping || ? where id=?').run('copying', mapping_record, task_id)
|
||||||
} catch (e) {
|
} 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])
|
folders = folders.filter(v => !mapping[v.id])
|
||||||
|
@ -627,7 +658,7 @@ async function confirm_dedupe ({ file_number, folder_number }) {
|
||||||
const answer = await prompts({
|
const answer = await prompts({
|
||||||
type: 'select',
|
type: 'select',
|
||||||
name: 'value',
|
name: 'value',
|
||||||
message: `检测到重复文件${file_number}个,重复目录${folder_number}个,是否删除?`,
|
message: `检测到同位置下重复文件${file_number}个,重复空目录${folder_number}个,是否删除?`,
|
||||||
choices: [
|
choices: [
|
||||||
{ title: 'Yes', description: '确认删除', value: 'yes' },
|
{ title: 'Yes', description: '确认删除', value: 'yes' },
|
||||||
{ title: 'No', description: '先不删除', value: 'no' }
|
{ title: 'No', description: '先不删除', value: 'no' }
|
||||||
|
@ -637,7 +668,14 @@ async function confirm_dedupe ({ file_number, folder_number }) {
|
||||||
return answer.value
|
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 }) {
|
async function rm_file ({ fid, service_account }) {
|
||||||
const headers = await gen_headers(service_account)
|
const headers = await gen_headers(service_account)
|
||||||
let retry = 0
|
let retry = 0
|
||||||
|
@ -677,7 +715,7 @@ async function dedupe ({ fid, update, service_account }) {
|
||||||
let file_count = 0
|
let file_count = 0
|
||||||
await Promise.all(dupes.map(async v => {
|
await Promise.all(dupes.map(async v => {
|
||||||
try {
|
try {
|
||||||
await limit(() => rm_file({ fid: v.id, service_account }))
|
await limit(() => trash_file({ fid: v.id, service_account }))
|
||||||
if (v.mimeType === FOLDER_TYPE) {
|
if (v.mimeType === FOLDER_TYPE) {
|
||||||
console.log('成功删除文件夹', v.name)
|
console.log('成功删除文件夹', v.name)
|
||||||
folder_count++
|
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 }
|
||||||
|
|
|
@ -2,21 +2,25 @@ const Router = require('@koa/router')
|
||||||
|
|
||||||
const { db } = require('../db')
|
const { db } = require('../db')
|
||||||
const { validate_fid, gen_count_body } = require('./gd')
|
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 { tg_whitelist } = AUTH
|
||||||
|
|
||||||
const counting = {}
|
const counting = {}
|
||||||
const router = new Router()
|
const router = new Router()
|
||||||
|
|
||||||
router.get('/api/gdurl/count', async ctx => {
|
router.get('/api/gdurl/count', async ctx => {
|
||||||
|
if (!ROUTER_PASSKEY) return ctx.body = 'gd-utils 成功启动'
|
||||||
const { query, headers } = ctx.request
|
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')
|
if (!validate_fid(fid)) throw new Error('无效的分享ID')
|
||||||
|
|
||||||
let ua = headers['user-agent'] || ''
|
let ua = headers['user-agent'] || ''
|
||||||
ua = ua.toLowerCase()
|
ua = ua.toLowerCase()
|
||||||
type = (type || '').toLowerCase()
|
type = (type || '').toLowerCase()
|
||||||
|
// todo type=tree
|
||||||
if (!type) {
|
if (!type) {
|
||||||
if (ua.includes('curl')) {
|
if (ua.includes('curl')) {
|
||||||
type = 'curl'
|
type = 'curl'
|
||||||
|
@ -38,6 +42,7 @@ router.post('/api/gdurl/tgbot', async ctx => {
|
||||||
const { body } = ctx.request
|
const { body } = ctx.request
|
||||||
console.log('ctx.ip', ctx.ip) // 可以只允许tg服务器的ip
|
console.log('ctx.ip', ctx.ip) // 可以只允许tg服务器的ip
|
||||||
console.log('tg message:', body)
|
console.log('tg message:', body)
|
||||||
|
if (TG_IPLIST && !TG_IPLIST.includes(ctx.ip)) return ctx.body = 'invalid ip'
|
||||||
ctx.body = '' // 早点释放连接
|
ctx.body = '' // 早点释放连接
|
||||||
const message = body.message || body.edited_message
|
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 chat_id = message && message.chat && message.chat.id
|
||||||
const text = message && message.text && message.text.trim()
|
const text = message && message.text && message.text.trim()
|
||||||
const username = message && message.from && message.from.username
|
let username = message && message.from && message.from.username
|
||||||
if (!chat_id || !text || !tg_whitelist.includes(username)) return console.warn('异常请求')
|
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']
|
const no_fid_commands = ['/task', '/help']
|
||||||
if (!no_fid_commands.some(cmd => text.startsWith(cmd)) && !validate_fid(fid)) {
|
if (!no_fid_commands.some(cmd => text.startsWith(cmd)) && !validate_fid(fid)) {
|
||||||
return sm({ chat_id, text: '未识别出分享ID' })
|
return sm({ chat_id, text: '未识别出分享ID' })
|
||||||
}
|
}
|
||||||
if (text.startsWith('/help')) return send_help(chat_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 (text.startsWith('/count')) {
|
||||||
if (counting[fid]) return sm({ chat_id, text: fid + ' 正在统计,请稍等片刻' })
|
if (counting[fid]) return sm({ chat_id, text: fid + ' 正在统计,请稍等片刻' })
|
||||||
try {
|
try {
|
||||||
counting[fid] = true
|
counting[fid] = true
|
||||||
await send_count({ fid, chat_id })
|
const update = text.endsWith(' -u')
|
||||||
|
await send_count({ fid, chat_id, update })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err)
|
||||||
sm({ chat_id, text: fid + ' 统计失败:' + err.message })
|
sm({ chat_id, text: fid + ' 统计失败:' + err.message })
|
||||||
|
@ -89,9 +98,10 @@ router.post('/api/gdurl/tgbot', async ctx => {
|
||||||
delete counting[fid]
|
delete counting[fid]
|
||||||
}
|
}
|
||||||
} else if (text.startsWith('/copy')) {
|
} 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} 格式不正确` })
|
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} 查询进度` })
|
task_id && sm({ chat_id, text: `开始复制,任务ID: ${task_id} 可输入 /task ${task_id} 查询进度` })
|
||||||
})
|
})
|
||||||
} else if (text.startsWith('/task')) {
|
} 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))
|
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)
|
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 {
|
} else {
|
||||||
sm({ chat_id, text: '暂不支持此命令' })
|
sm({ chat_id, text: '暂不支持此命令' })
|
||||||
}
|
}
|
||||||
|
|
129
src/tg.js
129
src/tg.js
|
@ -4,15 +4,25 @@ const axios = require('@viegg/axios')
|
||||||
const HttpsProxyAgent = require('https-proxy-agent')
|
const HttpsProxyAgent = require('https-proxy-agent')
|
||||||
|
|
||||||
const { db } = require('../db')
|
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 { AUTH, DEFAULT_TARGET } = require('../config')
|
||||||
const { tg_token } = AUTH
|
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')
|
if (!tg_token) throw new Error('请先在auth.js里设置tg_token')
|
||||||
const { https_proxy } = process.env
|
const { https_proxy } = process.env
|
||||||
const axins = axios.create(https_proxy ? { httpsAgent: new HttpsProxyAgent(https_proxy) } : {})
|
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) {
|
function send_help (chat_id) {
|
||||||
const text = `<pre>[使用帮助]
|
const text = `<pre>[使用帮助]
|
||||||
|
@ -20,9 +30,9 @@ function send_help (chat_id) {
|
||||||
|
|
||||||
/help | 返回本条使用说明
|
/help | 返回本条使用说明
|
||||||
|
|
||||||
/count shareID | 返回sourceID的文件统计信息, sourceID可以是google drive分享网址本身,也可以是分享ID
|
/count shareID [-u] | 返回sourceID的文件统计信息, sourceID可以是google drive分享网址本身,也可以是分享ID。如果命令最后加上 -u,则无视之前的记录强制从线上获取,适合一段时候后才更新完毕的分享链接。
|
||||||
|
|
||||||
/copy sourceID targetID | 将sourceID的文件复制到targetID里(会新建一个文件夹),若不填targetID,则会复制到默认位置(在config.js里设置)。返回拷贝任务的taskID
|
/copy sourceID targetID [-u] | 将sourceID的文件复制到targetID里(会新建一个文件夹),若不填targetID,则会复制到默认位置(在config.js里设置)。如果命令最后加上 -u,则无视本地缓存强制从线上获取源文件夹信息。返回拷贝任务的taskID
|
||||||
|
|
||||||
/task taskID | 返回对应任务的进度信息,若不填则返回所有正在运行的任务进度,若填 all 则返回所有任务列表
|
/task taskID | 返回对应任务的进度信息,若不填则返回所有正在运行的任务进度,若填 all 则返回所有任务列表
|
||||||
</pre>`
|
</pre>`
|
||||||
|
@ -60,9 +70,10 @@ async function send_all_tasks (chat_id) {
|
||||||
chat_id,
|
chat_id,
|
||||||
parse_mode: 'HTML',
|
parse_mode: 'HTML',
|
||||||
text: `所有拷贝任务:\n<pre>${text}</pre>`
|
text: `所有拷贝任务:\n<pre>${text}</pre>`
|
||||||
}).catch(async err => {
|
}).catch(err => {
|
||||||
const description = err.response && err.response.data && err.response.data.description
|
// const description = err.response && err.response.data && err.response.data.description
|
||||||
if (description && description.includes('message is too long')) {
|
// if (description && description.includes('message is too long')) {
|
||||||
|
if (true) {
|
||||||
const text = [headers].concat(records).map(v => v.join('\t')).join('\n')
|
const text = [headers].concat(records).map(v => v.join('\t')).join('\n')
|
||||||
return sm({ chat_id, parse_mode: 'HTML', text: `所有拷贝任务:\n<pre>${text}</pre>` })
|
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)
|
const record = db.prepare('select * from task where id=?').get(task_id)
|
||||||
if (!record) return sm({ chat_id, text: '数据库不存在此任务ID:' + task_id })
|
if (!record) return {}
|
||||||
|
|
||||||
const gen_link = fid => `<a href="https://drive.google.com/drive/folders/${fid}">${fid}</a>`
|
|
||||||
const { source, target, status, copied, mapping, ctime, ftime } = record
|
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 { summary } = db.prepare('select summary from gd where fid=?').get(source) || {}
|
||||||
const { file_count, folder_count, total_size } = summary ? JSON.parse(summary) : {}
|
const { file_count, folder_count, total_size } = summary ? JSON.parse(summary) : {}
|
||||||
const copied_files = copied ? copied.trim().split('\n').length : 0
|
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'
|
let text = '任务编号:' + task_id + '\n'
|
||||||
text += '源ID:' + gen_link(source) + '\n'
|
const folder_name = await get_folder_name(source)
|
||||||
text += '目的ID:' + gen_link(target) + '\n'
|
text += '源文件夹:' + gen_link(source, folder_name) + '\n'
|
||||||
|
text += '目的位置:' + gen_link(target) + '\n'
|
||||||
|
text += '新文件夹:' + (new_folder ? gen_link(new_folder) : '暂未创建') + '\n'
|
||||||
text += '任务状态:' + status + '\n'
|
text += '任务状态:' + status + '\n'
|
||||||
text += '创建时间:' + dayjs(ctime).format('YYYY-MM-DD HH:mm:ss') + '\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 += '完成时间:' + (ftime ? dayjs(ftime).format('YYYY-MM-DD HH:mm:ss') : '未完成') + '\n'
|
||||||
text += '目录进度:' + copied_folders + '/' + (folder_count === undefined ? '未知数量' : folder_count) + '\n'
|
text += '目录进度:' + copied_folders + '/' + (folder_count === undefined ? '未知数量' : folder_count) + '\n'
|
||||||
text += '文件进度:' + copied_files + '/' + (file_count === undefined ? '未知数量' : file_count) + '\n'
|
text += '文件进度:' + copied_files + '/' + (file_count === undefined ? '未知数量' : file_count) + '\n'
|
||||||
text += '总大小:' + (total_size || '未知大小')
|
text += '合计大小:' + (total_size || '未知大小')
|
||||||
return sm({ chat_id, text, parse_mode: 'HTML' })
|
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
|
target = target || DEFAULT_TARGET
|
||||||
if (!target) {
|
if (!target) {
|
||||||
sm({ chat_id, text: '请输入目的地ID或先在config.js里设置默认复制目的地ID(DEFAULT_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 })
|
sm({ chat_id, text: '已有相同源ID和目的ID的任务正在进行,查询进度可输入 /task ' + record.id })
|
||||||
return
|
return
|
||||||
} else if (record.status === 'finished') {
|
} else if (record.status === 'finished') {
|
||||||
sm({ chat_id, text: '有相同源ID和目的ID的任务已复制完成,如需重新复制请更换目的地' })
|
sm({ chat_id, text: `检测到已存在的任务 ${record.id},开始继续拷贝` })
|
||||||
return
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
real_copy({ source: fid, target, not_teamdrive: true, service_account: true, is_server: true })
|
real_copy({ source: fid, update, target, not_teamdrive: true, service_account: true, is_server: true })
|
||||||
.then(folder => {
|
.then(async info => {
|
||||||
if (!record) record = {} // 防止无限循环
|
if (!record) record = {} // 防止无限循环
|
||||||
if (!folder) return
|
if (!info) return
|
||||||
const link = 'https://drive.google.com/drive/folders/' + folder.id
|
const { task_id } = info
|
||||||
sm({ chat_id, text: `${fid} 复制完成,新文件夹链接:${link}` })
|
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 => {
|
.catch(err => {
|
||||||
if (!record) record = {}
|
if (!record) record = {}
|
||||||
|
@ -145,26 +191,31 @@ function reply_cb_query ({ id, data }) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function send_count ({ fid, chat_id }) {
|
async function send_count ({ fid, chat_id, update }) {
|
||||||
const table = await gen_count_body({ fid, type: 'tg', service_account: true })
|
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 url = `https://api.telegram.org/bot${tg_token}/sendMessage`
|
||||||
const gd_link = `https://drive.google.com/drive/folders/${fid}`
|
const gd_link = `https://drive.google.com/drive/folders/${fid}`
|
||||||
|
const name = await get_folder_name(fid)
|
||||||
return axins.post(url, {
|
return axins.post(url, {
|
||||||
chat_id,
|
chat_id,
|
||||||
parse_mode: 'HTML',
|
parse_mode: 'HTML',
|
||||||
// todo 输出文件名
|
text: `<pre>源文件夹名称:${name}
|
||||||
text: `<pre>${gd_link}
|
源链接:${gd_link}
|
||||||
${table}</pre>`
|
${table}</pre>`
|
||||||
}).catch(async err => {
|
}).catch(async err => {
|
||||||
const description = err.response && err.response.data && err.response.data.description
|
// const description = err.response && err.response.data && err.response.data.description
|
||||||
if (description && description.includes('message is too long')) {
|
// 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 smy = await gen_count_body({ fid, type: 'json', service_account: true })
|
||||||
const { file_count, folder_count, total_size } = JSON.parse(smy)
|
const { file_count, folder_count, total_size } = JSON.parse(smy)
|
||||||
return sm({
|
return sm({
|
||||||
chat_id,
|
chat_id,
|
||||||
parse_mode: 'HTML',
|
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消息限制,只显示概要:
|
表格太长超出telegram消息限制,只显示概要:
|
||||||
|
目录名称:${name}
|
||||||
文件总数:${file_count}
|
文件总数:${file_count}
|
||||||
目录总数:${folder_count}
|
目录总数:${folder_count}
|
||||||
合计大小:${total_size}
|
合计大小:${total_size}
|
||||||
|
@ -178,25 +229,31 @@ ${table}</pre>`
|
||||||
function sm (data) {
|
function sm (data) {
|
||||||
const url = `https://api.telegram.org/bot${tg_token}/sendMessage`
|
const url = `https://api.telegram.org/bot${tg_token}/sendMessage`
|
||||||
return axins.post(url, data).catch(err => {
|
return axins.post(url, data).catch(err => {
|
||||||
console.error('fail to post', url, data)
|
// console.error('fail to post', url, data)
|
||||||
console.error(err)
|
console.error('fail to send message to tg:', err.message)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function extract_fid (text) {
|
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())
|
const [source, target] = text.split(' ').map(v => v.trim())
|
||||||
if (validate_fid(source)) return source
|
if (validate_fid(source)) return source
|
||||||
try {
|
try {
|
||||||
if (!text.startsWith('http')) text = 'https://' + text
|
if (!text.startsWith('http')) text = 'https://' + text
|
||||||
const u = new URL(text)
|
const u = new URL(text)
|
||||||
if (u.pathname.includes('/folders/')) {
|
if (u.pathname.includes('/folders/')) {
|
||||||
const reg = /\/folders\/([a-zA-Z0-9_-]{10,100})/
|
const reg = /[^\/?]+$/
|
||||||
const match = u.pathname.match(reg)
|
const match = u.pathname.match(reg)
|
||||||
return match && match[1]
|
return match && match[0]
|
||||||
}
|
}
|
||||||
return u.searchParams.get('id')
|
return u.searchParams.get('id')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return ''
|
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])
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue