Compare commits

..

81 Commits

Author SHA1 Message Date
vitaminx 8ae95bbb2d
CF反代非SSL版
此版本脚本不需要SSL!不需要SSL!不需要SSL!
2020-07-08 15:02:04 +08:00
vitaminx a30f523f79
@jkliao 改的SSL版 2020-07-08 14:59:40 +08:00
vitaminx c694fc1406
@jkliao 改的SSL版 2020-07-08 14:58:59 +08:00
vitaminx b6f591f696
Merge pull request #7 from liaojack8/master
sa路径
2020-07-07 02:11:48 +08:00
liaojack8 53e6f75f1b update readme 2020-07-07 01:35:03 +08:00
liaojack8 b74425f882 refresh sa_tokens every 12 hours 2020-07-07 01:33:14 +08:00
liaojack8 769093719e update task status to error when tg_copy failed 2020-07-07 01:32:46 +08:00
liaojack8 db3a1101a6 update readme 2020-07-07 01:09:51 +08:00
liaojack8 a8700a2994 Merge branch 'master' of https://github.com/liaojack8/gd-utils-cht 2020-07-07 01:00:37 +08:00
jkliao.tw 17f3a1a091
Update readme.md 2020-07-07 01:00:17 +08:00
liaojack8 2b05a01a94 let user can set the number of buttons per column in config.js 2020-07-07 00:55:05 +08:00
liaojack8 a3b0c2c9bb 修正tg bot按鈕顯示 2020-07-06 21:18:00 +08:00
jkliao.tw 0c867ff8f0
Update config.js 2020-07-06 21:07:33 +08:00
liaojack8 54965cf5cb update bookmark func 2020-07-06 18:48:19 +08:00
liaojack8 31ca1ead39 update readme 2020-07-06 11:59:34 +08:00
liaojack8 83db45e9d6 update readme.md 2020-07-06 11:46:27 +08:00
liaojack8 6f59f7ca3e can set copy dstID in config.js 2020-07-06 11:35:32 +08:00
liaojack8 757c5f132d fix. 2020-07-06 10:27:46 +08:00
liaojack8 ea8b6ad451 fix problems in scripts 2020-07-06 10:23:03 +08:00
liaojack8 a731dc3975 Merge branch 'master' of https://github.com/liaojack8/gd-utils-cht 2020-07-06 10:17:01 +08:00
liaojack8 136f9290d9 fix problem in scripts 2020-07-06 10:16:01 +08:00
jkliao.tw 8d89d024b2
Update readme.md 2020-07-06 10:05:09 +08:00
liaojack8 1327616635 config.js can setting sa path, and install w/ my repo in script. 2020-07-06 09:58:43 +08:00
liaojack8 3b94ccac7d fix idiot problem 2020-07-05 22:55:15 +08:00
liaojack8 a7602d9cf5 fix 2020-07-05 22:37:31 +08:00
liaojack8 a12656ebad 更新config.js 可自訂sa路徑 2020-07-05 22:15:21 +08:00
liaojack8 cbcdeb52e8 add forceRefresh=true when call gtoken.getToken by @iwestlin 2020-07-05 22:02:58 +08:00
liaojack8 5b67053077 rm teamdrive: true by @iwestlin 2020-07-05 22:01:29 +08:00
liaojack8 6fe42e3462 add -D option to ./copy, means do not create new root while copying by @iwestlin 2020-07-05 21:53:20 +08:00
jkliao.tw 010c23a060
Update readme.md 2020-07-05 16:16:25 +08:00
jkliao.tw 3f2bc2196f
Update readme.md 2020-07-05 16:15:43 +08:00
jkliao.tw b4f74090f2
Update readme.md 2020-07-05 16:14:55 +08:00
liaojack8 ec71af7364 pm2 start w/ args to prevent crash 2020-07-05 15:18:43 +08:00
liaojack8 8623faf2b7 stop task while no sa token available and use_sa is true 2020-07-05 15:15:39 +08:00
jkliao.tw 33a8bdfd21
Update readme.md 2020-07-05 13:22:00 +08:00
jkliao.tw 8e83c6568a
Update readme.md 2020-07-05 13:21:34 +08:00
liaojack8 85ccba7c72 update readme, and fix auto install sctript. 2020-07-05 13:20:12 +08:00
liaojack8 5f424722f7 use ssl in nginx 2020-07-05 01:33:25 +08:00
jkliao.tw 97e7352f57
Update readme.md 2020-07-05 01:17:36 +08:00
jkliao.tw 926593113b
Update readme.md 2020-07-04 01:54:15 +08:00
liaojack8 ed246ab1f1 translate router.js and add one more func. for tg_bot button 2020-07-04 01:48:13 +08:00
liaojack8 fae19ac900 translate 2020-07-04 01:41:35 +08:00
liaojack8 45c995526e translate tg.js, add percentage in task content, add one more button to copy 2020-07-04 01:37:34 +08:00
liaojack8 e9e37d1d03 update readme 2020-07-04 01:15:21 +08:00
liaojack8 ba07307030 update tg.js gd.js by @iwestlin 2020-07-04 01:12:20 +08:00
vitaminx f73cc04880
Delete 2020-07-02 123957.png 2020-07-02 12:59:22 +08:00
vitaminx 1106ff8853
test 2020-07-02 12:59:08 +08:00
vitaminx 29f7aa1621
一键脚本说明 2020-07-02 12:58:05 +08:00
vitaminx 3ce2ad9c79
Delete 批注 2020-07-02 123957.png 2020-07-02 12:57:33 +08:00
vitaminx 92f0a965d7
一键脚本说明 2020-07-02 12:43:18 +08:00
vitaminx f7cc2f1aba
终于支持绑定telegram的id了 2020-07-02 12:23:34 +08:00
vitaminx 12945a4939
更新绑定telegram ID
终于支持绑定telegram ID,这样四个参数都可以判读对错,避免小白输入错误!
2020-07-02 01:39:25 +08:00
vitaminx 29c3bffa3c
Delete gdutilsinstall.sh 2020-07-02 01:37:36 +08:00
vitaminx cca1d0d502
Delete gdutilsbotinstall.sh 2020-07-02 01:37:21 +08:00
vitaminx 8eed170331
简化输入!
只需要输入“token”“域名”“tg名”三个就行
2020-07-01 21:56:18 +08:00
vitaminx 16b5a31b01
Delete gdutilsbotinstall.sh 2020-07-01 21:55:11 +08:00
vitaminx eb145beae4
Delete gdutilsinstall.sh 2020-07-01 21:54:55 +08:00
vitaminx 833633c0f9
修改换行符错误 2020-07-01 20:51:14 +08:00
vitaminx 1a307e4152
格式错误 2020-07-01 20:50:17 +08:00
vitaminx b57da4294a
格式错误 2020-07-01 20:49:42 +08:00
vitaminx 1534cf1ae5
格式化重新上传 2020-07-01 20:42:02 +08:00
vitaminx 0634d2d235
未格式化 2020-07-01 20:41:08 +08:00
vitaminx d2089ba04f
未格式化 2020-07-01 20:40:21 +08:00
vitaminx 4d7e665695
Update gdutilscsinstall.sh 2020-07-01 20:25:43 +08:00
vitaminx b5dab5ed9d
Update gdutilscsinstall.sh 2020-07-01 20:17:50 +08:00
vitaminx 4eb211bb2a
增加分脚本说明 2020-07-01 20:06:46 +08:00
vitaminx a1264df17a
分脚本
针对有些人只要查询转存和机器人两部分功能中的一部分,把脚本分开了两个一键部署
gdutilscsinstall.sh为查询转存一键部署脚本
gdutilsbotinstall.sh为机器人一键部署脚本
2020-07-01 20:02:05 +08:00
vitaminx 0c86a645c3
Update gdutilsinstall.sh 2020-07-01 16:45:47 +08:00
vitaminx a86fdb3ed8
Update gdutilsinstall.sh 2020-07-01 16:41:06 +08:00
vitaminx 9026cabb5b
nginx配置格式修改 2020-07-01 13:27:34 +08:00
vitaminx 7919b11644
Merge pull request #6 from vitaminx/vitaminx-patch-gdutilsinstall.sh-2.1
改版脚本错误修复
2020-07-01 13:08:19 +08:00
vitaminx dec3202d21
改版脚本错误修复
脚本改版说明:
1-对脚本重新编写,效率跟高代码更少
2-修复了有些系统不能识别的BUG
3-增加了输入“机器人token/telegram账号名/WEB服务名/网址”信息对错的判断
4-修复了两处小错误
2020-07-01 13:08:04 +08:00
vitaminx a4daca2534
Delete gdutilsinstall.sh 2020-07-01 13:05:42 +08:00
vitaminx 6bd7c5c022
Merge pull request #5 from vitaminx/vitaminx-patch-gdutilsinstall.sh-2.0
一键脚本改版
2020-07-01 12:48:10 +08:00
vitaminx 322417a77a
一键脚本改版
脚本改版说明:
1-对脚本进行改版,效率更高代码更简洁
2-修复了有些系统不能识别的BUG
2-增加了输入“机器人token/telegram账号名/WEB服务名/网址”信息对错的判断

一件脚本使用方法:
之际复制以下链接到VPS命令行窗口粘贴回车即可以执行
2020-07-01 12:47:55 +08:00
vitaminx 2ebdf2d816
跟新更高效的脚本 2020-07-01 12:44:15 +08:00
vitaminx 5f323ce4ec
修改说明 2020-06-30 21:20:22 +08:00
vitaminx ec87e0b498
bash
用bash命令兼容性更广
2020-06-30 20:30:34 +08:00
vitaminx 4dd45aaca7
Update readme.md 2020-06-30 20:07:11 +08:00
vitaminx 9ec771a1c2
Update readme.md 2020-06-30 20:05:01 +08:00
vitaminx 158cc5772e
Merge pull request #4 from vitaminx/vitaminx-patch-一键脚本说明
一键脚本说明
2020-06-30 19:38:15 +08:00
19 changed files with 1367 additions and 983 deletions

View File

@ -1,63 +1,63 @@
# 对比本工具和其他类似工具在 server side copy 的速度上的差异 # 對比本工具和其他類似工具在 server side copy 的速度上的差異
### 這裡使用機器翻譯直接簡轉繁, 大家看得懂就好: )
以拷貝[https://drive.google.com/drive/folders/1W9gf3ReGUboJUah-7XDg5jKXKl5XwQQ3](https://drive.google.com/drive/folders/1W9gf3ReGUboJUah-7XDg5jKXKl5XwQQ3)為例([文件統計](https://gdurl.viegg.com/api/gdrive/count?fid=1W9gf3ReGUboJUah-7XDg5jKXKl5XwQQ3)
共 242 個文件和 26 個文件夾
以拷贝[https://drive.google.com/drive/folders/1W9gf3ReGUboJUah-7XDg5jKXKl5XwQQ3](https://drive.google.com/drive/folders/1W9gf3ReGUboJUah-7XDg5jKXKl5XwQQ3)为例([文件统计](https://gdurl.viegg.com/api/gdrive/count?fid=1W9gf3ReGUboJUah-7XDg5jKXKl5XwQQ3) 如無特殊說明,以下運行環境都是在本地命令行(掛代理)
共 242 个文件和 26 个文件夹
如无特殊说明,以下运行环境都是在本地命令行(挂代理) ## 本工具耗時 40 秒
## 本工具耗时 40 秒
<!-- ![](https://viegg.oss-cn-shenzhen.aliyuncs.com/1592732262296.png) --> <!-- ![](https://viegg.oss-cn-shenzhen.aliyuncs.com/1592732262296.png) -->
![](static/gdurl.png) ![](static/gdurl.png)
另外我在一台洛杉矶的vps上执行相同的命令耗时23秒。 另外我在一台洛杉磯的vps上執行相同的命令耗時23秒。
这个速度是在使用本项目默认配置**20个并行请求**得出来的,此值可自行修改(下文有方法),并行请求数越大,总速度越快。 這個速度是在使用本項目默認配置**20個並行請求**得出來的,此值可自行修改(下文有方法),並行請求數越大,總速度越快。
## AutoRclone 耗时 4 分 57 秒(去掉拷贝后验证时间 4 分 6 秒) ## AutoRclone 耗時 4 分 57 秒(去掉拷貝後驗證時間 4 分 6 秒)
<!-- ![](https://viegg.oss-cn-shenzhen.aliyuncs.com/1592732547295.png) --> <!-- ![](https://viegg.oss-cn-shenzhen.aliyuncs.com/1592732547295.png) -->
![](static/autorclone.png) ![](static/autorclone.png)
## gclone 耗 3 分 7 秒 ## gclone 耗 3 分 7 秒
<!-- ![](https://viegg.oss-cn-shenzhen.aliyuncs.com/1592732597593.png) --> <!-- ![](https://viegg.oss-cn-shenzhen.aliyuncs.com/1592732597593.png) -->
![](static/gclone.png) ![](static/gclone.png)
## 为什么速度会有这么大差异 ## 為什麽速度會有這麽大差異
首先要明确一下 server side copy后称ssc 的原理。 首先要明確一下 server side copy後稱ssc 的原理。
对于 Google Drive 本身而言它不会因为你ssc复制了一份文件而真的去在自己的文件系统上复制一遍否则不管它有多大硬盘都会被填满它只是在数据库里添上了一笔记录 對於 Google Drive 本身而言它不會因為你ssc覆制了一份文件而真的去在自己的文件系統上覆制一遍否則不管它有多大硬盤都會被填滿它只是在數據庫里添上了一筆記錄
所以,无论ssc一份大文件还是小文件理论上它的耗时都是一样的。 所以,無論ssc一份大文件還是小文件理論上它的耗時都是一樣的。
各位在使用这些工具的时候也可以感受到,复制一堆小文件比复制几个大文件要慢得多。 各位在使用這些工具的時候也可以感受到,覆制一堆小文件比覆制幾個大文件要慢得多。
Google Drive 官方的 API 只提供了复制单个文件的功能,无法直接复制整个文件夹。甚至也无法读取整个文件夹,只能读取某个文件夹的第一层子文件(夹)信息,类似 Linux 命令行里的 `ls` 命令。 Google Drive 官方的 API 只提供了覆制單個文件的功能,無法直接覆制整個文件夾。甚至也無法讀取整個文件夾,只能讀取某個文件夾的第一層子文件(夾)信息,類似 Linux 命令行里的 `ls` 命令。
这三个工具的ssc功能本质上都是对[官方file copy api](https://developers.google.com/drive/api/v3/reference/files/copy)的调用。 這三個工具的ssc功能本質上都是對[官方file copy api](https://developers.google.com/drive/api/v3/reference/files/copy)的調用。
后说一下本工具的原理,其大概步骤如下: 後說一下本工具的原理,其大概步驟如下:
- 首先,它会递归读取要复制的目录里的所有文件和文件夹的信息,并保存到本地。 - 首先,它會遞歸讀取要覆制的目錄里的所有文件和文件夾的信息,並保存到本地。
- 然后,将所有文件夹对象过滤出来,再根据彼此的父子关系,创建新的同名文件夹,还原出原始结构。(在保证速度的同时保持原始文件夹结构不变,这真的费了一番功夫) - 然後,將所有文件夾對象過濾出來,再根據彼此的父子關系,創建新的同名文件夾,還原出原始結構。(在保證速度的同時保持原始文件夾結構不變,這真的費了一番功夫)
- 根据上一步创建文件夹时留下的新旧文件夹ID的对应关系调用官方API复制文件。 - 根據上一步創建文件夾時留下的新舊文件夾ID的對應關系調用官方API覆制文件。
得益于本地数据库的存在,它可以在任务中断后从断点继续执行。比如用户按下`ctrl+c`后,可以再执行一遍相同的拷贝命令,本工具会给出三个选项 得益於本地數據庫的存在,它可以在任務中斷後從斷點繼續執行。比如用戶按下`ctrl+c`後,可以再執行一遍相同的拷貝命令,本工具會給出三個選項
<!-- ![](https://viegg.oss-cn-shenzhen.aliyuncs.com/1592735608511.png) --> <!-- ![](https://viegg.oss-cn-shenzhen.aliyuncs.com/1592735608511.png) -->
![](static/choose.png) ![](static/choose.png)
另外两个工具也支持断点续传它们是怎样做到的呢AutoRclone是用python对rclone命令的一层封装gclone是基于rclone的魔改。 另外兩個工具也支持斷點續傳它們是怎樣做到的呢AutoRclone是用python對rclone命令的一層封裝gclone是基於rclone的魔改。
对了——值得一提的是——本工具是直接调用的官方API不依赖于rclone。 對了——值得一提的是——本工具是直接調用的官方API不依賴於rclone。
没有仔细阅读过rclone的源码但是从它的执行日志中可以大概猜出其工作原理。 沒有仔細閱讀過rclone的源碼但是從它的執行日志中可以大概猜出其工作原理。
补充个背景知识对于存在于Google drive的所有文件对象它们的一生都伴随着一个独一无二的ID就算一个文件是另一个的拷贝它们的ID也不一样 補充個背景知識對於存在於Google drive的所有文件對象它們的一生都伴隨著一個獨一無二的ID就算一個文件是另一個的拷貝它們的ID也不一樣
所以rclone是怎么知道哪些文件拷贝过,哪些没有呢?如果它没有像我一样将记录保存在本地数据库的话,那么它只能在同一路径下搜索是否存在同名文件,如果存在,再比对它们的 大小/修改时间/md5值 等判断是否拷贝过 所以rclone是怎麽知道哪些文件拷貝過,哪些沒有呢?如果它沒有像我一樣將記錄保存在本地數據庫的話,那麽它只能在同一路徑下搜索是否存在同名文件,如果存在,再比對它們的 大小/修改時間/md5值 等判斷是否拷貝過
也就是在最坏的情况下假设它没做缓存它每拷贝一个文件之前都要先调用官方API来搜索判断此文件是否已存在! 也就是在最壞的情況下假設它沒做緩存它每拷貝一個文件之前都要先調用官方API來搜索判斷此文件是否已存在!
此外AutoRclone和gclone虽然都支持自动切换service account但是它们执行拷贝任务的时候都是单一SA在调用API这就注定了它们不能把请求频率调太高——否则可能触发限制。 此外AutoRclone和gclone雖然都支持自動切換service account但是它們執行拷貝任務的時候都是單一SA在調用API這就注定了它們不能把請求頻率調太高——否則可能觸发限制。
而本工具同样支持自动切换service account区别在于它的每次请求都是随机选一个SA我的[文件统计](https://gdurl.viegg.com/api/gdrive/count?fid=1W9gf3ReGUboJUah-7XDg5jKXKl5XwQQ3)接口就用了20个SA的token同时请求数设置成20个也就是平均而言单个SA的并发请求数只有一次。 而本工具同樣支持自動切換service account區別在於它的每次請求都是隨機選一個SA我的[文件統計](https://gdurl.viegg.com/api/gdrive/count?fid=1W9gf3ReGUboJUah-7XDg5jKXKl5XwQQ3)接口就用了20個SA的token同時請求數設置成20個也就是平均而言單個SA的並发請求數只有一次。
所以瓶颈不在于SA的频率限制而在运行的vps或代理上各位可以根据各自的情况适当调整 PARALLEL_LIMIT 的值(在 `config.js` 里)。 所以瓶頸不在於SA的頻率限制而在運行的vps或代理上各位可以根據各自的情況適當調整 PARALLEL_LIMIT 的值(在 `config.js` 里)。
当然如果某个SA的单日流量超过了750G会自动切换成别的SA同时过滤掉流量用尽的SA。当所有SA流量用完后会切换到个人的access token直到流量同样用尽最终进程退出。 當然如果某個SA的單日流量超過了750G會自動切換成別的SA同時過濾掉流量用盡的SA。當所有SA流量用完後會切換到個人的access token直到流量同樣用盡最終進程退出。
*使用SA存在的限制除了每日流量限制外实每个SA还有个**15G的个人盘空间限额**也就是说你每个SA最多能拷贝15G的文件到个人盘但是拷贝到团队盘则无此限制。* *使用SA存在的限制除了每日流量限制外實每個SA還有個**15G的個人盤空間限額**也就是說你每個SA最多能拷貝15G的文件到個人盤但是拷貝到團隊盤則無此限制。*

View File

@ -21,4 +21,7 @@ const AUTH = { // 如果您拥有service account的json授权文件可将其
tg_whitelist: ['your_tg_username'] // 你的tg username(t.me/username)bot只会执行这个列表里的用户所发送的指令 tg_whitelist: ['your_tg_username'] // 你的tg username(t.me/username)bot只会执行这个列表里的用户所发送的指令
} }
module.exports = { AUTH, PARALLEL_LIMIT, RETRY_LIMIT, TIMEOUT_BASE, TIMEOUT_MAX, LOG_DELAY, PAGE_SIZE, DEFAULT_TARGET } //-------------------MOD-------------------
const SA_PATH = '../sa' //sa路徑配置, 給定絕對路徑或是以src為當前路徑給定相對路徑, 預設為'../sa'
const BUTTON_LEVEL = 1 //預設為1, 填入大於2皆視為2
module.exports = { AUTH, PARALLEL_LIMIT, RETRY_LIMIT, TIMEOUT_BASE, TIMEOUT_MAX, LOG_DELAY, PAGE_SIZE, DEFAULT_TARGET, SA_PATH, BUTTON_LEVEL }

8
copy
View File

@ -16,6 +16,8 @@ const { argv } = require('yargs')
.describe('s', '不填默认拷贝全部文件如果设置了这个值则过滤掉小于这个size的文件必须以b结尾比如10mb') .describe('s', '不填默认拷贝全部文件如果设置了这个值则过滤掉小于这个size的文件必须以b结尾比如10mb')
.alias('S', 'service_account') .alias('S', 'service_account')
.describe('S', '指定使用service account进行操作前提是必须在 ./sa 目录下放置json授权文件请确保sa帐号拥有操作权限。') .describe('S', '指定使用service account进行操作前提是必须在 ./sa 目录下放置json授权文件请确保sa帐号拥有操作权限。')
.alias('D', 'dncnr')
.describe('D', 'do not create new root, 不在目的地创建同名文件夹,直接将源文件夹中的文件原样复制到目的文件夹中')
.help('h') .help('h')
.alias('h', 'help') .alias('h', 'help')
@ -25,11 +27,11 @@ const { DEFAULT_TARGET } = require('./config')
let [source, target] = argv._ let [source, target] = argv._
if (validate_fid(source)) { if (validate_fid(source)) {
const { name, update, file, not_teamdrive, size, service_account } = argv const { name, update, file, not_teamdrive, size, service_account, dncnr } = argv
if (file) { if (file) {
target = target || DEFAULT_TARGET target = target || DEFAULT_TARGET
if (!validate_fid(target)) throw new Error('target id 格式不正确') if (!validate_fid(target)) throw new Error('target id 格式不正确')
return copy_file(source, target).then(r => { return copy_file(source, target, service_account).then(r => {
const link = 'https://drive.google.com/drive/folders/' + target const link = 'https://drive.google.com/drive/folders/' + target
console.log('任务完成,文件所在位置:\n', link) console.log('任务完成,文件所在位置:\n', link)
}).catch(console.error) }).catch(console.error)
@ -39,7 +41,7 @@ if (validate_fid(source)) {
console.log(`不复制大小低于 ${size} 的文件`) console.log(`不复制大小低于 ${size} 的文件`)
min_size = bytes.parse(size) min_size = bytes.parse(size)
} }
copy({ source, target, name, min_size, update, not_teamdrive, service_account }).then(folder => { copy({ source, target, name, min_size, update, not_teamdrive, service_account, dncnr }).then(folder => {
if (!folder) return if (!folder) return
const link = 'https://drive.google.com/drive/folders/' + folder.id const link = 'https://drive.google.com/drive/folders/' + folder.id
console.log('\n任务完成新文件夹链接\n', link) console.log('\n任务完成新文件夹链接\n', link)

View File

@ -27,3 +27,19 @@ CREATE UNIQUE INDEX "task_source_target" ON "task" (
"source", "source",
"target" "target"
); );
CREATE TABLE "copied" (
"taskid" INTEGER,
"fileid" TEXT
);
CREATE INDEX "copied_taskid" ON "copied" ("taskid");
CREATE TABLE "bookmark" (
"alias" TEXT,
"target" TEXT
);
CREATE UNIQUE INDEX "bookmark_alias" ON "bookmark" (
"alias"
);

30
db.js
View File

@ -2,4 +2,34 @@ const path = require('path')
const db_location = path.join(__dirname, 'gdurl.sqlite') const db_location = path.join(__dirname, 'gdurl.sqlite')
const db = require('better-sqlite3')(db_location) const db = require('better-sqlite3')(db_location)
db.pragma('journal_mode = WAL')
create_table_copied()
function create_table_copied () {
const [exists] = db.prepare('PRAGMA table_info(copied)').all()
if (exists) return
const create_table = `CREATE TABLE "copied" (
"taskid" INTEGER,
"fileid" TEXT
)`
db.prepare(create_table).run()
const create_index = `CREATE INDEX "copied_taskid" ON "copied" ("taskid");`
db.prepare(create_index).run()
}
create_table_bookmark()
function create_table_bookmark () {
const [exists] = db.prepare('PRAGMA table_info(bookmark)').all()
if (exists) return
const create_table = `CREATE TABLE "bookmark" (
"alias" TEXT,
"target" TEXT
);`
db.prepare(create_table).run()
const create_index = `CREATE UNIQUE INDEX "bookmark_alias" ON "bookmark" (
"alias"
);`
db.prepare(create_index).run()
}
module.exports = { db } module.exports = { db }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 160 KiB

View File

@ -1,129 +0,0 @@
# 几个坑
* Telegram Bot API 提供了两种方式, webhook 和 long polling目前项目只支持 webhook 方式。
* webhook 方式必须要用HTTPS 也就是需要准备**个人域名**和**一个有效证书**
* 证书一定要单独域名证书(泛域名证书不能用)
# 原理/思路
TG创建bot要起一个服务支持BOT的功能 所以需要配置webhook 让tg 和服务器建立连接。webhook 需要有HTTPS的外网域名并且修改DNS指向你所配置的服务器IP这样就能保证TG的请求可以顺利到达并且验证BOT。
在服务器内部如果如果是单BOT 可以直接用nodje 配合 PM2 直接起服务,然后修改server.js端口号443。 如果服务器上有多个服务,那么就需要用反向代理,反代简单说就是一个服务+映射规则 (ngnix或者apache后者其他都可以) 侦听80或者443端口如果有指定的映射请求 就转发到内部映射的各个服务。
例如
```
aaa.domain.com <=> locahost:3001
bbb.domain.com <=> locahost:3002
domain.com/ccc <=> localhost:3003
```
# 步骤
1. 需要去tg 创建一个bot会得到token 和bot的tgurl
2. BOT服务
1. 服务器上clone 项目安装node, npm install
2. 如果需要配置多个BOT, clone不同目录, server.js里修改配置port和config.js
3. 安装PM2在每个bot目录下 PM2 start server.js
4. ``` pm2 status``` 确认服务跑起来了
1. 如果没起来, 查log文件见底部
5. curl 检查本地连接, curl 检查远端连接, not found 就对了
3. 外部连接
1. 修改DNS我是用cloudflare 把添加A record 直接把静态IP 绑定
2. 绑定以后, 本地开个terminal, ping 刚添加域名直到解析的IP是你绑定的这步确保连接上是畅通的
4. apache2开启SSL和反代
1. 复制证书到任意位置
2. 运行底部命令
3. /etc/apache2/sites-available 下找到默认的.conf或者自己建个conf也行
4. 修改底部配置信息
5. 保存重启 ```service apache2 restart```
5. 剩下的就是配置和检查webhook这里面也有不少坑在反代配置文件部分。。记不清了。。
6. 如果一切顺利 /help 会弹出目录
```
pm2 部分
tail -200 ~/.pm2/logs/server-error.log
tail -200 ~/.pm2/logs/server-out.log
curl "localhost:23333"
curl "domain:23333"
SSL+反代
sudo a2enmod ssl
sudo a2enmod proxy
sudo a2enmod proxy_balancer
sudo a2enmod proxy_http
/etc/apache2/sites-available/xxx.conf
<VirtualHost *:443>
SSLEngine on
SSLProtocol all
SSLCertificateFile {{CERT_DIR}}/{{domain.cer}}
SSLCertificateKeyFile {{CERT_DIR}}/{{domain.key}}
SSLCACertificateFile {{CERT_DIR}}/{{domain.ca.cer}}
ServerName {{domain}}
ProxyRequests Off
ProxyPreserveHost On
ProxyVia Full
<Proxy *>
Require all granted
</Proxy>
# 这里我用的是子目录映射方式。懒得再申请一个证书。。domain.com/ccc <=> localhost:3003
ProxyPass /{{bot1url}}/ http://127.0.0.1:23334/ # bot1
ProxyPassReverse /{{bot1url}}/ http://127.0.0.1:23334/ # bot1
ProxyPass /{{bot2url}}/ http://127.0.0.1:23333/ # bot2
ProxyPassReverse /{{bot2url}}/ http://127.0.0.1:23333/ # bot2
</VirtualHost>
something for verify and DEBUG
Apache command:
service apache2 restart
service apache2 stop
service apache2 status
service apache2 reload
tail -100 /var/log/apache2/error.log
验证一下SSL:
https://www.ssllabs.com/ssltest/analyze.html 确保Trusted和In trust store是绿的反正我这两个绿的就TG就能找到的到
SET webhook
curl -F "url=https://{{domain}}/{{bot1url}}/api/gdurl/tgbot" 'https://api.telegram.org/bot{{BOT_TOKEN}}/setWebhook'
delete webhook
curl -F "url=" https://api.telegram.org/bot{{BOT_TOKEN}}/setWebhook
check webhook
curl "https://api.telegram.org/bot{{BOT_TOKEN}}/getWebhookInfo"
```
![avatar](/doc/bot-worked.png)
# Reference Link
https://core.telegram.org/bots
https://core.telegram.org/bots/api
https://www.jianshu.com/p/ca804497afa0

167
gdutilsbotinstall.sh Normal file
View File

@ -0,0 +1,167 @@
#!/bin/bash
echo
echo -e "\033[1;32m===== <<gdutils项目一键部署脚本之“TG机器人”>> =====\033[0m"
echo -e "\033[1;32m----------------[ v2.1 by oneking ]----------------\033[0m"
echo -e "\033[32m 01.\033[0m 本脚本是针对TG大神@viegg的gdutils项目“TG机器人”部分一键部署脚本;"
echo -e "\033[32m 02.\033[0m 准备工作一部署完成gdutils项目TD查询转存部分;"
echo -e "\033[32m 03.\033[0m 准备工作二在TG上注册好机器人取得并记录下该机器人TOKEN"
echo -e "\033[32m 04.\033[0m 准备工作三拥有一个域名绑定到cloudflare解析到该机器人所在服务器IP"
echo -e "\033[32m 05.\033[0m 准备工作四:向机器人@userinfobot获取个人TG账号ID并记录"
echo -e "\033[32m 06.\033[0m 准备工作五注册好一个Google team drive加入sa并记录下该盘ID"
echo -e "\033[32m 07.\033[0m 本脚本适应CentOS/Debian/Ubuntu三种操作系统自动识别、自动匹配参数一键部署"
echo -e "\033[32m 08.\033[0m 由于本脚本涉及到依赖软件较多避免中断建议使用screen窗口安装"
echo -e "\033[32m 09.\033[0m 经测试可用完美安装系统Centos 7/8 debian 9/10 ubuntu 16.04/18.04/19.10/20.04"
echo -e "\033[32m 10.\033[0m 部署过程中有任何问题请把“错误截图”“部署VPS系统名称版本”信息发给TGonekings 或 vitaminor@gmail.com"
echo -e "\033[1;32m------------------------------------------------\033[0m"
read -s -n1 -p "★★★ 如已做好以上[2/3/4/5/6]准备请按任意键开始部署如未做好准备请按“Ctrl+c”终止部署 ★★★"
echo
echo -e "\033[1;32m------------------------------------------------\033[0m"
# 识别操作系统
aNAME="$(uname -a)"
bNAME="$(cat /proc/version)"
cNAME="$(lsb_release -a)"
if [ -f "/etc/redhat-release" ]; then
if [[ $(cat /etc/redhat-release) =~ "CentOS" ]]; then
os="CentOS"
fi
elif [ "$aNAME"=~"Debian" -o "$bNAME"=~"Debian" -o "$cNAME"=~"Debian" ]; then
os="Debian"
elif [ "$aNAME"=~"Ubuntu" -o "$bNAME"=~"Ubuntu" -o "$cNAME"=~"Ubuntu" ]; then
os="Debian"
elif [ "$aNAME"=~"CentOS" -o "$bNAME"=~"CentOS" -o "$cNAME"=~"CentOS" ]; then
os="CentOS"
elif [ "$aNAME"=~"Darwin" -o "$bNAME"=~"Darwin" -o "$cNAME"=~"Darwin" ]; then
os="mac"
else
os="$bNAME"
fi
#根据操作系统设置变量
if [[ "$os" = "Debian" ]]; then
cmd_install="apt-get" #安装命令
nginx_conf="/etc/nginx/sites-enabled/" #nginx配置文件存放路径
rm_nginx_default="rm -f /etc/nginx/sites-enabled/default" #删除default
echo
echo -e "\033[1;32m★★★★★ 您的操作系统为Debian即将为你开始自动部署gdutils项目“TG机器人”部分 ★★★★★\033[0m"
elif [[ "$os" = "Ubuntu" ]]; then
cmd_install="sudo apt-get"
nginx_conf="/etc/nginx/sites-enabled/"
rm_nginx_default="rm -f /etc/nginx/sites-enabled/default"
echo
echo -e "\033[1;32m★★★★★ 您的操作系统为Ubuntu即将为你开始自动部署gdutils项目“TG机器人”部分 ★★★★★\033[0m"
elif [[ "$os" = "CentOS" ]]; then
cmd_install="yum"
nginx_conf="/etc/nginx/conf.d/"
rm_nginx_default=""
echo
echo -e "\033[1;32m★★★★★ 您的操作系统为Centos即将为你开始自动部署gdutils项目“TG机器人”部分 ★★★★★\033[0m"
elif [[ "$os" = "mac" ]]; then
echo
echo -e "\033[1;32m★★★★★ 您的操作系统为MacOS请在图形界面手动部署 ★★★★★\033[0m"
exit
echo
echo
else
echo
echo -e "\033[1;32m unknow os $OS, exit! \033[0m"
exit
echo
echo
fi
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
#输入“机器人token/TG账号ID/域名/转存目的盘ID”
read -p """请输入机器人token并回车
Your Bot Token =>:""" YOUR_BOT_TOKEN
#判断token是否输入正确
while [[ "${#YOUR_BOT_TOKEN}" != 46 ]]; do
echo -e "\033[1;32m★★★ 机器人TOKEN输入不正确请重新输入或按“Ctrl+C”结束安装 ★★★\033[0m"
read -p """请输入机器人token并回车
Your Bot Token =>:""" YOUR_BOT_TOKEN
done
read -p """请输入你的域名(在cloudflare上解析到你机器人所在VPS的域名格式bot.abc.com)并回车
Your Domain Name =>:""" YOUR_DOMAIN_NAME
#判断域名是否正确
while [[ "$YOUR_DOMAIN_NAME" =~ "http" ]]; do
echo -e "\033[1;32m★★★ “Your Domain Name”输入错误应该输入你在cloudflare上解析的域名且不包含“http”请重新输入或按“Ctrl+C”结束安装 ★★★\033[0m"
read -p """请输入你的域名(在cloudflare上解析到你机器人所在VPS的域名格式bot.abc.com)并回车
Your Domain Name =>:""" YOUR_DOMAIN_NAME
done
read -p """请输入使用机器人的telegram账号ID(获取ID机器人@userinfobot)并回车
Your Telegram ID =>:""" YOUR_TELEGRAM_ID
#判断telegram ID是否正确(通过判断是不是纯数字)
until [[ $YOUR_TELEGRAM_ID =~ ^-?[0-9]+$ ]]; do
echo -e "\033[1;32m★★★ 您的TG账号ID输入不正确请重新输入或按“Ctrl+C”结束安装 ★★★\033[0m"
read -p """请输入使用机器人的telegram账号ID(获取ID机器人@userinfobot)并回车
Your Telegram ID =>:""" YOUR_TELEGRAM_ID
done
read -p """请输入转存默认目的地团队盘ID(不指定转存目的地默认改地址脚本强制要求输入团队盘ID)并回车
Your Google Team Drive ID =>:""" YOUR_GOOGLE_TEAM_DRIVE_ID
#判断google team drive ID是否正确团队盘ID长度19位
while [[ "${#YOUR_GOOGLE_TEAM_DRIVE_ID}" != 19 ]]; do
echo -e "\033[1;32m★★★ 您的Google team drive ID输入不正确请重新输入或按“Ctrl+C”结束安装 ★★★\033[0m"
read -p """请输入转存默认目的地ID(不指定转存目的地默认该地址脚本强制要求输入团队盘ID)并回车
Your Google Team Drive ID =>:""" YOUR_GOOGLE_TEAM_DRIVE_ID
done
cd ~ &&
sed -i "s/bot_token/$YOUR_BOT_TOKEN/g" ./gd-utils/config.js &&
sed -i "s/your_tg_username/$YOUR_TELEGRAM_ID/g" ./gd-utils/config.js &&
sed -i "s/DEFAULT_TARGET = ''/DEFAULT_TARGET = '$YOUR_GOOGLE_TEAM_DRIVE_ID'/g" ./gd-utils/config.js
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
echo -e "\033[1;32m“进程守护程序pm2”开始安装......\033[0m"
cd /root/gd-utils &&
npm i pm2 -g && pm2 l
echo -e "\033[1;32m启动守护进程......\033[0m"
pm2 start server.js
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
echo -e "\033[1;32m“nginx”开始安装......\033[0m"
cd ~ &&
$cmd_install install nginx -y
echo
echo -e "\033[1;32m===== <<配置nginx服务>> ===== \033[0m"
echo
echo -e "\033[1;32m“nginx”起一个web服务......\033[0m"
cd $nginx_conf
echo "server {
listen 80;
server_name $YOUR_DOMAIN_NAME;
location / {
proxy_pass http://127.0.0.1:23333/;
}
}" >${nginx_conf}gdutilsbot.conf &&
$rm_nginx_default
ls &&
nginx -t &&
nginx -c /etc/nginx/nginx.conf &&
nginx -s reload &&
netstat -tulpen
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
echo -e "\033[1;32m“检查网站是否部署成功”......\033[0m"
curl $YOUR_DOMAIN_NAME/api/gdurl/count\?fid=124pjM5LggSuwI1n40bcD5tQ13wS0M6wg
echo
echo -e "\033[1;32m设置Webhook服务......\033[0m"
print_webhook=$(curl -F "url=https://$YOUR_DOMAIN_NAME/api/gdurl/tgbot" "https://api.telegram.org/bot$YOUR_BOT_TOKEN/setWebhook")
echo
# 判断反向代理是否部署成功
if [[ $print_webhook =~ "true" ]]; then
echo -e "\033[1;32m★★★ 恭喜你GoogleDrive查询转存机器人部署成功请回到TG界面给bot发送个“/help”获取使用帮助 ★★★\033[0m"
else
echo -e "\033[32m★★★ 很遗憾!机器人设置失败,请返回检查网站是否部署成功,并重复本安装过程 ★★★\033[0m", exit!
fi
nginx -t && nginx -s reload
echo
echo
cd ~
rm -f gdutilsbotinstall.sh

175
gdutilsbotinstall_SSL.sh Normal file
View File

@ -0,0 +1,175 @@
#!/bin/bash
echo
echo -e "\033[1;32m===== <<gdutils项目一键部署脚本之“TG机器人”>> =====\033[0m"
echo -e "\033[1;32m----------------[ v2.1 by oneking ]----------------\033[0m"
echo -e "\033[32m 01.\033[0m 本脚本是针对TG大神@viegg的gdutils项目“TG机器人”部分一键部署脚本;"
echo -e "\033[32m 02.\033[0m 准备工作一部署完成gdutils项目TD查询转存部分;"
echo -e "\033[32m 03.\033[0m 准备工作二在TG上注册好机器人取得并记录下该机器人TOKEN"
echo -e "\033[32m 04.\033[0m 准备工作三拥有一个域名绑定到cloudflare解析到该机器人所在服务器IP"
echo -e "\033[32m 05.\033[0m 准备工作四:向机器人@userinfobot获取个人TG账号ID并记录"
echo -e "\033[32m 06.\033[0m 准备工作五注册好一个Google team drive加入sa并记录下该盘ID"
echo -e "\033[32m 07.\033[0m 本脚本适应CentOS/Debian/Ubuntu三种操作系统自动识别、自动匹配参数一键部署"
echo -e "\033[32m 08.\033[0m 由于本脚本涉及到依赖软件较多避免中断建议使用screen窗口安装"
echo -e "\033[32m 09.\033[0m 经测试可用完美安装系统Centos 7/8 debian 9/10 ubuntu 16.04/18.04/19.10/20.04"
echo -e "\033[32m 10.\033[0m 部署过程中有任何问题请把“错误截图”“部署VPS系统名称版本”信息发给TGonekings 或 vitaminor@gmail.com"
echo -e "\033[1;32m------------------------------------------------\033[0m"
read -s -n1 -p "★★★ 如已做好以上[2/3/4/5/6]准备请按任意键开始部署如未做好准备请按“Ctrl+c”终止部署 ★★★"
echo
echo -e "\033[1;32m------------------------------------------------\033[0m"
# 识别操作系统
aNAME="$(uname -a)"
bNAME="$(cat /proc/version)"
cNAME="$(lsb_release -a)"
if [ -f "/etc/redhat-release" ]; then
if [[ $(cat /etc/redhat-release) =~ "CentOS" ]]; then
os="CentOS"
fi
elif [ "$aNAME"=~"Debian" -o "$bNAME"=~"Debian" -o "$cNAME"=~"Debian" ]; then
os="Debian"
elif [ "$aNAME"=~"Ubuntu" -o "$bNAME"=~"Ubuntu" -o "$cNAME"=~"Ubuntu" ]; then
os="Debian"
elif [ "$aNAME"=~"CentOS" -o "$bNAME"=~"CentOS" -o "$cNAME"=~"CentOS" ]; then
os="CentOS"
elif [ "$aNAME"=~"Darwin" -o "$bNAME"=~"Darwin" -o "$cNAME"=~"Darwin" ]; then
os="mac"
else
os="$bNAME"
fi
#根据操作系统设置变量
if [[ "$os" = "Debian" ]]; then
cmd_install="apt-get" #安装命令
nginx_conf="/etc/nginx/sites-enabled/" #nginx配置文件存放路径
rm_nginx_default="rm -f /etc/nginx/sites-enabled/default" #删除default
echo
echo -e "\033[1;32m★★★★★ 您的操作系统为Debian即将为你开始自动部署gdutils项目“TG机器人”部分 ★★★★★\033[0m"
elif [[ "$os" = "Ubuntu" ]]; then
cmd_install="sudo apt-get"
nginx_conf="/etc/nginx/sites-enabled/"
rm_nginx_default="rm -f /etc/nginx/sites-enabled/default"
echo
echo -e "\033[1;32m★★★★★ 您的操作系统为Ubuntu即将为你开始自动部署gdutils项目“TG机器人”部分 ★★★★★\033[0m"
elif [[ "$os" = "CentOS" ]]; then
cmd_install="yum"
nginx_conf="/etc/nginx/conf.d/"
rm_nginx_default=""
echo
echo -e "\033[1;32m★★★★★ 您的操作系统为Centos即将为你开始自动部署gdutils项目“TG机器人”部分 ★★★★★\033[0m"
elif [[ "$os" = "mac" ]]; then
echo
echo -e "\033[1;32m★★★★★ 您的操作系统为MacOS请在图形界面手动部署 ★★★★★\033[0m"
exit
echo
echo
else
echo
echo -e "\033[1;32m unknow os $OS, exit! \033[0m"
exit
echo
echo
fi
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
#输入“机器人token/TG账号ID/域名/转存目的盘ID”
read -p """请输入机器人token并回车
Your Bot Token =>:""" YOUR_BOT_TOKEN
#判断token是否输入正确
while [[ "${#YOUR_BOT_TOKEN}" != 46 ]]; do
echo -e "\033[1;32m★★★ 机器人TOKEN输入不正确请重新输入或按“Ctrl+C”结束安装 ★★★\033[0m"
read -p """请输入机器人token并回车
Your Bot Token =>:""" YOUR_BOT_TOKEN
done
read -p """请输入你的域名(在cloudflare上解析到你机器人所在VPS的域名格式bot.abc.com)并回车
Your Domain Name =>:""" YOUR_DOMAIN_NAME
#判断域名是否正确
while [[ "$YOUR_DOMAIN_NAME" =~ "http" ]]; do
echo -e "\033[1;32m★★★ “Your Domain Name”输入错误应该输入你在cloudflare上解析的域名且不包含“http”请重新输入或按“Ctrl+C”结束安装 ★★★\033[0m"
read -p """请输入你的域名(在cloudflare上解析到你机器人所在VPS的域名格式bot.abc.com)并回车
Your Domain Name =>:""" YOUR_DOMAIN_NAME
done
read -p """请输入使用机器人的telegram账号ID(获取ID机器人@userinfobot)并回车
Your Telegram ID =>:""" YOUR_TELEGRAM_ID
#判断telegram ID是否正确(通过判断是不是纯数字)
until [[ $YOUR_TELEGRAM_ID =~ ^-?[0-9]+$ ]]; do
echo -e "\033[1;32m★★★ 您的TG账号ID输入不正确请重新输入或按“Ctrl+C”结束安装 ★★★\033[0m"
read -p """请输入使用机器人的telegram账号ID(获取ID机器人@userinfobot)并回车
Your Telegram ID =>:""" YOUR_TELEGRAM_ID
done
read -p """请输入转存默认目的地团队盘ID(不指定转存目的地默认改地址脚本强制要求输入团队盘ID)并回车
Your Google Team Drive ID =>:""" YOUR_GOOGLE_TEAM_DRIVE_ID
#判断google team drive ID是否正确团队盘ID长度19位
while [[ "${#YOUR_GOOGLE_TEAM_DRIVE_ID}" != 19 ]]; do
echo -e "\033[1;32m★★★ 您的Google team drive ID输入不正确请重新输入或按“Ctrl+C”结束安装 ★★★\033[0m"
read -p """请输入转存默认目的地ID(不指定转存目的地默认该地址脚本强制要求输入团队盘ID)并回车
Your Google Team Drive ID =>:""" YOUR_GOOGLE_TEAM_DRIVE_ID
done
cd ~ &&
sed -i "s/bot_token/$YOUR_BOT_TOKEN/g" ./gd-utils-cht/config.js &&
sed -i "s/your_tg_username/$YOUR_TELEGRAM_ID/g" ./gd-utils-cht/config.js &&
sed -i "s/DEFAULT_TARGET = ''/DEFAULT_TARGET = '$YOUR_GOOGLE_TEAM_DRIVE_ID'/g" ./gd-utils-cht/config.js
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
echo -e "\033[1;32m“进程守护程序pm2”开始安装......\033[0m"
cd /root/gd-utils-cht &&
npm i pm2 -g && pm2 l
echo -e "\033[1;32m启动守护进程......\033[0m"
pm2 start server.js --node-args="--max-old-space-size=4096"
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
echo -e "\033[1;32m“nginx”开始安装......\033[0m"
cd ~ &&
$cmd_install install nginx -y
echo
echo -e "\033[1;32m===== <<配置nginx服务>> ===== \033[0m"
echo
echo -e "\033[1;32m“nginx”起一个web服务......\033[0m"
cd $nginx_conf
echo "server {
listen 80;
server_name $YOUR_DOMAIN_NAME;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
ssl on;
ssl_certificate /etc/ssl/certificate.crt;
ssl_certificate_key /etc/ssl/private.key;
server_name $YOUR_DOMAIN_NAME;
location / {
proxy_pass http://127.0.0.1:23333/;
}
}" >${nginx_conf}gdutilsbot.conf &&
$rm_nginx_default
ls &&
nginx -t &&
nginx -c /etc/nginx/nginx.conf &&
nginx -s reload &&
netstat -tulpen
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
echo -e "\033[1;32m“检查网站是否部署成功”......\033[0m"
curl $YOUR_DOMAIN_NAME/api/gdurl/count\?fid=124pjM5LggSuwI1n40bcD5tQ13wS0M6wg
echo
echo -e "\033[1;32m设置Webhook服务......\033[0m"
print_webhook=$(curl -F "url=https://$YOUR_DOMAIN_NAME/api/gdurl/tgbot" "https://api.telegram.org/bot$YOUR_BOT_TOKEN/setWebhook")
echo
# 判断反向代理是否部署成功
if [[ $print_webhook =~ "true" ]]; then
echo -e "\033[1;32m★★★ 恭喜你GoogleDrive查询转存机器人部署成功请回到TG界面给bot发送个“/help”获取使用帮助 ★★★\033[0m"
else
echo -e "\033[32m★★★ 很遗憾!机器人设置失败,请返回检查网站是否部署成功,并重复本安装过程 ★★★\033[0m", exit!
fi
nginx -t && nginx -s reload
echo
echo
cd ~
rm -f gdutilsbotinstall.sh

131
gdutilscsinstall_SSL.sh Normal file
View File

@ -0,0 +1,131 @@
#!/bin/bash
echo
echo -e "\033[1;32m===== <<gdutils项目一键部署脚本之“TD查询转存”>> =====\033[0m"
echo -e "\033[1;32m-----------------[ v2.1 by oneking ]-----------------\033[0m"
echo -e "\033[32m 1.\033[0m 本脚本是针对TG大神@viegg的gdutils项目“TD查询转存”部分一键部署脚本;"
echo -e "\033[32m 2.\033[0m 本脚本适应CentOS/Debian/Ubuntu三种操作系统自动识别、自动匹配参数一键部署"
echo -e "\033[32m 3.\033[0m 由于本脚本涉及到系统升级和依赖软件较多避免中断建议使用screen窗口安装"
echo -e "\033[32m 4.\033[0m 经测试可用完美安装系统Centos 7/8 debian 9/10 ubuntu 16.04/18.04/19.10/20.04"
echo -e "\033[32m 5.\033[0m 部署过程中有任何问题请把“错误截图”“部署VPS系统名称版本”信息发给TGonekings 或 vitaminor@gmail.com"
echo -e "\033[1;32m------------------------------------------------\033[0m"
read -s -n1 -p "★★★ 请按任意键开始部署按“Ctrl+c”终止部署 ★★★"
echo
echo -e "\033[1;32m------------------------------------------------\033[0m"
# 需要安装的软件工具及依赖
insofts=(epel-release update upgrade wget curl git unzip zip python3-distutils python3 python3-pip)
# 识别操作系统
aNAME="$(uname -a)"
bNAME="$(cat /proc/version)"
cNAME="$(lsb_release -a)"
if [ -f "/etc/redhat-release" ]; then
if [[ $(cat /etc/redhat-release) =~ "CentOS" ]]; then
os="CentOS"
fi
elif [ "$aNAME"=~"Debian" -o "$bNAME"=~"Debian" -o "$cNAME"=~"Debian" ]; then
os="Debian"
elif [ "$aNAME"=~"Ubuntu" -o "$bNAME"=~"Ubuntu" -o "$cNAME"=~"Ubuntu" ]; then
os="Debian"
elif [ "$aNAME"=~"CentOS" -o "$bNAME"=~"CentOS" -o "$cNAME"=~"CentOS" ]; then
os="CentOS"
elif [ "$aNAME"=~"Darwin" -o "$bNAME"=~"Darwin" -o "$cNAME"=~"Darwin" ]; then
os="mac"
else
os="$bNAME"
fi
#根据操作系统设置变量
if [[ "$os" = "Debian" ]]; then
cmd_install="apt-get" #安装命令
cmd_install_rely="build-essential" #c++编译环境
nodejs_curl="https://deb.nodesource.com/setup_10.x" #nodejs下载链接
cmd_install_rpm_build="" #安装rpm-build
echo
echo -e "\033[1;32m★★★★★ 您的操作系统为Debian即将为你开始部署gdutils项目“TD查询转存”部分 ★★★★★\033[0m"
elif [[ "$os" = "Ubuntu" ]]; then
cmd_install="sudo apt-get"
cmd_install_rely="build-essential"
nodejs_curl="https://deb.nodesource.com/setup_10.x"
cmd_install_rpm_build=""
echo
echo -e "\033[1;32m★★★★★ 您的操作系统为Ubuntu即将为你开始部署gdutils项目“TD查询转存”部分 ★★★★★\033[0m"
elif [[ "$os" = "CentOS" ]]; then
cmd_install="yum"
cmd_install_rely="gcc-c++ make"
nodejs_curl="https://rpm.nodesource.com/setup_10.x"
cmd_install_rpm_build="yum install rpm-build -y"
echo
echo -e "\033[1;32m★★★★★ 您的操作系统为Centos即将为你开始部署gdutils项目“TD查询转存”部分 ★★★★★\033[0m"
elif [[ "$os" = "mac" ]]; then
echo
echo -e "\033[1;32m★★★★★ 您的操作系统为MacOS请在图形界面手动部署 ★★★★★\033[0m"
exit
echo
echo
else
echo
echo -e "\033[1;32m unknow os $OS, exit! \033[0m"
exit
echo
echo
fi
echo
echo -e "\033[1;32m===== <<升级系统/更新软件/安装工具/安装依赖>> =====\033[0m"
echo
# 检查which和sudo是否安装如已安装跳过未安装先装
if [[ "$(which which)" == "" ]]; then
echo -e "\033[1;32m“which”开始安装......\033[0m"
$cmd_install install which -y
echo -e "\033[1;32m------------------------------------------------\033[0m"
elif [[ "$(which sudo)" == "" ]]; then
echo -e "\033[1;32m“sudo”开始安装......\033[0m"
$cmd_install install sudo -y
echo -e "\033[1;32m------------------------------------------------\033[0m"
fi
#安装工具和依赖
for ((aloop = 0; aloop < ${#insofts[@]}; aloop++)); do
if [ ${insofts[$aloop]} = "update" -o ${insofts[$aloop]} = "upgrade" ]; then
echo -e "\033[1;32m“${insofts[$aloop]}”开始安装......\033[0m"
$cmd_install ${insofts[$aloop]} -y
echo -e "\033[1;32m------------------------------------------------\033[0m"
else
echo -e "\033[1;32m“${insofts[$aloop]}”开始安装......\033[0m"
$cmd_install install ${insofts[$aloop]} -y
echo -e "\033[1;32m------------------------------------------------\033[0m"
fi
done
echo
echo -e "\033[1;32m===== <<安装gdutils依赖-nodejs和npm/安装配置gdutils>> =====\033[0m"
echo
$cmd_install install $cmd_install_rely -y
curl -sL $nodejs_curl | bash -
$cmd_install install nodejs -y
$cmd_install_rpm_build
git clone https://github.com/liaojack8/gd-utils-cht && cd gd-utils-cht
npm config set unsafe-perm=true
npm i
echo
echo -e "\033[1;32m★★★ 恭喜您!gdutils项目“TD查询转存”部分已部署完成请上传sa到“./gd-utils-cht/sa/”目录下完成最后的配置 ★★★\033[0m"
echo
cd ~
rm -f gdutilscs.sh
###########################gdutils功能建议##################################
# 本部分是对gdutils项目的建议因为我主要用的是查询功能所以以下建议只涉及查询功能
# 1-把以下参数放入配置文件设置sa存放路径
# 2-改sa“随机”使用为“顺序”分组使用
# 3-增加输出模式,可以用命令行后带参数选择,具体模式建议:
# ①按一级或者二级文件夹显示数量大小
# ②可以一次性统计多个磁盘并且输出单个磁盘文件数和大小以及几个磁盘总和
# ③获取id对应的文件夹名或者磁盘明保存数据库给个命令能够查询历史记录汇总或者指定汇总
# 4-查询过程中输出方式不要每次都输出一次,可以固定+数字变化
# 5-命令参数可加在ID前或后如果非要固定一种的话就加在ID之前
# 6-命令行也改为默认sa模式
############################################################################

View File

@ -1,386 +1,228 @@
#!/bin/bash #!"C:\Program Files\Git\usr\bin\bash"
echo #/bin/bash
echo -e "\033[1;32m===== <<gdutils项目一件部署脚本要求及说明>> =====\033[0m" #颜色变量,因为颜色字符复杂,定义一个函数表示其代码字符串能够很好容错,更改也方便
echo -e "\033[1;32m---------------[ v1.0 by oneking ]---------------\033[0m" color_yellow='\033[1;32m'
echo -e "\033[32m 1.\033[0m 本脚本是针对TG大神@viegg的gdutils项目一键部署脚本;" color_end='\033[0m'
echo -e "\033[32m 2.\033[0m 脚本包括“TD盘VPS上查询转存部署”和“Telegram机器人部署”两部分"
echo -e "\033[32m 3.\033[0m 本脚本适应CentOS/Debian/Ubuntu三种操作系统自动识别、自动选择对应分支一键安装部署" echo -e "\n$color_yellow===== <<gdutils项目一件部署脚本要求及说明>> =====$color_end"
echo -e "\033[32m 4.\033[0m 三步即可完成部署上传脚本到VPS → 设置脚本执行权限 → 运行" echo -e "$color_yellow---------------[ v2.1 by oneking ]---------------$color_end"
echo -e "\033[32m 5.\033[0m 在TG上注册好机器人并取得并记录下该机器人TOKEN" echo -e "$color_yellow 01.$color_end 本脚本是针对TG大神@viegg的gdutils项目一键部署脚本;"
echo -e "\033[32m 6.\033[0m 拥有一个域名绑定到cloudflare解析到该机器人所在服务器IP" echo -e "$color_yellow 02.$color_end 脚本包括“TD盘VPS上查询转存部署”和“Telegram机器人部署”两部分"
echo -e "\033[1;32m------------------------------------------------\033[0m" echo -e "$color_yellow 03.$color_end 本脚本适应CentOS/Debian/Ubuntu三种操作系统自动识别、自动选择对应分支一键安装部署"
read -s -n1 -p "★★★ 如已做好以上准备或不需要安装Telegram机器人请按任意键继续如未做好准备请按“Ctrl+c”终止脚本 ★★★" echo -e "$color_yellow 04.$color_end 三步即可完成部署上传脚本到VPS → 设置脚本执行权限 → 运行"
echo echo -e "$color_yellow 05.$color_end 准备工作一在TG上注册好机器人取得并记录下该机器人TOKEN"
echo -e "\033[1;32m------------------------------------------------\033[0m" echo -e "$color_yellow 06.$color_end 准备工作二拥有一个域名绑定到cloudflare解析到该机器人所在服务器IP"
echo -e "$color_yellow 07.$color_end 准备工作三:向机器人@userinfobot获取个人TG账号ID并记录"
echo -e "$color_yellow 08.$color_end 准备工作四注册好一个Google team drive加入sa并记录下该盘ID"
echo -e "$color_yellow 09.$color_end 经测试可用完美安装系统Centos 7/8 debian 9/10 ubuntu 16.04/18.04/19.10/20.04"
echo -e "$color_yellow 10.$color_end 部署过程中有任何问题请把“错误截图”“部署VPS系统名称版本”信息发给TGonekings 或 vitaminor@gmail.com"
echo -e "$color_yellow------------------------------------------------$color_end"
read -s -n1 -p "★★★ 如已做好以上[5/6/7/8]准备或不需要安装Telegram机器人请按任意键开始部署如未做好准备请按“Ctrl+c”终止脚本 ★★★"
echo -e "\n$color_yellow------------------------------------------------$color_end"
# 识别操作系统 # 识别操作系统
aNAME=`uname -a` aNAME="$(uname -a)"
bNAME="$(cat /proc/version)"
cNAME="$(lsb_release -a)"
if [ -f "/etc/redhat-release" ]; then if [ -f "/etc/redhat-release" ]; then
if [[ `cat /etc/redhat-release` =~ "CentOS" ]];then if [[ $(cat /etc/redhat-release) =~ "CentOS" ]]; then
os="CentOS" os="CentOS"
fi fi
elif [[ $aNAME =~ "Debian" ]];then elif [ "$aNAME"=~"Debian" -o "$bNAME"=~"Debian" -o "$cNAME"=~"Debian" ]; then
os="Debian" os="Debian"
elif [[ $aNAME =~ "CentOS" ]];then elif [ "$aNAME"=~"Ubuntu" -o "$bNAME"=~"Ubuntu" -o "$cNAME"=~"Ubuntu" ]; then
os="Debian"
elif [ "$aNAME"=~"CentOS" -o "$bNAME"=~"CentOS" -o "$cNAME"=~"CentOS" ]; then
os="CentOS" os="CentOS"
elif [[ $aNAME =~ "Ubuntu" ]];then elif [ "$aNAME"=~"Darwin" -o "$bNAME"=~"Darwin" -o "$cNAME"=~"Darwin" ]; then
os="Ubuntu"
elif [[ $aNAME =~ "Darwin" ]];then
os="mac" os="mac"
else else
os=$aNAME os="$bNAME"
fi fi
# insofts为软件数组,里面的元数为你需要安装的软件 # 需要安装的软件工具及依赖
insofts=(epel-release update upgrade wget curl git unzip zip sudo python3-distutils python3 python3-pip) insofts=(epel-release update upgrade wget curl git unzip zip python3-distutils python3 python3-pip)
# 具体业务逻辑 #根据操作系统设置变量
os_debian(){ if [[ "$os" = "Debian" ]]; then
cmd_install="apt-get" #安装命令
cmd_install_rely="build-essential" #c++编译环境
nodejs_curl="https://deb.nodesource.com/setup_10.x" #nodejs下载链接
cmd_install_rpm_build="" #安装rpm-build
nginx_conf="/etc/nginx/sites-enabled/" #nginx配置文件存放路径
rm_nginx_default="rm -f /etc/nginx/sites-enabled/default" #删除default
echo echo
echo -e "\033[1;32m===== <<升级系统/更新软件/安装工具/安装依赖>> =====\033[0m" echo -e "$color_yellow★★★★★ 您的操作系统为Debian即将为你开始部署gdutils项目 ★★★★★$color_end"
echo elif [[ "$os" = "Ubuntu" ]]; then
for(( aloop=0;aloop<${#insofts[@]};aloop++ )) do cmd_install="sudo apt-get"
if [ ${insofts[$aloop]} = "update" -o ${insofts[$aloop]} = "upgrade" ];then cmd_install_rely="build-essential"
echo -e "\033[1;32m“${insofts[$aloop]}”开始安装......\033[0m" nodejs_curl="https://deb.nodesource.com/setup_10.x"
sudo apt-get ${insofts[$aloop]} -y cmd_install_rpm_build=""
echo -e "\033[1;32m------------------------------------------------\033[0m" nginx_conf="/etc/nginx/sites-enabled/"
rm_nginx_default="rm -f /etc/nginx/sites-enabled/default"
echo -e "\n$color_yellow★★★★★ 您的操作系统为Ubuntu即将为你开始部署gdutils项目 ★★★★★$color_end"
elif [[ "$os" = "CentOS" ]]; then
cmd_install="yum"
cmd_install_rely="gcc-c++ make"
nodejs_curl="https://rpm.nodesource.com/setup_10.x"
cmd_install_rpm_build="yum install rpm-build -y"
nginx_conf="/etc/nginx/conf.d/"
rm_nginx_default=""
echo -e "\n$color_yellow★★★★★ 您的操作系统为Centos即将为你开始部署gdutils项目 ★★★★★$color_end"
elif [[ "$os" = "mac" ]]; then
echo -e "\n$color_yellow★★★★★ 您的操作系统为MacOS请在图形界面手动安装 ★★★★★$color_end\n" && exit
else else
echo -e "\033[1;32m“${insofts[$aloop]}”开始安装......\033[0m" echo -e "\n$color_yellow unknow os $OS, exit! $color_end" && exit
sudo apt-get install ${insofts[$aloop]} -y fi
echo -e "\033[1;32m------------------------------------------------\033[0m"
echo -e "\n$color_yellow===== <<升级系统/更新软件/安装工具/安装依赖>> =====$color_end\n"
#安装which和sudo
if [[ "$(which which)" == "" ]]; then
echo -e "$color_yellow“which”开始安装......$color_end"
$cmd_install install which -y
echo -e "$color_yellow------------------------------------------------$color_end"
elif [[ "$(which sudo)" == "" ]]; then
echo -e "$color_yellow“sudo”开始安装......$color_end"
$cmd_install install sudo -y
echo -e "$color_yellow------------------------------------------------$color_end"
fi
#安装工具和依赖
for ((aloop = 0; aloop < ${#insofts[@]}; aloop++)); do
if [ ${insofts[$aloop]} = "update" -o ${insofts[$aloop]} = "upgrade" ]; then
echo -e "$color_yellow${insofts[$aloop]}”开始安装......$color_end"
$cmd_install ${insofts[$aloop]} -y
echo -e "$color_yellow------------------------------------------------$color_end"
else
echo -e "$color_yellow${insofts[$aloop]}”开始安装......$color_end"
$cmd_install install ${insofts[$aloop]} -y
echo -e "$color_yellow------------------------------------------------$color_end"
fi fi
done done
echo echo -e "\n$color_yellow===== <<安装gdutils依赖-nodejs和npm/安装配置gdutils>> =====$color_end\n"
echo -e "\033[1;32m===== <<安装gdutils依赖-nodejs和npm/安装配置gdutils>> =====\033[0m"
echo $cmd_install install $cmd_install_rely -y
sudo apt-get install build-essential -y curl -sL $nodejs_curl | bash -
curl -sL https://deb.nodesource.com/setup_10.x | bash - $cmd_install install nodejs -y
sudo apt-get install -y nodejs $cmd_install_rpm_build
git clone https://github.com/iwestlin/gd-utils && cd gd-utils git clone https://github.com/iwestlin/gd-utils && cd gd-utils
npm config set unsafe-perm=true npm config set unsafe-perm=true
npm i npm i
echo echo -e "\n$color_yellow★★★ 恭喜您!gdutils统计转存系统已经正确安装完成请上传sa到“./gd-utils/sa/”目录下完成最后的配置 ★★★$color_end\n"
echo -e "\033[1;32m★★★ 恭喜您!gdutils统计转存系统已经正确安装完成请上传sa到“./gd-utils/sa/”目录下完成最后的部署 ★★★\033[0m"
echo
################################################################################################# #################################################################################################
echo -e "\033[1;32m----------------------------------------------------------\033[0m" echo -e "$color_yellow----------------------------------------------------------$color_end"
read -s -n1 -p "★★★ 下面将部署Telegram机器人请确保准备所需条件已准备好请按任意键继续如未做好准备请按“Ctrl+c”终止部署机器人 ★★★" read -s -n1 -p "★★★ 下面将部署Telegram机器人请确保准备所需条件已准备好按任意键开始部署机器人如未做好准备请按“Ctrl+c”终止部署机器人 ★★★"
echo
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
echo echo -e "\n$color_yellow----------------------------------------------------------$color_end"
echo -e "\033[1;32m ===== <<开始部署gdutils查询转存TG机器人>> ===== \033[0m"
echo echo -e "\n$color_yellow ===== <<开始部署gdutils查询转存TG机器人>> ===== $color_end\n"
#输入“机器人token/TG账号ID/域名/转存目的盘ID”
read -p """请输入机器人token并回车
Your Bot Token =>:""" YOUR_BOT_TOKEN
#判断token是否输入正确
while [[ "${#YOUR_BOT_TOKEN}" != 46 ]]; do
echo -e "$color_yellow★★★ 机器人TOKEN输入不正确请重新输入或按“Ctrl+C”结束安装 ★★★$color_end"
read -p """请输入机器人token并回车 read -p """请输入机器人token并回车
Your Bot Token =>:""" YOUR_BOT_TOKEN Your Bot Token =>:""" YOUR_BOT_TOKEN
read -p """请输入使用机器人的telegram账号名(“@”后面部分)并回车
Your Telegram Name =>:""" YOUR_TELEGRAM_NAME
read -p """请为WEB服务设置一个名称(填写你的域名格式:***.***.com)并回车
Your Bot Server Name =>:""" YOUR_BOT_SERVER_NAME
read -p """请输入在cloudflare上设置的网址(填写你的完整域名网址以“https”开头)并回车
Your Website =>:""" YOUR_WEBSITE
echo
cd ~ &&
sed -i "s/bot_token/$YOUR_BOT_TOKEN/g" ./gd-utils/config.js
sed -i "s/your_tg_username/$YOUR_TELEGRAM_NAME/g" ./gd-utils/config.js
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
echo -e "\033[1;32m“进程守护程序pm2”开始安装......\033[0m"
cd /root/gd-utils &&
npm i pm2 -g && pm2 l
echo -e "\033[1;32m启动守护进程......\033[0m"
pm2 start server.js
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
echo -e "\033[1;32m“nginx”开始安装......\033[0m"
cd ~ &&
sudo apt-get install nginx -y
echo
echo -e "\033[1;32m===== <<配置nginx服务>> ===== \033[0m"
echo
echo -e "\033[1;32m“nginx”起一个web服务......\033[0m"
cd /etc/nginx/sites-enabled/
echo "server {
listen 80;
server_name $YOUR_BOT_SERVER_NAME;
location / {
proxy_pass http://127.0.0.1:23333/;
}
}" > /etc/nginx/sites-enabled/gdutilsbot &&
rm -f /etc/nginx/sites-enabled/default
ls &&
nginx -t &&
nginx -c /etc/nginx/nginx.conf &&
nginx -s reload &&
netstat -tulpen
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
echo -e "\033[1;32m“检查网站是否部署成功”......\033[0m"
curl $YOUR_WEBSITE/api/gdurl/count\?fid=124pjM5LggSuwI1n40bcD5tQ13wS0M6wg
echo
echo -e "\033[1;32m设置Webhook服务......\033[0m"
print_webhook=`curl -F "url=$YOUR_WEBSITE/api/gdurl/tgbot" "https://api.telegram.org/bot$YOUR_BOT_TOKEN/setWebhook"`
echo
# 判断反向代理是否部署成功
if [[ $print_webhook =~ "true" ]];then
echo -e "\033[1;32m★★★ 恭喜你GoogleDrive查询转存机器人部署成功请回到TG界面给bot发送个“/help”获取使用帮助 ★★★\033[0m"
else
echo -e "\033[32m★★★ 很遗憾!机器人设置失败,请返回检查网站是否部署成功,并重复本安装过程 ★★★\033[0m", exit!
fi
cd ~
rm -f gdutils.sh
}
os_ubuntu(){
echo
echo -e "\033[1;32m===== <<升级系统/更新软件/安装工具/安装依赖>> =====\033[0m"
echo
for(( aloop=0;aloop<${#insofts[@]};aloop++ )) do
if [ ${insofts[$aloop]} = "update" -o ${insofts[$aloop]} = "upgrade" ];then
echo -e "\033[1;32m“${insofts[$aloop]}”开始安装......\033[0m"
sudo apt-get ${insofts[$aloop]} -y
echo -e "\033[1;32m------------------------------------------------\033[0m"
else
echo -e "\033[1;32m“${insofts[$aloop]}”开始安装......\033[0m"
sudo apt-get install ${insofts[$aloop]} -y
echo -e "\033[1;32m------------------------------------------------\033[0m"
fi
done done
echo read -p """请输入你的域名(在cloudflare上解析到你机器人所在VPS的域名格式bot.abc.com)并回车
echo -e "\033[1;32m===== <<安装gdutils依赖-nodejs和npm/安装配置gdutils>> =====\033[0m" Your Domain Name =>:""" YOUR_DOMAIN_NAME
echo #判断域名是否正确
sudo apt-get install build-essential -y while [[ "$YOUR_DOMAIN_NAME" =~ "http" ]]; do
curl -sL https://deb.nodesource.com/setup_10.x | bash - echo -e "$color_yellow★★★ “Your Domain Name”输入错误应该输入你在cloudflare上解析的域名且不包含“http”请重新输入或按“Ctrl+C”结束安装 ★★★$color_end"
sudo apt-get install -y nodejs read -p """请输入你的域名(在cloudflare上解析到你机器人所在VPS的域名格式bot.abc.com)并回车
git clone https://github.com/iwestlin/gd-utils && cd gd-utils Your Domain Name =>:""" YOUR_DOMAIN_NAME
npm config set unsafe-perm=true
npm i
echo
echo -e "\033[1;32m★★★ 恭喜您!gdutils统计转存系统已经正确安装完成请上传sa到“./gd-utils/sa/”目录下完成最后的部署 ★★★\033[0m"
echo
#################################################################################################
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
read -s -n1 -p "★★★ 下面将部署Telegram机器人请确保准备所需条件已准备好请按任意键继续如未做好准备请按“Ctrl+c”终止部署机器人 ★★★"
echo
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
echo
echo -e "\033[1;32m ===== <<开始部署gdutils查询转存TG机器人>> ===== \033[0m"
echo
read -p """请输入机器人token并回车
Your Bot Token =>:""" YOUR_BOT_TOKEN
read -p """请输入使用机器人的telegram账号名(“@”后面部分)并回车
Your Telegram Name =>:""" YOUR_TELEGRAM_NAME
read -p """请为WEB服务设置一个名称(填写你的域名格式:***.***.com)并回车
Your Bot Server Name =>:""" YOUR_BOT_SERVER_NAME
read -p """请输入在cloudflare上设置的网址(填写你的完整域名网址以“https”开头)并回车
Your Website =>:""" YOUR_WEBSITE
echo
cd ~ &&
sed -i "s/bot_token/$YOUR_BOT_TOKEN/g" ./gd-utils/config.js
sed -i "s/your_tg_username/$YOUR_TELEGRAM_NAME/g" ./gd-utils/config.js
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
echo -e "\033[1;32m“进程守护程序pm2”开始安装......\033[0m"
cd /root/gd-utils &&
npm i pm2 -g && pm2 l
echo -e "\033[1;32m启动守护进程......\033[0m"
pm2 start server.js
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
echo -e "\033[1;32m“nginx”开始安装......\033[0m"
cd ~ &&
sudo apt-get install nginx -y
echo
echo -e "\033[1;32m===== <<配置nginx服务>> ===== \033[0m"
echo
echo -e "\033[1;32m“nginx”起一个web服务......\033[0m"
cd /etc/nginx/sites-enabled/
echo "server {
listen 80;
server_name $YOUR_BOT_SERVER_NAME;
location / {
proxy_pass http://127.0.0.1:23333/;
}
}" > /etc/nginx/sites-enabled/gdutilsbot &&
rm -f /etc/nginx/sites-enabled/default
ls &&
nginx -t &&
nginx -c /etc/nginx/nginx.conf &&
nginx -s reload &&
netstat -tulpen
echo -e "\033[1;32m----------------------------------------------------------\033[0m"
echo -e "\033[1;32m“检查网站是否部署成功”......\033[0m"
curl $YOUR_WEBSITE/api/gdurl/count\?fid=124pjM5LggSuwI1n40bcD5tQ13wS0M6wg
echo
echo -e "\033[1;32m设置Webhook服务......\033[0m"
print_webhook=`curl -F "url=$YOUR_WEBSITE/api/gdurl/tgbot" "https://api.telegram.org/bot$YOUR_BOT_TOKEN/setWebhook"`
echo
# 判断反向代理是否部署成功
if [[ $print_webhook =~ "true" ]];then
echo -e "\033[1;32m★★★ 恭喜你GoogleDrive查询转存机器人部署成功请回到TG界面给bot发送个“/help”获取使用帮助 ★★★\033[0m"
else
echo -e "\033[32m★★★ 很遗憾!机器人设置失败,请返回检查网站是否部署成功,并重复本安装过程 ★★★\033[0m", exit!
fi
cd ~
rm -f gdutils.sh
}
os_centos(){
echo
echo -e "\033[1;32m===== <<升级系统/更新软件/安装工具/安装依赖>> =====\033[0m"
echo
for(( aloop=0;aloop<${#insofts[@]};aloop++ )) do
if [ ${insofts[$aloop]} = "update" -o ${insofts[$aloop]} = "upgrade" ];then
echo -e "\033[1;32m“${insofts[$aloop]}”开始安装......\033[0m"
yum ${insofts[$aloop]} -y
echo -e "\033[1;32m------------------------------------------------\033[0m"
else
echo -e "\033[1;32m“${insofts[$aloop]}”开始安装......\033[0m"
yum install ${insofts[$aloop]} -y
echo -e "\033[1;32m------------------------------------------------\033[0m"
fi
done done
echo read -p """请输入使用机器人的telegram账号ID(获取ID机器人@userinfobot)并回车
echo -e "\033[1;32m===== <<安装gdutils依赖-nodejs和npm/安装配置gdutils>> =====\033[0m" Your Telegram ID =>:""" YOUR_TELEGRAM_ID
echo #判断telegram ID是否正确(通过判断是不是纯数字)
yum install gcc-c++ make -y until [[ $YOUR_TELEGRAM_ID =~ ^-?[0-9]+$ ]]; do
curl -sL https://rpm.nodesource.com/setup_10.x | bash - echo -e "$color_yellow★★★ 您的TG账号ID输入不正确请重新输入或按“Ctrl+C”结束安装 ★★★$color_end"
yum install nodejs -y read -p """请输入使用机器人的telegram账号ID(获取ID机器人@userinfobot)并回车
yum install rpm-build -y Your Telegram ID =>:""" YOUR_TELEGRAM_ID
git clone https://github.com/iwestlin/gd-utils && cd gd-utils done
npm config set unsafe-perm=true
npm i
echo read -p """请输入转存默认目的地团队盘ID(不指定转存目的地默认改地址脚本强制要求输入团队盘ID)并回车
echo -e "\033[1;32m★★★ 恭喜您!gdutils统计转存系统已经正确安装完成请上传sa到“./gd-utils/sa/”目录下完成最后的部署 ★★★\033[0m" Your Google Team Drive ID =>:""" YOUR_GOOGLE_TEAM_DRIVE_ID
echo #判断google team drive ID是否正确团队盘ID长度19位
while [[ "${#YOUR_GOOGLE_TEAM_DRIVE_ID}" != 19 ]]; do
################################################################################################# echo -e "$color_yellow★★★ 您的Google team drive ID输入不正确请重新输入或按“Ctrl+C”结束安装 ★★★$color_end"
echo -e "\033[1;32m------------------------------------------------\033[0m" read -p """请输入转存默认目的地ID(不指定转存目的地默认该地址脚本强制要求输入团队盘ID)并回车
read -s -n1 -p "★★★ 下面将部署Telegram机器人请确保准备所需条件已准备好请按任意键继续如未做好准备请按“Ctrl+c”终止部署机器人 ★★★" Your Google Team Drive ID =>:""" YOUR_GOOGLE_TEAM_DRIVE_ID
echo done
echo -e "\033[1;32m------------------------------------------------\033[0m"
echo
echo -e "\033[1;32m===== <<开始部署gdutils查询转存TG机器人>> =====\033[0m"
echo
read -p """请输入机器人token并回车
Your Bot Token =>:""" YOUR_BOT_TOKEN
read -p """请输入使用机器人的telegram账号名(“@”后面部分)并回车
Your Telegram Name =>:""" YOUR_TELEGRAM_NAME
read -p """请为WEB服务设置一个名称(填写你的域名格式:***.***.com)并回车
Your Bot Server Name =>:""" YOUR_BOT_SERVER_NAME
read -p """请输入在cloudflare上设置的网址(填写你的完整域名网址以“https”开头)并回车
Your Website =>:""" YOUR_WEBSITE
echo
echo -e "\033[1;32m------------------------------------------------\033[0m"
cd ~ && cd ~ &&
sed -i "s/bot_token/$YOUR_BOT_TOKEN/g" ./gd-utils/config.js sed -i "s/bot_token/$YOUR_BOT_TOKEN/g" ./gd-utils/config.js &&
sed -i "s/your_tg_username/$YOUR_TELEGRAM_NAME/g" ./gd-utils/config.js sed -i "s/your_tg_username/$YOUR_TELEGRAM_ID/g" ./gd-utils/config.js &&
sed -i "s/DEFAULT_TARGET = ''/DEFAULT_TARGET = '$YOUR_GOOGLE_TEAM_DRIVE_ID'/g" ./gd-utils/config.js
echo -e "$color_yellow----------------------------------------------------------$color_end"
echo -e "\033[1;32m“进程守护程序pm2”开始安装......\033[0m" echo -e "$color_yellow“进程守护程序pm2”开始安装......$color_end"
cd /root/gd-utils && cd /root/gd-utils &&
npm i pm2 -g && pm2 l npm i pm2 -g && pm2 l
echo -e "$color_yellow启动守护进程......$color_end"
echo -e "\033[1;32m启动守护进程......\033[0m"
pm2 start server.js pm2 start server.js
echo -e "\033[1;32m------------------------------------------------\033[0m" echo -e "$color_yellow----------------------------------------------------------$color_end"
echo -e "\033[1;32m“nginx”开始安装......\033[0m" echo -e "$color_yellow“nginx”开始安装......$color_end"
cd ~ && cd ~ &&
#yum install -y pcre pcre-devel && $cmd_install install nginx -y
#yum install -y zlib zlib-devel &&
#yum install -y openssl openssl-devel &&
yum install nginx -y
echo -e "\033[1;32m------------------------------------------------\033[0m"
echo echo
echo -e "\033[1;32m===== <<配置nginx服务>> =====\033[0m" echo -e "$color_yellow===== <<配置nginx服务>> ===== $color_end"
echo echo
echo -e "$color_yellow“nginx”起一个web服务......$color_end"
echo -e "\033[1;32m“nginx”起一个web服务......\033[0m" cd $nginx_conf
cd /etc/nginx/conf.d/
# nginx起一个web服务
echo "server { echo "server {
listen 80; listen 80;
server_name $YOUR_BOT_SERVER_NAME; server_name $YOUR_DOMAIN_NAME;
location / { location / {
proxy_pass http://127.0.0.1:23333/; proxy_pass http://127.0.0.1:23333/;
} }
}" > /etc/nginx/conf.d/gdutilsbot.conf && }" >${nginx_conf}gdutilsbot.conf &&
$rm_nginx_default
ls && ls &&
nginx -t && nginx -t &&
nginx -c /etc/nginx/nginx.conf && nginx -c /etc/nginx/nginx.conf &&
nginx -s reload && nginx -s reload &&
netstat -tulpen netstat -tulpen
echo -e "\033[1;32m------------------------------------------------\033[0m" echo -e "$color_yellow----------------------------------------------------------$color_end"
echo -e "\033[1;32m检查网站是否部署成功......\033[0m" echo -e "$color_yellow“检查网站是否部署成功”......$color_end"
curl $YOUR_WEBSITE/api/gdurl/count\?fid=124pjM5LggSuwI1n40bcD5tQ13wS0M6wg curl $YOUR_DOMAIN_NAME/api/gdurl/count\?fid=124pjM5LggSuwI1n40bcD5tQ13wS0M6wg
echo echo -e "\n$color_yellow设置Webhook服务......$color_end"
print_webhook=$(curl -F "url=https://$YOUR_DOMAIN_NAME/api/gdurl/tgbot" "https://api.telegram.org/bot$YOUR_BOT_TOKEN/setWebhook")
echo -e "\033[1;32m设置Webhook服务......\033[0m"
print_webhook=`curl -F "url=$YOUR_WEBSITE/api/gdurl/tgbot" "https://api.telegram.org/bot$YOUR_BOT_TOKEN/setWebhook"`
echo echo
# 判断反向代理是否部署成功 # 判断反向代理是否部署成功
if [[ $print_webhook =~ "true" ]]; then if [[ $print_webhook =~ "true" ]]; then
echo -e "\033[1;32m★★★ 恭喜你GoogleDrive查询转存机器人部署成功请回到TG界面给bot发送个“/help”获取使用帮助 ★★★\033[0m" echo -e "$color_yellow★★★ 恭喜你GoogleDrive查询转存机器人部署成功请回到TG界面给bot发送个“/help”获取使用帮助 ★★★$color_end"
else else
echo -e "\033[32m★★★ 很遗憾!机器人设置失败,请返回检查网站是否部署成功,并重复本安装过程 ★★★\033[0m", exit! echo -e "$color_yellow★★★ 很遗憾!机器人设置失败,请返回检查网站是否部署成功,并重复本安装过程 ★★★$color_end", exit!
fi fi
nginx -t && nginx -s reload && \n\n
cd ~ cd ~
rm -f gdutils.sh rm -f gdutilsinstall.sh
} ###########################gdutils功能建议##################################
# 本部分是对gdutils项目的建议因为我主要用的是查询功能所以以下建议只涉及查询功能
# 不同的操作系统选择执行不同的分支 # 1-把以下参数放入配置文件设置sa存放路径
case "$os" in # 2-改sa“随机”使用为“顺序”分组使用
Ubuntu) # 3-增加输出模式,可以用命令行后带参数选择,具体模式建议:
echo # ①按一级或者二级文件夹显示数量大小
echo -e "\033[1;32m==<<您的操作系统为Ubuntu即将为你开始部署gdutils项目>>==\033[0m" # ②可以一次性统计多个磁盘并且输出单个磁盘文件数和大小以及几个磁盘总和
os_ubuntu # ③获取id对应的文件夹名或者磁盘明保存数据库给个命令能够查询历史记录汇总或者指定汇总
;; # 4-查询过程中输出方式不要每次都输出一次,可以固定+数字变化
CentOS) # 5-命令参数可加在ID前或后如果非要固定一种的话就加在ID之前
echo # 6-命令行也改为默认sa模式
echo -e "\033[1;32m==<<您的操作系统为Centos即将为你开始部署gdutils项目>>==\033[0m" ############################################################################
os_centos
;;
Debian)
echo
echo -e "\033[1;32m==<<您的操作系统为Debian即将为你开始部署gdutils项目>>==\033[0m"
os_debian
;;
mac)
echo
echo -e "\033[1;32m==<<您的操作系统为MacOS请在图形界面手动安装>>==\033[0m"
echo
;;
*)
echo -e "\033[1;32m unknow os $OS, exit! \033[0m"
;;
esac

BIN
pic/example2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
pic/example3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

286
readme.md
View File

@ -1,229 +1,155 @@
# Google Drive 百宝箱 # gd-utils-cht
> 不只是最快的 google drive 拷贝工具 [与其他工具的对比](./compare.md) > 不只是最快的 google drive 拷貝工具 [與其他工具的對比](./compare.md)
## 一键安装脚本 > 我的readme可能不夠完全, 主要寫上我更新、修改的內容, 具體說明還是看[這邊](https://github.com/iwestlin/gd-utils)和[這邊](https://github.com/vitaminx/gd-utils)吧
- 首先准备好以下两个条件: ## 更新紀錄
- 在Telegram上注册好机器人并取得并记录下该机器人TOKEN ### 2020.07.07
- 一个域名在cloudflare解析到该机器人所在VPS的IP - 參照原作者@iwestlin更新tg.js及gd.js
- 准备好以上两个条件后复制以下全部内容粘贴到VPS命令行窗口回车即可 - 整體繁體化, 介面部分
- 新增用戶可以在config.js自訂按鈕顯示的個數(每列), 可設定為1或2
### 2020.07.06
- 部分繁體中文化
- 執行/task命令時, 會回傳完成度百分比
- 複製完成時, 跳出的通知會顯示文件大小
## tg_bot 修改部分
- 執行/task命令時, 會回傳完成度百分比
![](./pic/example2.png)
- 複製完成時, 跳出的通知會顯示文件大小
![](./pic/example3.png)
> 這邊說一下我用的服務及配置(免費配置): always-free gcp Compute Engine + zerossl + 免費的domain hosting
>注意我的配置沒有用到cloudflare
## 一鍵安裝腳本(感謝 腳本製作者 [@vitaminx](https://github.com/vitaminx))
- 這邊的安裝腳本我有稍作修改 與fork過來的原版不一樣
- 不使用cloudflare解析
- ssl另外配置在nginx服務當中(後面會說明證書放置路徑)
- 具體安裝條件、限制請去參考[腳本原作者的專案](https://github.com/vitaminx/gd-utils)
- 這邊放了貼上就能用的命令
- gdutils項目一鍵部署腳本包括“查詢轉存”和“TG機器人”兩部分
``` ```
wget https://raw.githubusercontent.com/vitaminx/gd-utils/master/gdutilsinstall.sh && chmod +x gdutilsinstall.sh && ./gdutilsinstall.sh bash -c "$(curl -fsSL https://raw.githubusercontent.com/liaojack8/gd-utils-cht/master/gdutilsinstall.sh)"
``` ```
- 安装过程中需要输入一下四个参数: - gdutils項目一鍵部署腳本之“轉存查詢部分”
- 机器人TOKEN这个在Telegram里面找“@BotFather”注册即可获得
- 使用机器人的Telegram用户名在Telegram里面直接查看
- web服务名这是个是很重要的识别标志请设置为你的域名不含HTTP
- 域名网址全称你在cloudflare上解析到VPS的域名网址全称含HTTP
## demo
[https://drive.google.com/drive/folders/124pjM5LggSuwI1n40bcD5tQ13wS0M6wg](https://drive.google.com/drive/folders/124pjM5LggSuwI1n40bcD5tQ13wS0M6wg)
## 更新日志
[2020-06-30]
- 命令行操作时不换行输出进度信息同时将进度信息输出间隔调整为1秒
- 隐藏 timeout exceed 报错信息
## 重要更新2020-06-29
如果你遇到了以下几种问题,请务必阅读此节:
- 任务异常中断
- 命令行日志无限循环输出但进度不变
- 复制完发现丢文件
有不少网友遇到这些问题但是作者一直无法复现直到有tg网友发了张运行日志截图
![](./static/error-log.png)
报错日志的意思是找不到对应的目录ID这种情况会发生在SA没有对应目录的阅读权限的时候。
当进行server side copy时需要向Google的服务器提交要复制的文件ID和复制的位置也就是新创建的目录ID由于在请求时是随机选取的SA所以当选中没有权限的SA时这次拷贝请求没有对应目录的权限就会发生图中的错误。
**所以上述这些问题的源头是sa目录下混杂了没有权限的json文件**
以下是解决办法:
- 在项目目录下,执行 `git pull` 拉取最新代码
- 执行 `./validate-sa.js -h` 查看使用说明
- 选择一个你的sa拥有阅读权限的目录ID执行 `./validate-sa.js 你的目录ID`
程序会读取sa目录下所有json文件依次检查它们是否拥有对 `你的目录ID` 的阅读权限如果最后发现了无效的SA程序会提供选项允许用户将无效的sa json移动到特定目录。
将无效sa文件移动以后如果你使用了pm2启动需要 `pm2 reload server` 重启下进程。
操作示例: [https://drive.google.com/drive/folders/1iiTAzWF_v9fo_IxrrMYiRGQ7QuPrnxHf](https://drive.google.com/drive/folders/1iiTAzWF_v9fo_IxrrMYiRGQ7QuPrnxHf)
## 常见问题
下面是一些网友的踩坑心得,如果你配置的时候也不小心掉进坑里,可以进去找找有没有解决办法:
- [ikarosone 基于宝塔的搭建过程](https://www.ikarosone.top/archives/195.html)
- [@greathappyforest 踩的坑](doc/tgbot-appache2-note.md)
在命令行操作时如果输出 `timeout exceed` 这样的消息是正常情况不会影响最终结果因为程序对每个请求都有7次重试的机制。
如果timeout的消息比较多可以考虑降低并行请求数下文有具体方法。
复制结束后,如果最后输出的消息里有 `未读取完毕的目录ID`只需要在命令行执行上次同样的拷贝命令选continue即可继续。
如果你成功复制完以后统计新的文件夹链接发现文件数比源文件夹少说明Google正在更新数据库请给它一点时间。。一般等半小时再统计数据会比较完整。
如果你使用tg操作时发送拷贝命令以后/task 进度始终未开始(在复制文件数超多的文件夹时常会发生),是正常现象。
这是因为程序正在获取源文件夹的所有文件信息。它的运行机制严格按照以下顺序:
1、获取源文件夹所有文件信息
2、根据源文件夹的目录结构在目标文件夹创建目录
3、所有目录创建完成后开始复制文件
**如果源文件夹的文件数非常多(一百万以上),请一定在命令行进行操作**因为程序运行的时候会把文件信息保存在内存中文件数太多的话容易内存占用太多被nodejs干掉。可以像这样执行命令
``` ```
node --max-old-space-size=4096 count folder-id -S bash -c "$(curl -fsSL https://raw.githubusercontent.com/liaojack8/gd-utils-cht/master/gdutilscsinstall.sh)"
``` ```
这样进程就能最大占用4G内存了。 - gdutils項目一鍵部署腳本之“TG機器人部分”
```
bash -c "$(curl -fsSL https://raw.githubusercontent.com/liaojack8/gd-utils-cht/master/gdutilsbotinstall.sh)"
```
- 安裝過程中需要輸入一下四個參數:
- 機器人TOKEN這個在Telegram裡面找“@BotFather”註冊即可獲得
- Telegram用戶ID在Telegram裡面向機器人@userinfobot发送消息即可獲得
- Google team drive ID即為你轉存文件的預設地址腳本強制要求寫Google小組雲端硬碟ID
- 域名你在cloudflare上解析到VPS的域名格式abc.34513.com
- 腳本安裝問題請信息發給TGonekings 或 vitaminor@gmail.com
- 系統使用問題(如無法轉存、重啟連不上機器人等等)請聯系項目作者@vegg
- 測試可用完美安裝系統:
- Centos 7/8
- debian 9/10
- ubuntu 16.04/18.04/19.10/20.04
## 搭建步驟
1. 啟用一台主機, VPS、私人伺服器都行(私人伺服器如果沒有設定硬撥, 必須去路由器設定端口對應)
2. 確認固定ip, 或是用ddns服務 都行
3. 使用domain hosting服務解析到動態域名, 或新增A record指定到固定ip
4. 用domain hosting設定好的固定域名, 去申請ssl證書
5. 將證書放到對應路徑 /etc/ssl/certificate.crt 和 /etc/ssl/private.key
6. 設定完成後, 確認主機的端口開放
7. 執行安裝腳本, 就會自動以nginx起動服務, 特別設定了http轉https的跳轉
## 搭建过程 ## 功能簡介
[https://drive.google.com/drive/folders/1Lu7Cwh9lIJkfqYDIaJrFpzi8Lgdxr4zT](https://drive.google.com/drive/folders/1Lu7Cwh9lIJkfqYDIaJrFpzi8Lgdxr4zT)
需要注意的地方:
- 视频中省略了一个比较重要的步骤就是**从本地上传service account授权文件到 sa 目录下**tg机器人的所有操作都是通过sa授权的所以你们别忘了。。
- 视频中**nginx的配置里server_name就是你的二级域名需要和cloudflare的设置一样**的mybbbottt我分开录的视频所以没做到一致。
- 还有省略的步骤就是注册域名和把域名托管到cloudflare了这一步网上太多资料了甚至也有免费注册一年域名的地方 https://www.freenom.com/ ),具体教程大家搜搜看吧。
## 功能简介
本工具目前支持以下功能: 本工具目前支持以下功能:
- 统计任意您拥有相关权限的下同不再赘述目录的文件信息且支持以各种形式html, table, json导出。 - 統計任意您擁有相關權限的下同不再贅述目錄的文件信息且支持以各種形式html, table, json導出。
支持中断恢复,且统计过的目录(包括其所有子孙目录)信息会记录在本地数据库文件中gdurl.sqlite 支持中斷恢覆且統計過的目錄包括其所有子孫目錄信息會記錄在本地數據庫文件中gdurl.sqlite
请在本项目目录下命令行输入 `./count -h` 查看使用帮 請在本項目目錄下命令行輸入 `./count -h` 查看使用幫助
- 拷贝任意目录所有文件到您指定目录,同样支持中断恢复 - 拷貝任意目錄所有文件到您指定目錄,同樣支持中斷恢覆。
支持根据文件大小过滤,可输入 `./copy -h` 查看使用帮 支持根據文件大小過濾,可輸入 `./copy -h` 查看使用幫助
- 对任意目录进行去重删除同一目录下的md5值相同的文件只保留一个删除空目录 - 對任意目錄進行去重刪除同一目錄下的md5值相同的文件只保留一個刪除空目錄。
命令行输入 `./dedupe -h` 查看使用帮 命令行輸入 `./dedupe -h` 查看使用幫助
- 在 config.js 里完成相关配置后,可以将本项目部署在(可正常访问谷歌服务的)服务器上,提供 http api 文件统计接口 - 在 config.js 里完成相關配置後可以將本項目部署在可正常訪問Google服務的服務器上提供 http api 文件統計接口
- 支持 telegram bot配置完成后,上述功能均可通过 bot 进行操作 - 支持 telegram bot配置完成後上述功能均可通過 bot 進行操作
## 境配置 ## 環境配置
本工具需要安装nodejs客户端安装请访问[https://nodejs.org/zh-cn/download/](https://nodejs.org/zh-cn/download/),服务器安装可参考[https://github.com/nodesource/distributions/blob/master/README.md#debinstall](https://github.com/nodesource/distributions/blob/master/README.md#debinstall) 本工具需要安裝nodejs客戶端安裝請訪問[https://nodejs.org/zh-cn/download/](https://nodejs.org/zh-cn/download/),服務器安裝可參考[https://github.com/nodesource/distributions/blob/master/README.md#debinstall](https://github.com/nodesource/distributions/blob/master/README.md#debinstall)
议选择v12版本的node以防接下来安装依赖出错 建議選擇v12版本的node以防接下來安裝依賴出錯。
如果你的网络环境无法正常访问谷歌服务,需要先在命令行进行一些配置:(如果可以正常访问则跳过此节 如果你的網絡環境無法正常訪問Google服務需要先在命令行進行一些配置如果可以正常訪問則跳過此節
``` ```
http_proxy="YOUR_PROXY_URL" && https_proxy=$http_proxy && HTTP_PROXY=$http_proxy && HTTPS_PROXY=$http_proxy http_proxy="YOUR_PROXY_URL" && https_proxy=$http_proxy && HTTP_PROXY=$http_proxy && HTTPS_PROXY=$http_proxy
``` ```
请把`YOUR_PROXY_URL`替换成你自己的代理地址 請把`YOUR_PROXY_URL`替換成你自己的代理地址
## 依赖安装 ## 依賴安裝
- 命令行执行`git clone https://github.com/iwestlin/gd-utils && cd gd-utils` 克隆并切换到本项目文件夹 - 命令行執行`git clone https://github.com/iwestlin/gd-utils && cd gd-utils` 克隆並切換到本項目文件夾下
- **执行 `npm install --unsafe-perm=true --allow-root` 安装依赖**,部分依赖可能需要代理环境才能下载,所以需要上一步的配置 - **執行 `npm install --unsafe-perm=true --allow-root` 安裝依賴**,部分依賴可能需要代理環境才能下載,所以需要上一步的配置
如果在安装过程中发生报错请切换nodejs版本到v12再试。如果报错信息里有`Error: not found: make`之类的消息说明你的命令行环境缺少make命令可参考[这里](https://askubuntu.com/questions/192645/make-command-not-found)或直接google搜索`Make Command Not Found` 如果在安裝過程中发生報錯請切換nodejs版本到v12再試。如果報錯信息里有`Error: not found: make`之類的消息說明你的命令行環境缺少make命令可參考[這里](https://askubuntu.com/questions/192645/make-command-not-found)或直接google搜索`Make Command Not Found`
如果报错信息里有 `better-sqlite3`,先执`npm config set unsafe-perm=true` 如果報錯信息里有 `better-sqlite3`,先執`npm config set unsafe-perm=true`
`rm -rf node_module` 删掉依赖目录,最后再执行下`npm i`安装试试 `rm -rf node_module` 刪掉依賴目錄,最後再執行下`npm i`安裝試試
赖安装完成后,项目文件夹下会多出个`node_modules`目录,请不要删除它,接下来进行下一步配置。 賴安裝完成後,項目文件夾下會多出個`node_modules`目錄,請不要刪除它,接下來進行下一步配置。
## Service Account 配置 ## Service Account 配置
强烈建议使用service account后称SA, 获取方法请参见 [https://gsuitems.com/index.php/archives/13/](https://gsuitems.com/index.php/archives/13/#%E6%AD%A5%E9%AA%A42%E7%94%9F%E6%88%90serviceaccounts) 強烈建議使用service account後稱SA, 獲取方法請參見 [https://gsuitems.com/index.php/archives/13/](https://gsuitems.com/index.php/archives/13/#%E6%AD%A5%E9%AA%A42%E7%94%9F%E6%88%90serviceaccounts)
获取到 SA 的 json 文件后,请将其拷贝到 `sa` 目录 獲取到 SA 的 json 文件後,請將其拷貝到 `sa` 目錄
配置好 SA 以后,如果你不需要对个人盘下的文件进行操作,可跳过[个人帐号配置]这节,而且执行命令的时候,记得带上 `-S` 参数告诉程序使用SA授权进行操作。 配置好 SA 以後,如果你不需要對個人盤下的文件進行操作,可跳過[個人帳號配置]這節,而且執行命令的時候,記得帶上 `-S` 參數告訴程序使用SA授權進行操作。
## 个人帐号配置 ## 個人帳號配置
- 命令行执行 `rclone config file` 找到 rclone 的配置文件路径 - 命令行執行 `rclone config file` 找到 rclone 的配置文件路徑
- 打开这个配置文件 `rclone.conf`, 找到 `client_id`, `client_secret``refresh_token` 这三个变量,将其分别填入本项目下的 `config.js`需要注意这三个值必须被成对的英文引号包裹且引号后以英文逗号结尾也就是需要符合JavaScript的[对象语法](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Object_initializer) - 打開這個配置文件 `rclone.conf`, 找到 `client_id`, `client_secret``refresh_token` 這三個變量,將其分別填入本項目下的 `config.js`需要注意這三個值必須被成對的英文引號包裹且引號後以英文逗號結尾也就是需要符合JavaScript的[對象語法](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Object_initializer)
如果你没有配置过rclone可以搜索`rclone google drive 教程`完成相关配置。 如果你沒有配置過rclone可以搜索`rclone google drive 教程`完成相關配置。
如果你的`rclone.conf`里没有`client_id`和`client_secret`说明你配置rclone的时候默认用了rclone自己的client_id连rclone自己[都不建议这样做](https://github.com/rclone/rclone/blob/8d55367a6a2f47a1be7e360a872bd7e56f4353df/docs/content/drive.md#making-your-own-client_id),因为大家共享了它的接口调用限额,在使用高峰期可能会触发限制。 如果你的`rclone.conf`里沒有`client_id`和`client_secret`說明你配置rclone的時候默認用了rclone自己的client_id連rclone自己[都不建議這樣做](https://github.com/rclone/rclone/blob/8d55367a6a2f47a1be7e360a872bd7e56f4353df/docs/content/drive.md#making-your-own-client_id),因為大家共享了它的接口調用限額,在使用高峰期可能會觸发限制。
获取自己的clinet_id可以参见这两篇文章:[Cloudbox/wiki/Google-Drive-API-Client-ID-and-Client-Secret](https://github.com/Cloudbox/Cloudbox/wiki/Google-Drive-API-Client-ID-and-Client-Secret) 和 [https://p3terx.com/archives/goindex-google-drive-directory-index.html#toc_2](https://p3terx.com/archives/goindex-google-drive-directory-index.html#toc_2) 獲取自己的clinet_id可以參見這兩篇文章:[Cloudbox/wiki/Google-Drive-API-Client-ID-and-Client-Secret](https://github.com/Cloudbox/Cloudbox/wiki/Google-Drive-API-Client-ID-and-Client-Secret) 和 [https://p3terx.com/archives/goindex-google-drive-directory-index.html#toc_2](https://p3terx.com/archives/goindex-google-drive-directory-index.html#toc_2)
获取到client_id和client_secret后再次执行一遍`rclone config`创建一个新的remote**在配置过程中一定要填入你新获取的clinet_id和client_secret**,就能在`rclone.conf`里看到新获取的`refresh_token`了。**注意不能使用之前的refrest_token**因为它对应的是rclone自带的client_id 獲取到client_id和client_secret後再次執行一遍`rclone config`創建一個新的remote**在配置過程中一定要填入你新獲取的clinet_id和client_secret**,就能在`rclone.conf`里看到新獲取的`refresh_token`了。**注意不能使用之前的refrest_token**因為它對應的是rclone自帶的client_id
参数配置好以后,在命令行执行 `node check.js`,如果命令返回了你的谷歌硬盘根目录的数据,说明配置成功,可以开始使用本工具了。 參數配置好以後,在命令行執行 `node check.js`如果命令返回了你的Google雲端硬碟根目錄的數據說明配置成功可以開始使用本工具了。
## Bot配置 ## Bot配置
如果要使用 telegram bot 功能,需要一步配置。 如果要使用 telegram bot 功能,需要一步配置。
首先在 [https://core.telegram.org/bots#6-botfather](https://core.telegram.org/bots#6-botfather) 根据指示拿到 bot 的 token然后填入 config.js 中的 `tg_token`量。 首先在 [https://core.telegram.org/bots#6-botfather](https://core.telegram.org/bots#6-botfather) 根據指示拿到 bot 的 token然後填入 config.js 中的 `tg_token`量。
后获取自己的 telegram username这个username不是显示的名称而是tg个人网址后面的那串字符比如我的tg个人网址是 `https://t.me/viegg` ,用户名就是 `viegg`获取用户名的目的是在代码里配置白名单只允许特定的用户调用机器人。将username填入 `config.js`里的配置,像这样 後獲取自己的 telegram username這個username不是顯示的名稱而是tg個人網址後面的那串字符比如我的tg個人網址是 `https://t.me/viegg` ,用戶名就是 `viegg`獲取用戶名的目的是在代碼里配置白名單只允許特定的用戶調用機器人。將username填入 `config.js`里的配置,像這樣
`tg_whitelist: ['viegg']`,就代表只允许我自己使用这个机器人了。 `tg_whitelist: ['viegg']`,就代表只允許我自己使用這個機器人了。
如果想把机器人的使用权限分享给别的用户,只需要改成这样子: `tg_whitelist: ['viegg', '其他人的username']` 如果想把機器人的使用權限分享給別的用戶,只需要改成這樣子: `tg_whitelist: ['viegg', '其他人的username']`
接下来需要将代码部署到服务器上。 ## 補充說明
如果你一开始就是在服务器上配置的,可以直接执行`npm i pm2 -g` 在`config.js`文件里,還有另外的幾個參數:
如果你之前是在本地操作的,请在服务器上同样重复一遍,配置好相关参数后,执行`npm i pm2 -g`安装进程守护程序pm2
安装好pm2之后执行 `pm2 start server.js`,代码运行后会在服务器上监听`23333`端口。
如果你启动程序后想看运行日志,执行 `pm2 logs`
查看 pm2 守护的进程列表,执行 `pm2 l`
停止运行中的进程,执行 `pm2 stop 对应的进程名称`
**如果你修改了代码中的配置,需要 `pm2 reload server` 才能生效**。
> 如果你不想用nginx可以将`server.js`中的`23333`改成`80`直接监听80端口可能需要root权限
接下来可通过nginx或其他工具起一个web服务示例nginx配置
``` ```
server { // 單次請求多少毫秒未響應以後超時基準值若連續超時則下次調整為上次的2倍
listen 80;
server_name your.server.name;
location / {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://127.0.0.1:23333/;
}
}
```
配置好nginx后可以再套一层cloudflare具体教程请自行搜索。
检查网站是否部署成功可以命令行执行请将YOUR_WEBSITE_URL替换成你的网址
```
curl 'YOUR_WEBSITE_URL/api/gdurl/count?fid=124pjM5LggSuwI1n40bcD5tQ13wS0M6wg'
```
![](./static/count.png)
如果返回了这样的文件统计,说明部署成功了。
最后,在命令行执行(请将[YOUR_WEBSITE]和[YOUR_BOT_TOKEN]分别替换成你自己的网址和bot token
```
curl -F "url=[YOUR_WEBSITE]/api/gdurl/tgbot" 'https://api.telegram.org/bot[YOUR_BOT_TOKEN]/setWebhook'
```
这样,就将你的服务器连接上你的 telegram bot 了试着给bot发送个 `/help`,如果它回复给你使用说明,那就配置成功了。
## 补充说明
在`config.js`文件里,还有另外的几个参数:
```
// 单次请求多少毫秒未响应以后超时基准值若连续超时则下次调整为上次的2倍
const TIMEOUT_BASE = 7000 const TIMEOUT_BASE = 7000
// 最大超时设置比如某次请求第一次7s超时第二次14s第三次28s第四次56s第五次不是112s而是60s后续同理 // 最大超時設置比如某次請求第一次7s超時第二次14s第三次28s第四次56s第五次不是112s而是60s後續同理
const TIMEOUT_MAX = 60000 const TIMEOUT_MAX = 60000
const LOG_DELAY = 5000 // 日志输出时间间隔,单位毫秒 const LOG_DELAY = 5000 // 日志輸出時間間隔,單位毫秒
const PAGE_SIZE = 1000 // 每次网络请求读取目录下的文件数,数值越大,越有可能超时,不得超过1000 const PAGE_SIZE = 1000 // 每次網絡請求讀取目錄下的文件數數值越大越有可能超時不得超過1000
const RETRY_LIMIT = 7 // 如果某次请求失败,允许其重试的最大次数 const RETRY_LIMIT = 7 // 如果某次請求失敗,允許其重試的最大次數
const PARALLEL_LIMIT = 20 // 网络请求的并行数量,可根据网络环境调 const PARALLEL_LIMIT = 20 // 網絡請求的並行數量,可根據網絡環境調整
const DEFAULT_TARGET = '' // 必填,拷贝默认目的地ID如果不指定target则会拷贝到此处建议填写团队盘ID注意要用英文引号包裹 const DEFAULT_TARGET = '' // 必填拷貝默認目的地ID如果不指定target則會拷貝到此處建議填寫團隊盤ID注意要用英文引號包裹
``` ```
读者可根据各自情况进行调 讀者可根據各自情況進行調整
## 注意事 ## 注意事項
程序的原理是用了[google drive官方接口](https://developers.google.com/drive/api/v3/reference/files/list)递归获取目标文件夹下所有文件及其子文件夹信息,粗略来讲,某个目录下包含多少个文件夹,就至少需要这么多次请求才能统计完成。 程序的原理是調用了[google drive官方接口](https://developers.google.com/drive/api/v3/reference/files/list),遞歸獲取目標文件夾下所有文件及其子文件夾信息,粗略來講,某個目錄下包含多少個文件夾,就至少需要這麽多次請求才能統計完成。
目前尚不知道google是否会对接口做频率限制也不知道会不会影响google账号本身的安全。 目前尚不知道google是否會對接口做頻率限制也不知道會不會影響google賬號本身的安全。
**请勿滥用,后果自负** **請勿濫用,後果自負**

253
src/gd.js
View File

@ -8,23 +8,37 @@ const HttpsProxyAgent = require('https-proxy-agent')
const { GoogleToken } = require('gtoken') const { GoogleToken } = require('gtoken')
const handle_exit = require('signal-exit') const handle_exit = require('signal-exit')
const { AUTH, RETRY_LIMIT, PARALLEL_LIMIT, TIMEOUT_BASE, TIMEOUT_MAX, LOG_DELAY, PAGE_SIZE, DEFAULT_TARGET } = require('../config') const { AUTH, RETRY_LIMIT, PARALLEL_LIMIT, TIMEOUT_BASE, TIMEOUT_MAX, LOG_DELAY, PAGE_SIZE, DEFAULT_TARGET, SA_PATH } = require('../config')
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_FILES = fs.readdirSync(path.join(__dirname, '../sa')).filter(v => v.endsWith('.json')) const SA_BATCH_SIZE = 1000
const SA_FILES = fs.readdirSync(path.join(__dirname, SA_PATH)).filter(v => v.endsWith('.json'))
SA_FILES.flag = 0
let SA_TOKENS = get_sa_batch()
let SA_TOKENS = SA_FILES.map(filename => { setInterval(() => {
SA_FILES.flag = 0
SA_TOKENS = get_sa_batch()
}, 1000 * 3600 * 12)
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({ const gtoken = new GoogleToken({
keyFile: path.join(__dirname, '../sa', filename), keyFile: path.join(__dirname, '../sa', filename),
scope: ['https://www.googleapis.com/auth/drive'] 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 +52,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 +82,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)
@ -86,7 +102,7 @@ async function count ({ fid, update, sort, type, output, not_teamdrive, service_
if (!update) { if (!update) {
const info = get_all_by_fid(fid) const info = get_all_by_fid(fid)
if (info) { if (info) {
console.log('找到本地缓存数据,缓存时间', dayjs(info.mtime).format('YYYY-MM-DD HH:mm:ss')) console.log('找到本地快取資料,快取時間', dayjs(info.mtime).format('YYYY-MM-DD HH:mm:ss'))
const out_str = get_out_str({ info, type, sort }) const out_str = get_out_str({ info, type, sort })
if (output) return fs.writeFileSync(output, out_str) if (output) return fs.writeFileSync(output, out_str)
return console.log(out_str) return console.log(out_str)
@ -154,7 +170,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)
@ -185,8 +201,8 @@ async function walk_and_save ({ fid, not_teamdrive, update, service_account }) {
} catch (e) { } catch (e) {
console.error(e) console.error(e)
} }
console.log('\n信息获取完毕') console.log('\n資訊獲取完畢')
not_finished.length ? console.log('未读取完毕的目录ID', JSON.stringify(not_finished)) : console.log('所有目录读取完毕') not_finished.length ? console.log('未讀取完畢的目錄ID', JSON.stringify(not_finished)) : console.log('所有目錄讀取完畢')
clearInterval(loop) clearInterval(loop)
const smy = summary(result) const smy = summary(result)
db.prepare('UPDATE gd SET summary=?, mtime=? WHERE fid=?').run(JSON.stringify(smy), Date.now(), fid) db.prepare('UPDATE gd SET summary=?, mtime=? WHERE fid=?').run(JSON.stringify(smy), Date.now(), fid)
@ -217,7 +233,8 @@ async function ls_folder ({ fid, not_teamdrive, service_account }) {
params.orderBy = 'folder,name desc' params.orderBy = 'folder,name desc'
params.fields = 'nextPageToken, files(id, name, mimeType, size, md5Checksum)' params.fields = 'nextPageToken, files(id, name, mimeType, size, md5Checksum)'
params.pageSize = Math.min(PAGE_SIZE, 1000) params.pageSize = Math.min(PAGE_SIZE, 1000)
const use_sa = (fid !== 'root') && (service_account || !not_teamdrive) // 不带参数默认使用sa // const use_sa = (fid !== 'root') && (service_account || !not_teamdrive) // 不带参数默认使用sa
const use_sa = (fid !== 'root') && service_account
const headers = await gen_headers(use_sa) const headers = await gen_headers(use_sa)
do { do {
if (pageToken) params.pageToken = pageToken if (pageToken) params.pageToken = pageToken
@ -236,7 +253,7 @@ async function ls_folder ({ fid, not_teamdrive, service_account }) {
} }
} }
if (!data) { if (!data) {
console.error('读取目录未完成(部分读取), 参数:', params) console.error('讀取目錄未完成(部分讀取), 參數:', params)
files.not_finished = true files.not_finished = true
return files return files
} }
@ -248,7 +265,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,37 +293,30 @@ 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帳號')
} }
function real_get_sa_token (el) { async function real_get_sa_token (el) {
const { value, expires, gtoken } = el const { value, expires, gtoken } = el
// 把gtoken传递出去的原因是当某账号流量用尽时可以依此过滤 // 把gtoken传递出去的原因是当某账号流量用尽时可以依此过滤
if (Date.now() < expires) return { access_token: value, gtoken } if (Date.now() < expires) return { access_token: value, gtoken }
return new Promise((resolve, reject) => { const { access_token, expires_in } = await gtoken.getToken({ forceRefresh: true })
gtoken.getToken((err, tokens) => {
if (err) {
reject(err)
} else {
// console.log('got sa token', 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 }) return { access_token, gtoken }
}
})
})
} }
function get_random_element (arr) { function get_random_element (arr) {
@ -323,7 +333,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 +343,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)
console.log('创建目录重试中:', name, '重试次数:', retry) 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) { async function get_info_by_id (fid, use_sa) {
@ -353,7 +379,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)
@ -365,10 +391,10 @@ async function user_choose () {
const answer = await prompts({ const answer = await prompts({
type: 'select', type: 'select',
name: 'value', name: 'value',
message: '检测到上次的复制记录,是否继续', message: '檢測到上次的複製紀錄,是否繼續',
choices: [ choices: [
{ title: 'Continue', description: '从上次中断的地方继续', value: 'continue' }, { title: 'Continue', description: '從上次中斷的地方繼續', value: 'continue' },
{ title: 'Restart', description: '无视已存在的记录,重新复制', value: 'restart' }, { title: 'Restart', description: '無視已存在的紀錄,重新複製', value: 'restart' },
{ title: 'Exit', description: '直接退出', value: 'exit' } { title: 'Exit', description: '直接退出', value: 'exit' }
], ],
initial: 0 initial: 0
@ -376,25 +402,26 @@ 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('目標位置不能為空')
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)
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)
if (record) db.prepare('update task set status=? where id=?').run('error', record.id) if (record) db.prepare('update task set status=? where id=?').run('error', record.id)
} }
} }
// 待解决如果用户手动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 {
@ -405,14 +432,14 @@ async function real_copy ({ source, target, name, min_size, update, not_teamdriv
const record = db.prepare('select * from task where source=? and target=?').get(source, target) const record = db.prepare('select * from task where source=? and target=?').get(source, target)
if (record) { if (record) {
const copied = db.prepare('select fileid from copied where taskid=?').all(record.id).map(v => v.fileid)
const choice = is_server ? 'continue' : await user_choose() const choice = is_server ? 'continue' : await user_choose()
if (choice === 'exit') { if (choice === 'exit') {
return console.log('退出程序') return console.log('退出程序')
} else if (choice === 'continue') { } else if (choice === 'continue') {
let { copied, mapping } = record let { mapping } = record
const copied_ids = {}
const old_mapping = {} const old_mapping = {}
copied = copied.trim().split('\n') const copied_ids = {}
copied.forEach(id => copied_ids[id] = true) copied.forEach(id => copied_ids[id] = true)
mapping = mapping.trim().split('\n').map(line => line.split(' ')) mapping = mapping.trim().split('\n').map(line => line.split(' '))
const root = mapping[0][1] const root = mapping[0][1]
@ -421,9 +448,9 @@ async function real_copy ({ source, target, name, min_size, update, not_teamdriv
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 })
let files = arr.filter(v => v.mimeType !== FOLDER_TYPE).filter(v => !copied_ids[v.id]) let files = arr.filter(v => v.mimeType !== FOLDER_TYPE).filter(v => !copied_ids[v.id])
if (min_size) files = files.filter(v => v.size >= min_size) if (min_size) files = files.filter(v => v.size >= min_size)
const folders = arr.filter(v => v.mimeType === FOLDER_TYPE).filter(v => !old_mapping[v.id]) const folders = arr.filter(v => v.mimeType === FOLDER_TYPE)
console.log('待复制的目录数', folders.length) console.log('待複製的目錄數', folders.length)
console.log('待复制的文件数', files.length) console.log('待複製的檔案數', files.length)
const all_mapping = await create_folders({ const all_mapping = await create_folders({
old_mapping, old_mapping,
source, source,
@ -432,21 +459,22 @@ 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=?, mapping=? where id=?').run('copying', root_mapping, record.id)
.run('copying', '', root_mapping, record.id) db.prepare('delete from copied where taskid=?').run(record.id)
const arr = await walk_and_save({ fid: source, update: true, not_teamdrive, service_account }) // const arr = await walk_and_save({ fid: source, update: true, not_teamdrive, service_account })
const arr = await walk_and_save({ fid: source, update, not_teamdrive, service_account })
let files = arr.filter(v => v.mimeType !== FOLDER_TYPE) let files = arr.filter(v => v.mimeType !== FOLDER_TYPE)
if (min_size) files = files.filter(v => v.size >= min_size) if (min_size) files = files.filter(v => v.size >= min_size)
const folders = arr.filter(v => v.mimeType === FOLDER_TYPE) const folders = arr.filter(v => v.mimeType === FOLDER_TYPE)
console.log('待复制的目录数', folders.length) console.log('待複製的目錄數', folders.length)
console.log('待复制的文件数', files.length) console.log('待複製的檔案數', files.length)
const mapping = await create_folders({ const mapping = await create_folders({
source, source,
folders, folders,
@ -454,24 +482,23 @@ 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 })
let files = arr.filter(v => v.mimeType !== FOLDER_TYPE) let files = arr.filter(v => v.mimeType !== FOLDER_TYPE)
if (min_size) files = files.filter(v => v.size >= min_size) if (min_size) files = files.filter(v => v.size >= min_size)
const folders = arr.filter(v => v.mimeType === FOLDER_TYPE) const folders = arr.filter(v => v.mimeType === FOLDER_TYPE)
console.log('待复制的目录数', folders.length) console.log('待複製的目錄數', folders.length)
console.log('待复制的文件数', files.length) console.log('待複製的檔案數', files.length)
const mapping = await create_folders({ const mapping = await create_folders({
source, source,
folders, folders,
@ -479,39 +506,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)) const new_file = await limit(() => copy_file(id, target, service_account, limit, task_id))
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('INSERT INTO copied (taskid, fileid) VALUES (?, ?)').run(task_id, id)
console.error(e)
} }
})) })).finally(() => clearInterval(loop))
clearInterval(loop)
} }
async function copy_file (id, parent) { async function copy_file (id, parent, use_sa, limit, task_id) {
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 +540,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,16 +555,24 @@ 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('rate limit')) { if (message && message.toLowerCase().includes('file limit')) {
if (limit) limit.clearQueue()
if (task_id) db.prepare('update task set status=? where id=?').run('error', task_id)
throw new Error('您的小組雲端硬碟文件數已超限,停止複製')
}
if (use_sa && message && message.toLowerCase().includes('rate limit')) {
SA_TOKENS = SA_TOKENS.filter(v => v.gtoken !== gtoken) SA_TOKENS = SA_TOKENS.filter(v => v.gtoken !== gtoken)
console.log('此帐号触发使用限额剩余可用service account帐号数量', SA_TOKENS.length) if (!SA_TOKENS.length) SA_TOKENS = get_sa_batch()
console.log('此帳號觸發使用限額剩餘可用service account帳號數量', SA_TOKENS.length)
} }
} }
} }
if (!SA_TOKENS.length) { if (use_sa && !SA_TOKENS.length) {
throw new Error('所有SA帐号流量已用完') if (limit) limit.clearQueue()
if (task_id) db.prepare('update task set status=? where id=?').run('error', task_id)
throw new Error('所有SA帳號流量已用完')
} else { } else {
console.warn('复制文件失败文件id: ' + id) console.warn('複製檔案失敗,檔案id: ' + id)
} }
} }
@ -553,33 +582,38 @@ async function create_folders ({ source, old_mapping, folders, root, task_id, se
mapping[source] = root mapping[source] = root
if (!folders.length) return mapping if (!folders.length) return mapping
console.log('开始复制文件夹,总数:', folders.length) const missed_folders = folders.filter(v => !mapping[v.id])
console.log('開始複製資料夾,總數:', missed_folders.length)
const limit = pLimit(PARALLEL_LIMIT) const limit = pLimit(PARALLEL_LIMIT)
let count = 0 let count = 0
let same_levels = folders.filter(v => v.parent === folders[0].parent) let same_levels = folders.filter(v => v.parent === folders[0].parent)
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)
while (same_levels.length) { while (same_levels.length) {
await Promise.all(same_levels.map(async v => { const same_levels_missed = same_levels.filter(v => !mapping[v.id])
await Promise.all(same_levels_missed.map(async v => {
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 mapping = mapping || ? where id=?').run(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])
same_levels = [].concat(...same_levels.map(v => folders.filter(vv => vv.parent === v.id))) same_levels = [].concat(...same_levels.map(v => folders.filter(vv => vv.parent === v.id)))
} }
@ -627,17 +661,24 @@ 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' }
], ],
initial: 0 initial: 0
}) })
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
@ -648,7 +689,7 @@ async function rm_file ({ fid, service_account }) {
} catch (err) { } catch (err) {
retry++ retry++
handle_error(err) handle_error(err)
console.log('删除重试中,重试次数', retry) console.log('刪除重試中,重試次數', retry)
} }
} }
} }
@ -658,7 +699,7 @@ async function dedupe ({ fid, update, service_account }) {
if (!update) { if (!update) {
const info = get_all_by_fid(fid) const info = get_all_by_fid(fid)
if (info) { if (info) {
console.log('找到本地缓存数据,缓存时间', dayjs(info.mtime).format('YYYY-MM-DD HH:mm:ss')) console.log('找到本地快取資料,快取時間', dayjs(info.mtime).format('YYYY-MM-DD HH:mm:ss'))
arr = info arr = info
} }
} }
@ -677,16 +718,16 @@ 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++
} else { } else {
console.log('成功删除文件', v.name) console.log('成功刪除檔案', v.name)
file_count++ file_count++
} }
} catch (e) { } catch (e) {
console.log('删除失败', e.message) console.log('刪除失敗', e.message)
} }
})) }))
return { file_count, folder_count } return { file_count, folder_count }
@ -704,10 +745,10 @@ function handle_error (err) {
function print_progress (msg) { function print_progress (msg) {
if (process.stdout.cursorTo) { if (process.stdout.cursorTo) {
process.stdout.cursorTo(0) process.stdout.cursorTo(0)
process.stdout.write(msg) process.stdout.write(msg + ' ')
} else { } else {
console.log(msg) console.log(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,26 @@ 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, send_bm_help, get_target_by_alias, send_all_bookmarks, set_bookmark, unset_bookmark } = require('./tg')
const { AUTH } = require('../config') const { AUTH, ROUTER_PASSKEY, TG_IPLIST } = require('../config')
const { tg_whitelist } = AUTH const { tg_whitelist } = AUTH
const COPYING_FIDS = {}
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-cht 成功啟動'
const { query, headers } = ctx.request const { query, headers } = ctx.request
let { fid, type, update } = query let { fid, type, update, passkey } = query
if (!validate_fid(fid)) throw new Error('无效的分享ID') if (passkey !== ROUTER_PASSKEY) return ctx.body = 'invalid passkey'
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 +43,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
@ -45,54 +51,76 @@ router.post('/api/gdurl/tgbot', async ctx => {
if (callback_query) { if (callback_query) {
const { id, data } = callback_query const { id, data } = callback_query
const chat_id = callback_query.from.id const chat_id = callback_query.from.id
const [action, fid] = data.split(' ') const [action, fid, target] = data.split(' ')
if (action === 'count') { if (action === 'count') {
if (counting[fid]) return sm({ chat_id, text: fid + ' 正在统计,请稍等片刻' }) if (counting[fid]) return sm({ chat_id, text: fid + ' 正在統計,請稍候' })
counting[fid] = true counting[fid] = true
send_count({ fid, chat_id }).catch(err => { send_count({ fid, chat_id }).catch(err => {
console.error(err) console.error(err)
sm({ chat_id, text: fid + ' 统计失败' + err.message }) sm({ chat_id, text: fid + ' 統計失敗' + err.message })
}).finally(() => { }).finally(() => {
delete counting[fid] delete counting[fid]
}) })
} else if (action === 'copy') { } else if (action === 'copy') {
tg_copy({ fid, chat_id }).then(task_id => { if (COPYING_FIDS[fid]) return sm({ chat_id, text: `正在處理 ${fid} 的複製命令` })
task_id && sm({ chat_id, text: `开始复制任务ID: ${task_id} 可输入 /task ${task_id} 查询进度` }) COPYING_FIDS[fid] = true
}) tg_copy({ fid, target: get_target_by_alias(target), chat_id }).then(task_id => {
task_id && sm({ chat_id, text: `開始複製任務ID: ${task_id} 可輸入 /task ${task_id} 查詢進度` })
}).finally(() => COPYING_FIDS[fid] = false)
} }
return reply_cb_query({ id, data }).catch(console.error) return reply_cb_query({ id, data }).catch(console.error)
} }
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', '/bm']
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/')) { if (text.startsWith('/bm')) {
return send_choice({ fid, chat_id }).catch(console.error) const [cmd, action, alias, target] = text.split(' ').map(v => v.trim())
if (!action) return send_all_bookmarks(chat_id)
if (action === 'set') {
if (!alias || !target) return sm({ chat_id, text: '標籤名和dstID不能為空' })
if (alias.length > 24) return sm({ chat_id, text: '標籤名請勿超過24个英文字符' })
if (!validate_fid(target)) return sm({ chat_id, text: 'dstID格式錯誤' })
set_bookmark({ chat_id, alias, target })
} else if (action === 'unset') {
if (!alias) return sm({ chat_id, text: '標籤名不能為空' })
unset_bookmark({ chat_id, alias })
} else {
send_bm_help(chat_id)
} }
if (text.startsWith('/count')) { } else 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 })
} finally { } finally {
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] let 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} 格式不正确` }) target = get_target_by_alias(target) || target
tg_copy({ fid, target, chat_id }).then(task_id => { if (target && !validate_fid(target)) return sm({ chat_id, text: `目標ID ${target} 格式不正確` })
task_id && sm({ chat_id, text: `开始复制任务ID: ${task_id} 可输入 /task ${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')) { } else if (text.startsWith('/task')) {
let task_id = text.replace('/task', '').trim() let task_id = text.replace('/task', '').trim()
@ -102,12 +130,14 @@ router.post('/api/gdurl/tgbot', async ctx => {
task_id = parseInt(task_id) task_id = parseInt(task_id)
if (!task_id) { if (!task_id) {
const running_tasks = db.prepare('select id from task where status=?').all('copying') const running_tasks = db.prepare('select id from task where status=?').all('copying')
if (!running_tasks.length) return sm({ chat_id, text: '当前暂无运行中的任务' }) if (!running_tasks.length) return sm({ chat_id, text: '目前沒有執行中的任務' })
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: '不支持此命令' })
} }
}) })

View File

@ -5,10 +5,10 @@ const { escape } = require('html-escaper')
module.exports = { make_table, summary, make_html, make_tg_table } module.exports = { make_table, summary, make_html, make_tg_table }
function make_html ({ file_count, folder_count, total_size, details }) { function make_html ({ file_count, folder_count, total_size, details }) {
const head = ['类型', '数量', '大小'] const head = ['類型', '數量', '大小']
const th = '<tr>' + head.map(k => `<th>${k}</th>`).join('') + '</tr>' const th = '<tr>' + head.map(k => `<th>${k}</th>`).join('') + '</tr>'
const td = details.map(v => '<tr>' + [escape(v.ext), v.count, v.size].map(k => `<td>${k}</td>`).join('') + '</tr>').join('') const td = details.map(v => '<tr>' + [escape(v.ext), v.count, v.size].map(k => `<td>${k}</td>`).join('') + '</tr>').join('')
let tail = ['合', file_count + folder_count, total_size] let tail = ['合', file_count + folder_count, total_size]
tail = '<tr style="font-weight:bold">' + tail.map(k => `<td>${k}</td>`).join('') + '</tr>' tail = '<tr style="font-weight:bold">' + tail.map(k => `<td>${k}</td>`).join('') + '</tr>'
const table = `<table border="1" cellpadding="12" style="border-collapse:collapse;font-family:serif;font-size:22px;margin:10px auto;text-align: center"> const table = `<table border="1" cellpadding="12" style="border-collapse:collapse;font-family:serif;font-size:22px;margin:10px auto;text-align: center">
${th} ${th}
@ -26,7 +26,7 @@ function make_table ({ file_count, folder_count, total_size, details }) {
return arr.map(content => ({ content, hAlign })) return arr.map(content => ({ content, hAlign }))
}) })
const total_count = file_count + folder_count const total_count = file_count + folder_count
const tails = ['总计', total_count, total_size].map(v => ({ content: colors.bold(v), hAlign })) const tails = ['總計', total_count, total_size].map(v => ({ content: colors.bold(v), hAlign }))
tb.push(headers, ...records) tb.push(headers, ...records)
tb.push(tails) tb.push(tails)
return tb.toString() + '\n' return tb.toString() + '\n'
@ -56,8 +56,8 @@ function make_tg_table ({ file_count, folder_count, total_size, details }) {
const hAlign = 'center' const hAlign = 'center'
const headers = ['Type', 'Count', 'Size'].map(v => ({ content: v, hAlign })) const headers = ['Type', 'Count', 'Size'].map(v => ({ content: v, hAlign }))
details.forEach(v => { details.forEach(v => {
if (v.ext === '文件夹') v.ext = '[Folder]' if (v.ext === '資料夾') v.ext = '[Folder]'
if (v.ext === '无扩展名') v.ext = '[NoExt]' if (v.ext === '無副檔名') v.ext = '[NoExt]'
}) })
const records = details.map(v => [v.ext, v.count, v.size]).map(arr => arr.map(content => ({ content, hAlign }))) const records = details.map(v => [v.ext, v.count, v.size]).map(arr => arr.map(content => ({ content, hAlign })))
const total_count = file_count + folder_count const total_count = file_count + folder_count
@ -107,8 +107,8 @@ function summary (info, sort_by) {
} else { } else {
details.sort((a, b) => b.count - a.count) details.sort((a, b) => b.count - a.count)
} }
if (no_ext) details.push({ ext: '无扩展名', count: no_ext, size: format_size(no_ext_size), raw_size: no_ext_size }) if (no_ext) details.push({ ext: '無副檔名', count: no_ext, size: format_size(no_ext_size), raw_size: no_ext_size })
if (folder_count) details.push({ ext: '文件夹', count: folder_count, size: 0, raw_size: 0 }) if (folder_count) details.push({ ext: '資料夾', count: folder_count, size: 0, raw_size: 0 })
return { file_count, folder_count, total_size, details } return { file_count, folder_count, total_size, details }
} }

288
src/tg.js
View File

@ -4,49 +4,151 @@ 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, USE_PERSONAL_AUTH } = 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('請先在config.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
}
function send_help (chat_id) { function send_help (chat_id) {
const text = `<pre>[使用帮助] const text = `<pre>[使用說明]
命令 说明 ***不支持單檔分享***
命令 說明
/help | 使 =====================
/help | 使
/count shareID | sourceID, sourceIDgoogle driveID =====================
/count sourceID [-u] | sourceID
/copy sourceID targetID | sourceIDtargetIDtargetIDconfig.jstaskID sourceID可以是共享網址本身也可以是共享ID如果命令最后加上 -u則無視快取記錄強制從線上獲取適合一段時候後才更新完畢的分享連結
=====================
/task taskID | all /copy sourceID targetID() [-u] | sourceIDtargetID
若無targetID則會複製到預設位置config.js中的DEFAULT_TARGET
如果設定了bookmark那麼targetID也可以是bookmark的標籤名
如果命令最後加上 -u則無視快取記錄強制從線上獲取源資料夾資訊返回拷貝任務的taskID
=====================
/task taskID() | taskID
若填 all 則返回所有任務列表(歷史紀錄)
=====================
/bm [action] [alias] [target] | bookmarkID
會在輸入共享連結後返回的文件統計開始複製這兩個按鈕的下方出現方便複製到常用位置
範例
/bm |
/bm set movie folder-id | folder-idmovie
/bm unset movie |
</pre>` </pre>`
return sm({ chat_id, text, parse_mode: 'HTML' }) return sm({ chat_id, text, parse_mode: 'HTML' })
} }
function send_bm_help (chat_id) {
const text = `<pre>/bm [action] [alias] [target] | bookmark添加常用目的資料夾ID
會在輸入共享連結後返回的文件統計開始複製這兩個按鈕的下方出現方便複製到常用位置
範例
/bm |
/bm set movie folder-id | folder-idmovie
/bm unset movie |
</pre>`
return sm({ chat_id, text, parse_mode: 'HTML' })
}
function send_all_bookmarks (chat_id) {
let records = db.prepare('select alias, target from bookmark').all()
if (!records.length) return sm({ chat_id, text: '資料庫中沒有收藏紀錄' })
const tb = new Table({ style: { head: [], border: [] } })
const headers = ['標籤名', 'dstID']
records = records.map(v => [v.alias, v.target])
tb.push(headers, ...records)
const text = tb.toString().replace(/─/g, '—')
return sm({ chat_id, text: `<pre>${text}</pre>`, parse_mode: 'HTML' })
}
function set_bookmark ({ chat_id, alias, target }) {
const record = db.prepare('select alias from bookmark where alias=?').get(alias)
if (record) return sm({ chat_id, text: '資料庫中已有同名的收藏' })
db.prepare('INSERT INTO bookmark (alias, target) VALUES (?, ?)').run(alias, target)
return sm({ chat_id, text: `成功設定收藏${alias} | ${target}` })
}
function unset_bookmark ({ chat_id, alias }) {
const record = db.prepare('select alias from bookmark where alias=?').get(alias)
if (!record) return sm({ chat_id, text: '未找到此標籤名的收藏' })
db.prepare('delete from bookmark where alias=?').run(alias)
return sm({ chat_id, text: '成功刪除收藏 ' + alias })
}
function get_target_by_alias (alias) {
const record = db.prepare('select target from bookmark where alias=?').get(alias)
return record && record.target
}
function send_choice ({ fid, chat_id }) { function send_choice ({ fid, chat_id }) {
if(BUTTON_LEVEL == 1){
return sm({ return sm({
chat_id, chat_id,
text: `识别出分享ID ${fid},请选择动作`, text: `辨識到分享ID ${fid},請選擇動`,
reply_markup: { reply_markup: {
inline_keyboard: [ inline_keyboard: [
[ [
{ text: '文件统计', callback_data: `count ${fid}` }, { text: '文件統計', callback_data: `count ${fid}` }
{ text: '开始复制', callback_data: `copy ${fid}` } ],
] [
{ text: '開始複製', callback_data: `copy ${fid}` }
] ]
].concat(gen_bookmark_choices(fid))
} }
}) })
}else{
return sm({
chat_id,
text: `辨識到分享ID ${fid},請選擇動作`,
reply_markup: {
inline_keyboard: [
[
{ text: '文件統計', callback_data: `count ${fid}` },
{ text: '開始複製', callback_data: `copy ${fid}` }
]
].concat(gen_bookmark_choices(fid))
}
})
}
}
// console.log(gen_bookmark_choices())
function gen_bookmark_choices (fid) {
let level = 1
if (BUTTON_LEVEL > 2){
level = 2
}else{
level = BUTTON_LEVEL
}
const gen_choice = v => ({text: `複製到 ${v.alias}`, callback_data: `copy ${fid} ${v.alias}`})
const records = db.prepare('select * from bookmark').all()
db.close()
const result = []
for (let i = 0; i < records.length; i++) {
const line = [gen_choice(records[i])]
for(let j = 0; j < level-1; j ++){
if (records[i+1]) line.push(gen_choice(records[i+1]))
i++
}
result.push(line)
}
return result
} }
async function send_all_tasks (chat_id) { async function send_all_tasks (chat_id) {
let records = db.prepare('select id, status, ctime from task').all() let records = db.prepare('select id, status, ctime from task').all()
if (!records.length) return sm({ chat_id, text: '数据库中没有任务记录' }) if (!records.length) return sm({ chat_id, text: '資料庫中沒有任務記錄' })
const tb = new Table({ style: { head: [], border: [] } }) const tb = new Table({ style: { head: [], border: [] } })
const headers = ['ID', 'status', 'ctime'] const headers = ['ID', 'status', 'ctime']
records = records.map(v => { records = records.map(v => {
@ -59,69 +161,103 @@ async function send_all_tasks (chat_id) {
return axins.post(url, { return axins.post(url, {
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>` })
} }
console.error(err) console.error(err)
}) })
} }
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 = '任務ID' + 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 += '任务状态:' + status + '\n' text += '目的位置:' + gen_link(target) + '\n'
text += '创建时间:' + dayjs(ctime).format('YYYY-MM-DD HH:mm:ss') + '\n' text += '新資料夾:' + (new_folder ? gen_link(new_folder) : '尚未創建') + '\n'
text += '完成时间:' + (ftime ? dayjs(ftime).format('YYYY-MM-DD HH:mm:ss') : '未完成') + '\n' text += '任務狀態:' + status + '\n'
text += '目录进度:' + copied_folders + '/' + (folder_count === undefined ? '未知数量' : folder_count) + '\n' text += '創建時間:' + dayjs(ctime).format('YYYY-MM-DD HH:mm:ss') + '\n'
text += '文件进度:' + copied_files + '/' + (file_count === undefined ? '未知数量' : file_count) + '\n' text += '完成時間:' + (ftime ? dayjs(ftime).format('YYYY-MM-DD HH:mm:ss') : '未完成') + '\n'
text += '总大小:' + (total_size || '未知大小') var pct = copied_folders/(folder_count === undefined ? '未知數量' : folder_count)*100
return sm({ chat_id, text, parse_mode: 'HTML' }) pct = pct.toFixed(2);
text += '目錄進度:' + copied_folders + '/' + (folder_count === undefined ? '未知數量' : folder_count) + ' - ' + pct + '%\n'
pct = copied_files/(file_count === undefined ? '未知數量' : file_count)*100
pct = pct.toFixed(2);
text += '文件進度:' + copied_files + '/' + (file_count === undefined ? '未知數量' : file_count) + ' - ' + pct + '%\n'
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, folder_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如果超1万就不每10秒更新了以后如果把mapping 也另存一张表可以取消此限制
if (!message_id || status !== 'copying' || folder_count > 10000) 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)' })
return return
} }
let record = db.prepare('select id, status from task where source=? and target=?').get(fid, target) let record = db.prepare('select id, status from task where source=? and target=?').get(fid, target)
if (record) { if (record) {
if (record.status === 'copying') { if (record.status === 'copying') {
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, service_account: !USE_PERSONAL_AUTH, 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 { text } = await get_task_info(task_id)
sm({ chat_id, text, parse_mode: 'HTML' })
}) })
.catch(err => { .catch(err => {
const task_id = record && record.id
if (task_id){
db.prepare('update task set status=? where id=?').run('error', task_id)
db.close()
}
if (!record) record = {} if (!record) record = {}
console.error('复制失败', fid, '-->', target) console.error('複製失敗', fid, '-->', target)
console.error(err) console.error(err)
sm({ chat_id, text: '复制失败,失败消息:' + err.message }) sm({ chat_id, text: '複製失敗,失敗訊息:' + err.message })
}) })
while (!record) { while (!record) {
@ -141,33 +277,39 @@ function reply_cb_query ({ id, data }) {
const url = `https://api.telegram.org/bot${tg_token}/answerCallbackQuery` const url = `https://api.telegram.org/bot${tg_token}/answerCallbackQuery`
return axins.post(url, { return axins.post(url, {
callback_query_id: id, callback_query_id: id,
text: '开始执行 ' + data text: '開始執行 ' + 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 }) sm({ chat_id, text: `開始獲取 ${fid} 所有檔案資訊,請稍後,建議統計完成前先不要開始複製,因为複製也需要先獲取來源資料夾資訊` })
const table = await gen_count_body({ fid, update, type: 'tg', service_account: !USE_PERSONAL_AUTH })
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']
const smy = await gen_count_body({ fid, type: 'json', service_account: true }) // if (description && too_long_msgs.some(v => description.toLowerCase().includes(v))) {
if (true) {
const smy = await gen_count_body({ fid, type: 'json', service_account: !USE_PERSONAL_AUTH })
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訊息限制僅顯示概要
文件总数${file_count} 目錄名稱${name}
目录总数${folder_count} 文件總數${file_count}
合计大小${total_size} 目錄總數${folder_count}
合計大小${total_size}
</pre>` </pre>`
}) })
} }
@ -178,25 +320,33 @@ ${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])
}
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, get_target_by_alias, send_bm_help, send_all_bookmarks, set_bookmark, unset_bookmark }

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 KiB