├── iCopy ├── drive │ ├── __init__.py │ └── gdrive.py ├── utils │ ├── __init__.py │ ├── __version__.py │ ├── callback_stage.py │ ├── process_bar.py │ ├── restricted.py │ ├── load.py │ ├── keyboard.py │ ├── messages.py │ ├── purge_payload.py │ ├── dedupe_payload.py │ ├── task_box.py │ ├── get_functions.py │ ├── size_payload.py │ ├── get_set.py │ └── task_payload.py ├── web │ ├── __init__.py │ ├── vue-admin-simple │ │ └── dist │ │ │ ├── favicon.ico │ │ │ ├── static │ │ │ ├── img │ │ │ │ ├── 404.a57b6f31.png │ │ │ │ └── 404_cloud.0f4bc32b.png │ │ │ ├── css │ │ │ │ ├── chunk-718881df.f9066ea7.css │ │ │ │ ├── chunk-130a7162.011d879c.css │ │ │ │ ├── chunk-2fd9fc79.011d879c.css │ │ │ │ ├── chunk-285369fe.6d571437.css │ │ │ │ ├── chunk-libs.3dfb7769.css │ │ │ │ └── chunk-238c903c.3c7f5ad9.css │ │ │ ├── fonts │ │ │ │ ├── fa-solid-900.781e85bb.ttf │ │ │ │ ├── fa-solid-900.89bd2e38.eot │ │ │ │ ├── element-icons.535877f5.woff │ │ │ │ ├── element-icons.732389de.ttf │ │ │ │ ├── fa-brands-400.085b1dd8.ttf │ │ │ │ ├── fa-brands-400.0fabb660.eot │ │ │ │ ├── fa-brands-400.cac68c83.woff2 │ │ │ │ ├── fa-brands-400.dc0bd022.woff │ │ │ │ ├── fa-regular-400.05b53beb.woff │ │ │ │ ├── fa-regular-400.1a78af41.ttf │ │ │ │ ├── fa-regular-400.ad3a7c0d.eot │ │ │ │ ├── fa-solid-900.c500da19.woff2 │ │ │ │ ├── fa-solid-900.ee09ad75.woff │ │ │ │ └── fa-regular-400.3a3398a6.woff2 │ │ │ └── js │ │ │ │ ├── chunk-2d21843e.48a6c318.js │ │ │ │ ├── chunk-718881df.f7f1bdd7.js │ │ │ │ ├── chunk-238c903c.f33d3eaf.js │ │ │ │ ├── chunk-2fd9fc79.3cba3c7b.js │ │ │ │ ├── chunk-285369fe.c6caa2dc.js │ │ │ │ └── chunk-130a7162.684ec14a.js │ │ │ └── index.html │ ├── cook_resp.py │ └── dash.py ├── workflow │ ├── __init__.py │ ├── start_workflow.py │ ├── quick_workflow.py │ ├── copy_workflow.py │ ├── purge_workflow.py │ ├── regex_workflow.py │ ├── dedupe_workflow.py │ └── size_workflow.py ├── docs │ ├── TODO.md │ ├── develop_update.md │ ├── COMMAND.md │ └── CHANGELOG.md ├── config │ ├── conf.toml.example │ └── text.toml └── iCopy.py ├── runtime.txt ├── Procfile ├── Aptfile ├── requirements.txt ├── Set.sh ├── README.md ├── app.json └── iCopy.ipynb /iCopy/drive/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /iCopy/utils/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /iCopy/web/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /runtime.txt: -------------------------------------------------------------------------------- 1 | python-3.8.5 -------------------------------------------------------------------------------- /iCopy/workflow/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | worker: source Set.sh 2 | -------------------------------------------------------------------------------- /Aptfile: -------------------------------------------------------------------------------- 1 | wget 2 | wget2 3 | p7zip-full 4 | unzip 5 | -------------------------------------------------------------------------------- /iCopy/utils/__version__.py: -------------------------------------------------------------------------------- 1 | ### local version 2 | __version__ = "v0.2.2-Post.2" -------------------------------------------------------------------------------- /iCopy/docs/TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | + new keyboard function 4 | + more web functions 5 | -------------------------------------------------------------------------------- /iCopy/docs/develop_update.md: -------------------------------------------------------------------------------- 1 | # DEVELOP UPDATE CHANGELOG 2 | 3 | More web functions is under construction. 4 | -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/favicon.ico -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/img/404.a57b6f31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/static/img/404.a57b6f31.png -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/css/chunk-718881df.f9066ea7.css: -------------------------------------------------------------------------------- 1 | .dashboard-container[data-v-f7ec0d96]{margin:30px}.dashboard-text[data-v-f7ec0d96]{font-size:30px;line-height:46px} -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/img/404_cloud.0f4bc32b.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/static/img/404_cloud.0f4bc32b.png -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/fonts/fa-solid-900.781e85bb.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/static/fonts/fa-solid-900.781e85bb.ttf -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/fonts/fa-solid-900.89bd2e38.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/static/fonts/fa-solid-900.89bd2e38.eot -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/fonts/element-icons.535877f5.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/static/fonts/element-icons.535877f5.woff -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/fonts/element-icons.732389de.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/static/fonts/element-icons.732389de.ttf -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/fonts/fa-brands-400.085b1dd8.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/static/fonts/fa-brands-400.085b1dd8.ttf -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/fonts/fa-brands-400.0fabb660.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/static/fonts/fa-brands-400.0fabb660.eot -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/fonts/fa-brands-400.cac68c83.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/static/fonts/fa-brands-400.cac68c83.woff2 -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/fonts/fa-brands-400.dc0bd022.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/static/fonts/fa-brands-400.dc0bd022.woff -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/fonts/fa-regular-400.05b53beb.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/static/fonts/fa-regular-400.05b53beb.woff -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/fonts/fa-regular-400.1a78af41.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/static/fonts/fa-regular-400.1a78af41.ttf -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/fonts/fa-regular-400.ad3a7c0d.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/static/fonts/fa-regular-400.ad3a7c0d.eot -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/fonts/fa-solid-900.c500da19.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/static/fonts/fa-solid-900.c500da19.woff2 -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/fonts/fa-solid-900.ee09ad75.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/static/fonts/fa-solid-900.ee09ad75.woff -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/fonts/fa-regular-400.3a3398a6.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coko8023/iCopy-Heroku/HEAD/iCopy/web/vue-admin-simple/dist/static/fonts/fa-regular-400.3a3398a6.woff2 -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/css/chunk-130a7162.011d879c.css: -------------------------------------------------------------------------------- 1 | .demo-table-expand{font-size:0}.demo-table-expand label{width:150px;color:#99a9bf}.demo-table-expand .el-form-item{margin-right:0;margin-bottom:0;width:50%} -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/css/chunk-2fd9fc79.011d879c.css: -------------------------------------------------------------------------------- 1 | .demo-table-expand{font-size:0}.demo-table-expand label{width:150px;color:#99a9bf}.demo-table-expand .el-form-item{margin-right:0;margin-bottom:0;width:50%} -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.24.0 2 | urllib3==1.25.9 3 | google_api_python_client==1.10.0 4 | protobuf==3.12.2 5 | pymongo[srv]==3.10.1 6 | toml==0.10.1 7 | python-telegram-bot==12.8 8 | sanic==20.6.3 9 | Sanic_Cors==0.10.0.post3 -------------------------------------------------------------------------------- /iCopy/utils/callback_stage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from telegram.ext import ConversationHandler 5 | 6 | ( 7 | SET_FAV_MULTI, 8 | CHOOSE_MODE, 9 | GET_LINK, 10 | IS_COVER_QUICK, 11 | GET_DST, 12 | COOK_ID, 13 | REGEX_IN, 14 | REGEX_GET_DST, 15 | COOK_FAV_TO_SIZE, 16 | COOK_FAV_PURGE, 17 | COOK_ID_DEDU, 18 | COOK_FAV_DEDU, 19 | FAV_PRE_DEDU_INFO, 20 | SET_WEB 21 | 22 | ) = range(14) -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/js/chunk-2d21843e.48a6c318.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2d21843e"],{c9f8:function(t,e,a){"use strict";a.r(e);var l=function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("el-table",{staticStyle:{width:"100%"},attrs:{data:t.tableData,height:"100%"}},[a("el-table-column",{attrs:{prop:"name",label:"GDrive NAME",width:"300"}}),a("el-table-column",{attrs:{prop:"id",label:"GDrive ID",width:"300"}})],1)},n=[],o={data:function(){return{tableData:[]}},mounted:function(){var t=this;this.$request({url:"/drivelist",method:"get"}).then((function(e){console.log(e);var a=[];for(var l in e.data)a.push({id:l,name:e.data[l]});console.log(a),t.tableData=a})).catch((function(t){console.log(t)}))}},i=o,r=a("2877"),c=Object(r["a"])(i,l,n,!1,null,null,null);e["default"]=c.exports}}]); -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/js/chunk-718881df.f7f1bdd7.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-718881df"],{3737:function(t,e,a){},9406:function(t,e,a){"use strict";a.r(e);var c=function(){var t=this,e=t.$createElement;t._self._c;return t._m(0)},n=[function(){var t=this,e=t.$createElement,a=t._self._c||e;return a("div",{staticClass:"dashboard-container"},[a("img",{attrs:{alt:"iCopy logo",src:"https://f002.backblazeb2.com/file/jsuforum-upload/optimized/1X/cff2835c1652bb57a18aac42a3eee34b51cd9b89_2_1380x386.gif",width:"33.3%"}}),a("div",{staticClass:"dashboard-text"},[t._v("Welcome iCopy WEB DASHBOARD")])])}],s=a("5530"),i=a("2f62"),o={name:"Dashboard",computed:Object(s["a"])({},Object(i["b"])(["name"]))},r=o,f=(a("f667"),a("2877")),u=Object(f["a"])(r,c,n,!1,null,"f7ec0d96",null);e["default"]=u.exports},f667:function(t,e,a){"use strict";var c=a("3737"),n=a.n(c);n.a}}]); -------------------------------------------------------------------------------- /iCopy/workflow/start_workflow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os, logging, re 5 | from telegram import ParseMode 6 | from telegram.ext import ( 7 | Updater, 8 | CommandHandler, 9 | MessageHandler, 10 | Filters, 11 | CallbackQueryHandler, 12 | ConversationHandler, 13 | ) 14 | from utils.load import _lang, _text 15 | from utils import ( 16 | messages as _msg, 17 | restricted as _r, 18 | keyboard as _KB, 19 | callback_stage as _stage, 20 | ) 21 | 22 | @_r.restricted 23 | def start(update, context): 24 | _first_name = update.effective_user.first_name 25 | update.effective_message.reply_text( 26 | _text[_lang]["start"].replace("replace",_first_name) 27 | + "\n" 28 | + _text[_lang]["guide_to_menu"] 29 | ) 30 | 31 | @_r.restricted 32 | def menu(update, context): 33 | update.effective_message.reply_text( 34 | _text[_lang]["menu_msg"], 35 | reply_markup=_KB.start_keyboard(), 36 | ) 37 | 38 | return _stage.CHOOSE_MODE 39 | -------------------------------------------------------------------------------- /Set.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | cd ./iCopy/config 3 | echo " 4 | [tg] 5 | 6 | token = \"$BOT_TOKEN\" 7 | 8 | usr_id = \"$USER_ID\" 9 | 10 | [database] 11 | 12 | db_connect_method = \"$DB_CONNECT_METHOD\" 13 | 14 | db_addr = \"$DB_ADDRESS\" 15 | 16 | db_port = $DB_PORT 17 | 18 | db_name = \"$DB_NAME\" 19 | 20 | db_user = \"$DB_USERNAME\" 21 | 22 | db_passwd = \"$DB_PASS\" 23 | 24 | [general] 25 | 26 | language = \"$LANGUAGE\" 27 | 28 | cloner = \"$CLONER\" 29 | 30 | option = \"$OPTION\" 31 | 32 | remote = \"$RCLONE_RMT\" 33 | 34 | parallel_c = \"$PARALLEL_CHECKERS\" 35 | 36 | parallel_t = \"$PARALLEL_TRANSFERS\" 37 | 38 | min_sleep = \"$MIN_SLEEP\" 39 | 40 | sa_path = \"$SA_PATH\" 41 | 42 | run_args = $RUN_ARGS 43 | 44 | [web] 45 | 46 | dashboard = 0 47 | 48 | port = 8000 49 | 50 | " >> conf.toml 51 | 52 | echo "[$RCLONE_RMT] 53 | type = drive 54 | scope = drive 55 | service_account_file = /app/iCopy/accounts/$SA_INIT_FILE 56 | service_account_file_path = $SA_PATH 57 | " >> rclone.conf 58 | cd .. 59 | ACCOUNTS_FILE="accounts.zip" 60 | wget --no-check-certificate -q $SA_ZIP_URL -O $ACCOUNTS_FILE 61 | unzip -qq $ACCOUNTS_FILE -d /app/iCopy/ 62 | rm -rf $ACCOUNTS_FILE 63 | chmod 777 iCopy.py 64 | python3 iCopy.py 65 | -------------------------------------------------------------------------------- /iCopy/workflow/quick_workflow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from utils.load import _lang, _text 5 | from telegram.ext import ConversationHandler 6 | from utils import ( 7 | restricted as _r, 8 | get_functions as _func, 9 | task_box as _box, 10 | callback_stage as _stage, 11 | ) 12 | 13 | @_r.restricted 14 | @_r.restricted_quick 15 | def quick(update, context): 16 | _func.mode = "quick" 17 | call_mode = update.effective_message.text 18 | 19 | if "/quick" == call_mode.strip()[:6]: 20 | update.effective_message.reply_text( 21 | _text[_lang]["mode_select_msg"].replace( 22 | "replace", _text[_lang]["quick_mode"] 23 | ) 24 | + "\n" 25 | + _text[_lang]["request_share_link"] 26 | ) 27 | 28 | return _stage.GET_LINK 29 | 30 | if update.callback_query.data == "quick": 31 | update.callback_query.edit_message_text( 32 | _text[_lang]["mode_select_msg"].replace( 33 | "replace", _text[_lang]["quick_mode"] 34 | ) 35 | + "\n" 36 | + _text[_lang]["request_share_link"] 37 | ) 38 | 39 | return _stage.GET_LINK 40 | -------------------------------------------------------------------------------- /iCopy/utils/process_bar.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | 6 | def status(val): 7 | if val == 0: 8 | ss = "│░░░░░░░░░░░░░│" 9 | 10 | if 0 < val <= 48: 11 | if val < 8: 12 | ss = "│█░░░░░░░░░░░░│" 13 | 14 | if 8 <= val <= 16: 15 | ss = "│██░░░░░░░░░░░│" 16 | 17 | if 16 < val <= 24: 18 | ss = "│███░░░░░░░░░░│" 19 | 20 | if 24 < val <= 32: 21 | ss = "│████░░░░░░░░░│" 22 | 23 | if 32 < val <= 40: 24 | ss = "│█████░░░░░░░░│" 25 | 26 | if 40 < val <= 48: 27 | ss = "│██████░░░░░░░│" 28 | 29 | if 48 < val <= 96: 30 | if 48 < val <= 56: 31 | ss = "│███████░░░░░░│" 32 | 33 | if 56 < val <= 64: 34 | ss = "│████████░░░░░│" 35 | 36 | if 64 < val <= 72: 37 | ss = "│█████████░░░░│" 38 | 39 | if 72 < val <= 80: 40 | ss = "│██████████░░░│" 41 | 42 | if 80 < val <= 88: 43 | ss = "│███████████░░│" 44 | 45 | if 88 < val <= 96: 46 | ss = "│████████████░│" 47 | 48 | if 96 < val <= 100: 49 | if 96 < val < 100: 50 | ss = "│█████████████│" 51 | 52 | if val == 100: 53 | ss = "✭│█████████████│✭" 54 | 55 | return ss -------------------------------------------------------------------------------- /iCopy/config/conf.toml.example: -------------------------------------------------------------------------------- 1 | 2 | [tg] 3 | 4 | token = "Bot API Token HERE" # get from tg::botfather 5 | 6 | usr_id = "user id HERE" # get from tg::@get_id_bot 7 | 8 | [database] 9 | 10 | db_connect_method = "mongodb+srv" # like "mongodb", "mongodb+srv",etc... 11 | 12 | db_addr = "Your database address here" # mongodb host address 13 | 14 | db_port = 27017 # mongodb connect port,default is 27017 15 | 16 | db_name = "iCopy" # set Your mongodb collection_Name,default is "iCopy" 17 | 18 | db_user = "Your mongodb user_Name HERE" # mongodb username 19 | 20 | db_passwd = "Your mongodb user_Passwd HERE" # mongodb passwd 21 | 22 | [general] 23 | 24 | language = "cn" # cn / eng / jp,default is "cn" 25 | 26 | cloner = "fclone" # Only fclone is suitable now,Future supports: rclone / gclone / autorclone 27 | 28 | option = "copy" # cloner option,e.g. "copy" , "sync" 29 | 30 | remote = "" # e.g. "fc" , "gc" , "rc", "sa" ,etc... 31 | 32 | parallel_c = "4" # customize checkers_NUM,default = "16" 33 | 34 | parallel_t = "4" # customize transfers_NUM,default = "32" 35 | 36 | min_sleep = "1ms" # customize drive-pacer-min-sleep,default = "1ms" 37 | 38 | sa_path = "/root/accounts" #Service_Accounts path,No slash at the end,default is "/root/accounts" 39 | 40 | run_args = ['--log-level=DEBUG', '--log-file=/root/icopy_cloner_debug.log'] # status flags 41 | 42 | [web] 43 | 44 | dashboard = 0 # web service switch 45 | 46 | port = 8000 # web service port, default is 8000. 47 | -------------------------------------------------------------------------------- /iCopy/docs/COMMAND.md: -------------------------------------------------------------------------------- 1 | # BOT COMMAND 2 | 3 | + Root Command: 4 | 5 | + start - nothing just say hello 6 | + menu - main entry point 7 | quick - quick mode 8 | copy - full mode 9 | set - customize settings 10 | task - task query 11 | reset - restore task 12 | size - just size task 13 | dedupe - dedupe drives and folders 14 | purge - delete files and folder in specified fav trash bin 15 | cancel - cancel TG conversation 16 | kill - kill task 17 | ver - check iCopy version 18 | restart - restart iCopy 19 | 20 | + Child Command: 21 | 22 | + set - customize settings 23 | ┖ set - batch way 24 | ┖ set rule - rules 25 | ┖ fav|quick +/- id - single way 26 | ┖ set purge - purge favorites 27 | ┖ set web - set web account&password 28 | + size - size query 29 | ┖ size - size the shared resource 30 | ┖ size id - size specified task 31 | ┖ size fav - size specified favorites 32 | + dedupe - dedupe drives and folders 33 | ┖ dedupe - dedupe specified favorites 34 | ┖ dedupe id - dedupe specified task 35 | + task - task query 36 | ┖ task - task in processing 37 | ┖ task list - future 10 tasks 38 | ┖ task id - show the specified task 39 | + reset - restore task 40 | ┖ reset - restore current task 41 | ┖ reset id - restore the specified task 42 | + kill - kill task 43 | ┖ kill - kill current transferring task 44 | ┖ kill task - kill current transferring task 45 | ┖ kill size - kill sizing task 46 | ┖ kill purge - kill purge task 47 | ┖ kill dedupe - kill dedupe task 48 | -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/css/chunk-285369fe.6d571437.css: -------------------------------------------------------------------------------- 1 | @supports(-webkit-mask:none) and (not (cater-color:#fff)){.login-container .el-input input{color:#fff}}.login-container .el-input{display:inline-block;height:47px;width:85%}.login-container .el-input input{background:transparent;border:0;-webkit-appearance:none;border-radius:0;padding:12px 5px 12px 15px;color:#fff;height:47px;caret-color:#fff}.login-container .el-input input:-webkit-autofill{-webkit-box-shadow:0 0 0 1000px #283443 inset!important;box-shadow:inset 0 0 0 1000px #283443!important;-webkit-text-fill-color:#fff!important}.login-container .el-form-item{border:1px solid hsla(0,0%,100%,.1);background:rgba(0,0,0,.1);border-radius:5px;color:#454545}.login-container[data-v-8bbd11dc]{min-height:100%;width:100%;background-color:#2d3a4b;overflow:hidden}.login-container .login-form[data-v-8bbd11dc]{position:relative;width:520px;max-width:100%;padding:160px 35px 0;margin:0 auto;overflow:hidden}.login-container .tips[data-v-8bbd11dc]{font-size:14px;color:#fff;margin-bottom:10px}.login-container .tips span[data-v-8bbd11dc]:first-of-type{margin-right:16px}.login-container .svg-container[data-v-8bbd11dc]{padding:6px 5px 6px 15px;color:#889aa4;vertical-align:middle;width:30px;display:inline-block}.login-container .title-container[data-v-8bbd11dc]{position:relative}.login-container .title-container .title[data-v-8bbd11dc]{font-size:26px;color:#eee;margin:0 auto 40px auto;text-align:center;font-weight:700}.login-container .show-pwd[data-v-8bbd11dc]{position:absolute;right:10px;top:7px;font-size:16px;color:#889aa4;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none} -------------------------------------------------------------------------------- /iCopy/workflow/copy_workflow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from utils.load import _lang, _text 5 | from telegram.ext import ConversationHandler 6 | from utils import ( 7 | messages as _msg, 8 | restricted as _r, 9 | get_functions as _func, 10 | task_box as _box, 11 | keyboard as _KB, 12 | callback_stage as _stage, 13 | ) 14 | 15 | current_dst_info = "" 16 | 17 | 18 | @_r.restricted 19 | @_r.restricted_copy 20 | def copy(update, context): 21 | _func.mode = "copy" 22 | call_mode = update.effective_message.text 23 | 24 | if "/copy" == call_mode.strip()[:5]: 25 | update.effective_message.reply_text( 26 | _text[_lang]["mode_select_msg"].replace( 27 | "replace", _text[_lang]["copy_mode"] 28 | ) 29 | + "\n" 30 | + _text[_lang]["request_dst_target"], 31 | reply_markup=_KB.dst_keyboard(update, context), 32 | ) 33 | 34 | return _stage.GET_DST 35 | 36 | if update.callback_query.data == "copy": 37 | update.callback_query.edit_message_text( 38 | _text[_lang]["mode_select_msg"].replace( 39 | "replace", _text[_lang]["copy_mode"] 40 | ) 41 | + "\n" 42 | + _text[_lang]["request_dst_target"], 43 | reply_markup=_KB.dst_keyboard(update, context), 44 | ) 45 | 46 | return _stage.GET_DST 47 | 48 | 49 | def request_srcinfo(update, context): 50 | global current_dst_info 51 | current_dst_info = update.callback_query.data 52 | update.callback_query.edit_message_text(_text[_lang]["request_share_link"]) 53 | 54 | return _stage.GET_LINK 55 | 56 | -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/js/chunk-238c903c.f33d3eaf.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-238c903c"],{"26fc":function(t,s,a){t.exports=a.p+"static/img/404_cloud.0f4bc32b.png"},"8cdb":function(t,s,a){"use strict";a.r(s);var e=function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("div",{staticClass:"wscn-http404-container"},[a("div",{staticClass:"wscn-http404"},[t._m(0),a("div",{staticClass:"bullshit"},[a("div",{staticClass:"bullshit__oops"},[t._v("OOPS!")]),t._m(1),a("div",{staticClass:"bullshit__headline"},[t._v(t._s(t.message))]),a("div",{staticClass:"bullshit__info"},[t._v("Please check that the URL you entered is correct, or click the button below to return to the homepage.")]),a("a",{staticClass:"bullshit__return-home",attrs:{href:""}},[t._v("Back to home")])])])])},c=[function(){var t=this,s=t.$createElement,e=t._self._c||s;return e("div",{staticClass:"pic-404"},[e("img",{staticClass:"pic-404__parent",attrs:{src:a("a36b"),alt:"404"}}),e("img",{staticClass:"pic-404__child left",attrs:{src:a("26fc"),alt:"404"}}),e("img",{staticClass:"pic-404__child mid",attrs:{src:a("26fc"),alt:"404"}}),e("img",{staticClass:"pic-404__child right",attrs:{src:a("26fc"),alt:"404"}})])},function(){var t=this,s=t.$createElement,a=t._self._c||s;return a("div",{staticClass:"bullshit__info"},[t._v("All rights reserved "),a("a",{staticStyle:{color:"#20a0ff"},attrs:{href:"https://wallstreetcn.com",target:"_blank"}},[t._v("wallstreetcn")])])}],i={name:"Page404",computed:{message:function(){return"The webmaster said that you can not enter this page..."}}},l=i,n=(a("97ef"),a("2877")),r=Object(n["a"])(l,e,c,!1,null,"c095f994",null);s["default"]=r.exports},"97ef":function(t,s,a){"use strict";var e=a("ed94"),c=a.n(e);c.a},a36b:function(t,s,a){t.exports=a.p+"static/img/404.a57b6f31.png"},ed94:function(t,s,a){}}]); -------------------------------------------------------------------------------- /iCopy/utils/restricted.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from functools import wraps 5 | from utils.load import _lang, _text 6 | from utils import messages as _msg, load 7 | from telegram import ParseMode 8 | 9 | def restricted(func): 10 | @wraps(func) 11 | def wrapped(update, context, *args, **kwargs): 12 | _user_id = str(update.effective_user.id) 13 | _first_name = update.effective_user.first_name 14 | if _user_id not in load.ENABLED_USERS: 15 | print(f"Unauthorized access denied for {_user_id}.") 16 | update.effective_message.reply_text(_msg.restricted_msg(_lang,_first_name,_user_id)) 17 | return 18 | return func(update, context, *args, **kwargs) 19 | 20 | return wrapped 21 | 22 | def restricted_quick(func): 23 | @wraps(func) 24 | def wrapped(update, context, *args, **kwargs): 25 | is_quick = {"_id": "fav_quick"} 26 | is_quick_cur = load.fav_col.find(is_quick) 27 | if list(is_quick_cur) == []: 28 | print("fav quick directory is not set.") 29 | update.effective_message.reply_text( 30 | _text[_lang]["null_fav_quick"], 31 | ) 32 | return 33 | return func(update, context, *args, **kwargs) 34 | return wrapped 35 | 36 | def restricted_copy(func): 37 | @wraps(func) 38 | def wrapped(update, context, *args, **kwargs): 39 | is_fav = {"fav_type": "fav"} 40 | is_fav_cur = load.fav_col.find(is_fav) 41 | if list(is_fav_cur) == []: 42 | print("fav directory is not set.") 43 | update.effective_message.reply_text( 44 | _text[_lang]["null_fav"], 45 | ) 46 | return 47 | return func(update, context, *args, **kwargs) 48 | return wrapped -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/js/chunk-2fd9fc79.3cba3c7b.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-2fd9fc79"],{"587b":function(e,t,a){},"7a8a":function(e,t,a){"use strict";a.r(t);var l=function(){var e=this,t=e.$createElement,a=e._self._c||t;return a("el-table",{staticStyle:{width:"100%"},attrs:{data:e.tableData,height:"100%"}},[a("el-table-column",{attrs:{type:"expand"},scopedSlots:e._u([{key:"default",fn:function(t){return[a("el-form",{staticClass:"demo-table-expand",attrs:{"label-position":"left",inline:""}},[a("el-form-item",{attrs:{label:"G Type"}},[a("span",[e._v(e._s(t.row.G_type))])]),a("el-form-item",{attrs:{label:"G NAME"}},[a("span",[e._v(e._s(t.row.G_name))])]),a("el-form-item",{attrs:{label:"G ID"}},[a("span",[e._v(e._s(t.row.G_id))])]),a("el-form-item",{attrs:{label:"Objects"}},[a("span",[e._v(e._s(t.row.fav_object))])]),a("el-form-item",{attrs:{label:"Size"}},[a("span",[e._v(e._s(t.row.show_size))])]),a("el-progress",{attrs:{type:"circle",percentage:t.row.percent,color:e.colors}})],1)]}}])}),a("el-table-column",{attrs:{fixed:"",label:"G Name",prop:"G_name",width:"300"}}),a("el-table-column",{attrs:{label:"G ID",prop:"G_id"}}),a("el-table-column",{attrs:{label:"Objects",prop:"fav_object"}}),a("el-table-column",{attrs:{label:"Size",prop:"show_size"}}),a("el-table-column",{attrs:{label:"Percentage",prop:"show_percent"}})],1)},o=[],r={data:function(){return{tableData:[],colors:[{color:"#58D68D",percentage:20},{color:"#48C9B0",percentage:40},{color:"#5DADE2",percentage:60},{color:"#F4D03F",percentage:80},{color:"#EB984E",percentage:90},{color:"#E74C3C",percentage:100}]}},mounted:function(){var e=this;this.$request({url:"/favlist",method:"get"}).then((function(t){e.tableData=t.data,console.log(t.data)})).catch((function(e){console.log(e)}))}},n=r,s=(a("fd5b"),a("2877")),c=Object(s["a"])(n,l,o,!1,null,null,null);t["default"]=c.exports},fd5b:function(e,t,a){"use strict";var l=a("587b"),o=a.n(l);o.a}}]); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iCopy Version 0.2.2 2 | 3 | [iCopy](https://bbs.jsu.net/c/official-project/icopy/6) 4 | 5 | [![iCopy Telegram-Bot](https://img.shields.io/badge/iCopy-Telegram%20BOT-red?style=flat-square&logo=appveyor)](https://bbs.jsu.net/c/official-project/icopy/6) 6 | [![Programming Language](https://img.shields.io/badge/LANGUAGE-Python%203.6%2B-success?style=flat-square&logo=appveyor)](https://bbs.jsu.net/c/official-project/icopy/6) 7 | [![Version](https://img.shields.io/badge/Version-0.2.2-ff69b4?style=flat-square&logo=appveyor)](https://bbs.jsu.net/c/official-project/icopy/6) 8 | [![License](https://img.shields.io/github/license/fxxkrlab/iCopy?style=flat-square&logo=appveyor)](https://bbs.jsu.net/c/official-project/icopy/6) 9 | [![DATABASE](https://img.shields.io/badge/DATABASE-MongoDB-brightgreen?style=flat-square&logo=appveyor)](https://github.com/mongodb/mongo) 10 | [![Stars](https://img.shields.io/github/stars/Nenokkadine/FClone-Bot?style=flat-square&logo=appveyor)](https://github.com/Nenokkadine/FClone-Bot) 11 | 12 | ## Running it Google Colab 13 | [![Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/Godcic/iCopy-Heroku/blob/master/iCopy.ipynb) 14 | 15 | ## Deployment to Heroku 16 | iCopy Version - v0.2.2 (No Web DashBoard Only Bot) 17 | 18 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://dashboard.heroku.com/new?template=https://github.com/Godcic/iCopy-Heroku/tree/master) 19 | 20 | iCopy Version - v0.2.2 (Only Web DashBoard)(NOT SUPPORT) 21 | 22 | [![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://dashboard.heroku.com/new?template=https://github.com/Godcic/iCopy-Heroku/tree/web) 23 | 24 | Deploy Both If U wanna Use Web Dashboard and use the same config vars for Web one as the Bot one. 25 | 26 | ## Credits 27 | 28 | iCopy - [fxxkrlab](https://github.com/fxxkrlab/iCopy) 29 | Fclone - [Mawaya](https://github.com/mawaya/rclone) 30 | FClone-Bot - [Nenokkadine](https://github.com/Nenokkadine/FClone-Bot) 31 | -------------------------------------------------------------------------------- /iCopy/web/cook_resp.py: -------------------------------------------------------------------------------- 1 | from utils import load 2 | import pymongo 3 | 4 | cfg = load.cfg 5 | 6 | # ### Mongodb 7 | myclient = pymongo.MongoClient( 8 | f"{cfg['database']['db_connect_method']}://{load.user}:{load.passwd}@{cfg['database']['db_addr']}", 9 | port=cfg["database"]["db_port"], 10 | connect=False, 11 | ) 12 | mydb = myclient[cfg["database"]["db_name"]] 13 | 14 | fav_col = mydb["fav_col"] 15 | task_list = mydb["task_list"] 16 | db_counters = mydb["counters"] 17 | 18 | def get_drive_list(): 19 | drivelist = {} 20 | all_drive = load.all_drive 21 | 22 | drivelist['data'] = all_drive 23 | drivelist['code'] = 20000 24 | drivelist['message'] = "" 25 | 26 | return drivelist 27 | 28 | def cook_fav_info(): 29 | favlist = {} 30 | fav_info_array = [] 31 | fav_info = fav_col.find({"fav_type":"fav"},{"_id": 0}) 32 | for each in fav_info: 33 | if 'fav_size' and 'fav_object' in each: 34 | each['show_size'] = str(each['fav_size']) + " " +each['fav_size_tail'] 35 | each['percent'] = float(each['fav_object'] / 4000 ) 36 | each['show_percent'] = str(each['percent']) + "%" 37 | else: 38 | each['fav_size'] = "UNKNOW" 39 | each['fav_object'] = "UNKNOW" 40 | each['show_size'] = "UNKNOW" 41 | each['show_percent'] = "UNKNOW" 42 | fav_info_array.append(each) 43 | 44 | favlist['data'] = fav_info_array 45 | favlist['code'] = 20000 46 | favlist['message'] = "" 47 | 48 | return favlist 49 | 50 | def cook_task_info(): 51 | tasklist = {} 52 | task_info_array = [] 53 | task_info = task_list.find({"status":1,"error":0}) 54 | for each in task_info: 55 | each['show_status'] = "Completed" 56 | if "task_total_prog_size_tail" in each: 57 | each['show_size'] = str(each['task_total_prog_size']) + " " + each['task_total_prog_size_tail'] 58 | else: 59 | each['show_size'] = "UNKNOW" 60 | task_info_array.append(each) 61 | 62 | tasklist['code'] = 20000 63 | tasklist['data'] = task_info_array 64 | tasklist['message'] = "" 65 | 66 | return tasklist 67 | -------------------------------------------------------------------------------- /iCopy/workflow/purge_workflow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from utils.load import _lang, _text, ns 5 | from utils import ( 6 | load, 7 | restricted as _r, 8 | keyboard as _KB, 9 | purge_payload as _p_payload, 10 | callback_stage as _stage, 11 | ) 12 | from multiprocessing import Process as _mp 13 | from telegram.ext import ConversationHandler 14 | 15 | bot = load.bot 16 | ns.purge = 0 17 | 18 | @_r.restricted 19 | def purge(update, context): 20 | update.effective_message.reply_text( 21 | _text[_lang]["mode_select_msg"].replace( 22 | "replace", _text[_lang]["purge_mode"] 23 | ) 24 | + "\n" 25 | + _text[_lang]["request_target_folder"], 26 | reply_markup=_KB.dst_keyboard(update, context), 27 | ) 28 | 29 | return _stage.COOK_FAV_PURGE 30 | 31 | def pre_to_purge(update, context): 32 | get_callback = update.callback_query.data 33 | purge_msg = bot.edit_message_text( 34 | chat_id=update.callback_query.message.chat_id, 35 | message_id=update.callback_query.message.message_id, 36 | text=_text[_lang]["ready_to_purge"], 37 | reply_markup=None, 38 | ) 39 | 40 | purge_chat_id = purge_msg.chat_id 41 | purge_message_id = purge_msg.message_id 42 | 43 | fav_info_list = get_callback.split("id+name") 44 | fav_id = fav_info_list[0] 45 | fav_name = fav_info_list[1] 46 | 47 | if len(fav_id) < 28: 48 | progress = _mp( 49 | target=_p_payload.purge_fav, 50 | args=( 51 | ns, 52 | purge_chat_id, 53 | purge_message_id, 54 | fav_id, 55 | fav_name, 56 | ), 57 | ) 58 | 59 | progress.start() 60 | 61 | bot.edit_message_text( 62 | chat_id=purge_chat_id, 63 | message_id=purge_message_id, 64 | text=_text[_lang]["purging"], 65 | ) 66 | 67 | return ConversationHandler.END 68 | 69 | else: 70 | bot.edit_message_text( 71 | chat_id=purge_chat_id, 72 | message_id=purge_message_id, 73 | text=_text[_lang]["is_folder_not_drive"], 74 | ) 75 | 76 | return ConversationHandler.END -------------------------------------------------------------------------------- /iCopy/utils/load.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os, sys, toml 5 | import pymongo 6 | from urllib import parse 7 | from drive import gdrive 8 | from multiprocessing import Manager 9 | from telegram.utils.request import Request as TGRequest 10 | from telegram import Bot 11 | 12 | 13 | _cfgFile_RAW = os.path.abspath(os.path.join("config", "conf.toml")) 14 | cfg = toml.load(_cfgFile_RAW) 15 | _textFile_RAW = os.path.abspath(os.path.join("config", "text.toml")) 16 | _text = toml.load(_textFile_RAW) 17 | 18 | 19 | ### load language selector 20 | _lang = cfg["general"]["language"] 21 | 22 | ### ENABLED_USERS 23 | ENABLED_USERS = os.environ.get("ENABLED_USERS", f"{cfg['tg']['usr_id']}") 24 | 25 | ### Mongodb 26 | user = parse.quote_plus(f"{cfg['database']['db_user']}") 27 | passwd = parse.quote_plus(f"{cfg['database']['db_passwd']}") 28 | myclient = pymongo.MongoClient( 29 | f"{cfg['database']['db_connect_method']}://{user}:{passwd}@{cfg['database']['db_addr']}", 30 | port=cfg["database"]["db_port"], 31 | connect=False, 32 | ) 33 | mydb = myclient[cfg["database"]["db_name"]] 34 | 35 | # main_col = mydb['main_col'] 36 | fav_col = mydb["fav_col"] 37 | task_list = mydb["task_list"] 38 | db_counters = mydb["counters"] 39 | login_col = mydb["login_col"] 40 | task_details = mydb["task_details"] 41 | 42 | # ### check is dashboard admin 43 | if cfg['web']['dashboard']: 44 | vaild_usr_doc = login_col.find_one({"_id": "login_info"}) 45 | if vaild_usr_doc is None: 46 | login_col.insert_one({"_id": "login_info","user_id":1,"username":"admin","password":"admin","user_role":"admin"}) 47 | 48 | ### drive().list 49 | all_drive = gdrive.GoogleDrive().drive_list() 50 | 51 | ### ns 52 | manager = Manager() 53 | ns = manager.Namespace() 54 | 55 | ### Restore Unexpected Interrupted Task status 2 --> 0 56 | task_list.update_one( 57 | {"status": 2}, {"$set": {"status": 0,}}, 58 | ) 59 | 60 | ### regex entry pattern 61 | regex_entry_pattern = r"https://drive\.google\.com/(?:drive/(?:u/[\d]+/)?(?:mobile/)?folders/([\w.\-_]+)(?:\?[\=\w]+)?|folderview\?id=([\w.\-_]+)(?:\&[=\w]+)?|open\?id=([\w.\-_]+)(?:\&[=\w]+)?|(?:a/[\w.\-_]+/)?file/d/([\w.\-_]+)|(?:a/[\w.\-_]+/)?uc\?id\=([\w.\-_]+)&?)" 62 | 63 | ### define bot 64 | request = TGRequest(con_pool_size=8) 65 | bot = Bot(token=f"{cfg['tg']['token']}", request=request) -------------------------------------------------------------------------------- /iCopy/utils/keyboard.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from utils import load 5 | from telegram import InlineKeyboardButton, InlineKeyboardMarkup 6 | 7 | _langtext = load._text[load._lang] 8 | 9 | # start InlineKeyBoard 10 | def start_keyboard(): 11 | keyboard = [ 12 | [ 13 | InlineKeyboardButton(_langtext['quick_mode'], callback_data="quick"), 14 | InlineKeyboardButton(_langtext['copy_mode'], callback_data="copy"), 15 | ], 16 | ] 17 | 18 | return InlineKeyboardMarkup(keyboard) 19 | 20 | def regex_in_keyboard(): 21 | keyboard = [ 22 | [ 23 | InlineKeyboardButton(_langtext['quick_mode'], callback_data="quick"), 24 | InlineKeyboardButton(_langtext['copy_mode'], callback_data="copy"), 25 | InlineKeyboardButton(_langtext['size_mode'], callback_data="size"), 26 | ], 27 | ] 28 | 29 | return InlineKeyboardMarkup(keyboard) 30 | 31 | def is_cover_keyboard(): 32 | keyboard = [ 33 | [ 34 | InlineKeyboardButton(_langtext['is_cover'], callback_data="cover_quick"), 35 | InlineKeyboardButton(_langtext['not_cover'], callback_data="not_cover_quick"), 36 | ], 37 | ] 38 | 39 | return InlineKeyboardMarkup(keyboard) 40 | 41 | def dedupe_mode_keyboard(): 42 | keyboard = [ 43 | [ 44 | InlineKeyboardButton("first", callback_data="first"), 45 | ], 46 | [ 47 | InlineKeyboardButton("newest", callback_data="newest"), 48 | InlineKeyboardButton("oldest", callback_data="oldest"), 49 | ], 50 | [ 51 | InlineKeyboardButton("largest", callback_data="largest"), 52 | InlineKeyboardButton("smallest", callback_data="smallest"), 53 | ], 54 | ] 55 | 56 | return InlineKeyboardMarkup(keyboard) 57 | 58 | def dst_keyboard(update, context): 59 | favs = load.fav_col.find({"fav_type":"fav"}) 60 | button_list = [] 61 | 62 | for item in favs: 63 | button_list.append(InlineKeyboardButton(item['G_name'], callback_data=item['G_id']+"id+name"+item['G_name'])) 64 | return InlineKeyboardMarkup(build_dst_keyboard(button_list,n_cols=2)) 65 | 66 | def build_dst_keyboard(buttons,n_cols,header_buttons=None,footer_buttons=None): 67 | menu = [buttons[i:i + n_cols] for i in range(0, len(buttons), n_cols)] 68 | if header_buttons: 69 | menu.insert(0, header_buttons) 70 | if footer_buttons: 71 | menu.append(footer_buttons) 72 | return menu -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/js/chunk-285369fe.c6caa2dc.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-285369fe"],{2017:function(e,t,s){"use strict";var n=s("cafe"),o=s.n(n);o.a},7538:function(e,t,s){},"9ed6":function(e,t,s){"use strict";s.r(t);var n=function(){var e=this,t=e.$createElement,s=e._self._c||t;return s("div",{staticClass:"login-container"},[s("el-form",{ref:"loginForm",staticClass:"login-form",attrs:{model:e.loginForm,rules:e.loginRules,"auto-complete":"on","label-position":"left"}},[s("div",{staticClass:"title-container"},[s("h3",{staticClass:"title"},[e._v("iCopy DASHBOARD")])]),s("el-form-item",{attrs:{prop:"username"}},[s("span",{staticClass:"svg-container"},[s("svg-icon",{attrs:{"icon-class":"user"}})],1),s("el-input",{ref:"username",attrs:{placeholder:"Username",name:"username",type:"text",tabindex:"1","auto-complete":"on"},model:{value:e.loginForm.username,callback:function(t){e.$set(e.loginForm,"username",t)},expression:"loginForm.username"}})],1),s("el-form-item",{attrs:{prop:"password"}},[s("span",{staticClass:"svg-container"},[s("svg-icon",{attrs:{"icon-class":"password"}})],1),s("el-input",{key:e.passwordType,ref:"password",attrs:{type:e.passwordType,placeholder:"Password",name:"password",tabindex:"2","auto-complete":"on"},nativeOn:{keyup:function(t){return!t.type.indexOf("key")&&e._k(t.keyCode,"enter",13,t.key,"Enter")?null:e.handleLogin(t)}},model:{value:e.loginForm.password,callback:function(t){e.$set(e.loginForm,"password",t)},expression:"loginForm.password"}}),s("span",{staticClass:"show-pwd",on:{click:e.showPwd}},[s("svg-icon",{attrs:{"icon-class":"password"===e.passwordType?"eye":"eye-open"}})],1)],1),s("el-button",{staticStyle:{width:"100%","margin-bottom":"30px"},attrs:{loading:e.loading,type:"primary"},nativeOn:{click:function(t){return t.preventDefault(),e.handleLogin(t)}}},[e._v("Login")])],1)],1)},o=[],r={name:"Login",data:function(){var e=function(e,t,s){s()},t=function(e,t,s){s()};return{loginForm:{username:"",password:""},loginRules:{username:[{required:!0,trigger:"blur",validator:e}],password:[{required:!0,trigger:"blur",validator:t}]},loading:!1,passwordType:"password",redirect:void 0}},watch:{$route:{handler:function(e){this.redirect=e.query&&e.query.redirect},immediate:!0}},methods:{showPwd:function(){var e=this;"password"===this.passwordType?this.passwordType="":this.passwordType="password",this.$nextTick((function(){e.$refs.password.focus()}))},handleLogin:function(){var e=this;this.$refs.loginForm.validate((function(t){if(!t)return console.log("error submit!!"),!1;e.loading=!0,e.$store.dispatch("user/login",e.loginForm).then((function(){e.$router.push({path:e.redirect||"/"}),e.loading=!1})).catch((function(){e.loading=!1}))}))}}},a=r,i=(s("2017"),s("c785"),s("2877")),l=Object(i["a"])(a,n,o,!1,null,"8bbd11dc",null);t["default"]=l.exports},c785:function(e,t,s){"use strict";var n=s("7538"),o=s.n(n);o.a},cafe:function(e,t,s){}}]); -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "TG-iCopy", 3 | "description": "Fclone Telegram Bot", 4 | "repository": "https://github.com/Nenokkadine/Fclone-Bot", 5 | "keywords": ["Fclone","Icopy"], 6 | "env": { 7 | "BOT_TOKEN": { 8 | "description": "Get from Telegram Botfather" 9 | }, 10 | "USER_ID": { 11 | "description": "get from telegram get_id_bot" 12 | }, 13 | "DB_ADDRESS": { 14 | "description": "MongoDB Host Address" 15 | }, 16 | "DB_USERNAME": { 17 | "description": "MongoDB Username" 18 | }, 19 | "DB_PASS": { 20 | "description": "MongoDB Password" 21 | }, 22 | "SA_ZIP_URL": { 23 | "description": "Service Accounts ZIP URL.It Should be Zipped Such that it should have a folder named accounts with SA in it " 24 | }, 25 | "SA_INIT_FILE": { 26 | "description": "Give a service account File Name. ex 1.json " 27 | }, 28 | "LANGUAGE": { 29 | "description": "cn by Default Support eng and jp", 30 | "value": "cn" 31 | }, 32 | "PARALLEL_CHECKERS": { 33 | "description": "Dont Give More than 300 Heroku Free Dyno may Crash", 34 | "value": "250" 35 | }, 36 | "PARALLEL_TRANSFERS": { 37 | "description": "Dont Give More than 300 Heroku Free Dyno may Crash", 38 | "value": "250" 39 | }, 40 | "DB_CONNECT_METHOD": { 41 | "description": "DB Connect Method", 42 | "value": "mongodb+srv" 43 | }, 44 | "DB_PORT": { 45 | "description": "MongoDB Port, Default is 27017", 46 | "value": "27017" 47 | }, 48 | "DB_NAME": { 49 | "description": "U Can give any name, Default is iCopy", 50 | "value": "iCopy" 51 | }, 52 | "CLONER": { 53 | "description": "Dont Change this", 54 | "value": "fclone" 55 | }, 56 | "OPTION": { 57 | "description": "Copy or Sync Default is Copy", 58 | "value": "copy" 59 | }, 60 | "RCLONE_RMT": { 61 | "description": "Give a Rclone Remote Name Default is also fine", 62 | "value": "icopy" 63 | }, 64 | "MIN_SLEEP": { 65 | "description": "customize drive-pacer-min-sleep", 66 | "value": "1ms" 67 | }, 68 | "RCLONE_CONFIG": { 69 | "description": "Dont Change", 70 | "value": "/app/iCopy/config/rclone.conf" 71 | }, 72 | "RUN_ARGS": { 73 | "description": "Only Change this if U Know", 74 | "value": "['--log-level=DEBUG', '--log-file=/app/icopy_cloner_debug.log']" 75 | }, 76 | "SA_PATH": { 77 | "description": "Dont Change", 78 | "value": "/app/iCopy/accounts" 79 | } 80 | }, 81 | "buildpacks": [ 82 | { 83 | "url": "heroku/python" 84 | }, 85 | { 86 | "url": "https://github.com/heroku/heroku-buildpack-apt.git" 87 | }, 88 | { 89 | "url": "https://github.com/opendoor-labs/heroku-buildpack-p7zip.git" 90 | }, 91 | { 92 | "url" : "https://github.com/Godcic/Fpack.git" 93 | } 94 | ], 95 | "formation": { 96 | "worker": { 97 | "quantity": 1, 98 | "size": "free" 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /iCopy/utils/messages.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | 5 | def restricted_msg(_lang,_first_name,_user_id): 6 | if "cn" == _lang: 7 | return(f"HI ! {_first_name} 您好\n" 8 | f"您的用户ID:{_user_id} 未经授权\n" 9 | "请用正确的方式添加") 10 | if "eng" == _lang: 11 | return(f"HI ! {_first_name}\n" 12 | f"Your user ID:{_user_id} is not allowed\n" 13 | "Pls set it in the correct way") 14 | if "jp" == _lang: 15 | return(f"HI ! {_first_name} こんにちは" 16 | f"ユーザーID:{_user_id}は許可されていない" 17 | "正しい方法で追加してください") 18 | 19 | # ##### /set Messages ##### 20 | 21 | def set_help(_lang): 22 | if "cn" == _lang: 23 | return("命令必须符合'/set' 或 '/set rule'规则") 24 | if "eng" == _lang: 25 | return("Only rules in '/set' and '/set rule' is vaild") 26 | if "jp" == _lang: 27 | return("'/set'または'/set rule'のルールに準拠する必要がある") 28 | 29 | def set_multi_fav_rule(): 30 | return ("\n " 31 | "`quick +\- folder_ID/drive_ID` \n " 32 | "`fav +\- folder_ID/drive_ID` \n " 33 | "\n") 34 | 35 | def set_multi_fav_guide(_lang): 36 | 37 | if "cn" == _lang: 38 | return ("*请输入需要修改的目标地址* \n " 39 | "\n" 40 | "例 : 如下 *\+/\-* \n " 41 | "_quick \| fav_ 为对应前缀 \n " 42 | + set_multi_fav_rule() + 43 | "说明:" 44 | "随意组合排序: '*\+*' *_增加_*, '*\-*' *_取消_* \n " 45 | "_quick_ 只可存在_一个_,为快速目录") 46 | if "eng" == _lang: 47 | return ("*pls modify the Dst\\_ID List* \n " 48 | "\n" 49 | "e\.g : *\+/\-* \n " 50 | "_quick \| drive \| folder_ is the prefix \n " 51 | + set_multi_fav_rule() + 52 | "explain:" 53 | "The order does not matter: '*\+*' *_select_*, '*\-*' *_unselect_* \n " 54 | "_Only one quick\\_id can exist for quick\\_mode_") 55 | if "jp" == _lang: 56 | return ("*フォルダーIDやシェアドライバIDを入力してください* \n " 57 | "\n" 58 | "例 : 接頭辞 *\+/\-* ID\n " 59 | "_quick と drive と folder_ は接頭辞です \n " 60 | + set_multi_fav_rule() + 61 | "説明:" 62 | "順番は関係ない: '*\+*' *_增加する_*, '*\-*' *_キャンセル_* \n " 63 | "「クイックモード」は1つしか存在できません") 64 | 65 | def set_single_fav_rule(): 66 | return ("\n " 67 | "`/set quick\|fav \+/\- folder/drive ID` \n " 68 | "\n") 69 | 70 | def set_single_fav_guide(_lang): 71 | 72 | if "cn" == _lang: 73 | return ("*请输入需要修改的目标地址* \n " 74 | "\n" 75 | "例 : 如下 *\+/\-* \n " 76 | + set_single_fav_rule()) 77 | if "eng" == _lang: 78 | return ("*pls modify the Dst\\_ID List* \n " 79 | "\n" 80 | "e\.g : *\+/\-* \n " 81 | + set_single_fav_rule()) 82 | if "jp" == _lang: 83 | return ("*フォルダーIDやシェアドライバIDを入力してください* \n " 84 | "\n" 85 | "例 : 接頭辞 *\+/\-* ID\n " 86 | + set_single_fav_rule()) 87 | 88 | def get_fav_len_invaild(_lang, each): 89 | if "cn" == _lang: 90 | return(f"您提交的 ID:{each[6:]} 不是有效的") 91 | if "eng" == _lang: 92 | return(f"ID:{each[6:]} is not vaild") 93 | if "jp" == _lang: 94 | return(f"入力したID:{each[6:]}は無効です") 95 | -------------------------------------------------------------------------------- /iCopy/drive/gdrive.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os, sys, logging, random 5 | 6 | from glob import glob 7 | from googleapiclient import discovery 8 | from google.oauth2 import service_account 9 | from googleapiclient import errors 10 | from google.auth.transport.requests import Request 11 | 12 | from utils import load 13 | logger = logging.getLogger(__name__) 14 | logging.getLogger('googleapiclient.discovery').setLevel(logging.CRITICAL) 15 | 16 | class GoogleDrive: 17 | def __init__(self): 18 | service_account_file = random.choice(glob(load.cfg['general']['sa_path'] + '/*.json')) 19 | 20 | credentials = None 21 | scopes = ['https://www.googleapis.com/auth/drive'] 22 | 23 | credentials = service_account.Credentials.from_service_account_file( 24 | service_account_file, scopes=scopes) 25 | 26 | self.service = discovery.build('drive', 'v3', credentials=credentials, cache_discovery=False) 27 | 28 | def drive_list(self): 29 | result = [] 30 | raw_drives = {} 31 | page_token = None 32 | 33 | while True: 34 | try: 35 | param = { 36 | 'pageSize': 100, 37 | } 38 | if page_token: 39 | param['pageToken'] = page_token 40 | drives = self.service.drives().list(**param).execute() 41 | 42 | result.extend(drives['drives']) 43 | logger.debug('Received {} drives'.format(len(drives['drives']))) 44 | page_token = drives.get('nextPageToken') 45 | if not page_token: 46 | break 47 | except: 48 | break 49 | 50 | for item in result: 51 | raw_drives[item['id']] = item['name'] 52 | return raw_drives 53 | 54 | def file_get_name(self, file_id): 55 | param = { 56 | 'fileId': file_id, 57 | 'supportsAllDrives': True, 58 | 'fields': 'name, driveId', 59 | } 60 | raw_file_info = self.service.files().get(**param).execute() 61 | file_name = raw_file_info['name'] 62 | 63 | return file_name 64 | 65 | def drive_get(self, drive_id): 66 | param = { 67 | 'driveId': drive_id, 68 | } 69 | drive_info = self.service.drives().get(**param).execute() 70 | 71 | return drive_info 72 | 73 | def get_dst_endpoint_id(self, dst_id, src_name): 74 | page_token = None 75 | result = [] 76 | while True: 77 | try: 78 | param = { 79 | 'q': r"name = '{}' and " 80 | r"mimeType = 'application/vnd.google-apps.folder' and " 81 | r"'{}' in parents and trashed = false".format(src_name, dst_id), 82 | 'includeItemsFromAllDrives': True, 83 | 'supportsAllDrives': True, 84 | 'fields': 'nextPageToken, files(id, name)', 85 | 'pageSize': 1000, 86 | } 87 | if page_token: 88 | param['pageToken'] = page_token 89 | 90 | all_files = self.service.files().list(**param).execute() 91 | result = all_files['files'][0] 92 | page_token = all_files.get('nextPageToken') 93 | 94 | if not page_token: 95 | break 96 | except: 97 | break 98 | return result -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/css/chunk-libs.3dfb7769.css: -------------------------------------------------------------------------------- 1 | /*! normalize.css v7.0.0 | MIT License | github.com/necolas/normalize.css */html{line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}abbr[title]{border-bottom:none;text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted}b,strong{font-weight:inherit;font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{padding:.35em .75em .625em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}[hidden],template{display:none}#nprogress{pointer-events:none}#nprogress .bar{background:#29d;position:fixed;z-index:1031;top:0;left:0;width:100%;height:2px}#nprogress .peg{display:block;position:absolute;right:0;width:100px;height:100%;-webkit-box-shadow:0 0 10px #29d,0 0 5px #29d;box-shadow:0 0 10px #29d,0 0 5px #29d;opacity:1;-webkit-transform:rotate(3deg) translateY(-4px);transform:rotate(3deg) translateY(-4px)}#nprogress .spinner{display:block;position:fixed;z-index:1031;top:15px;right:15px}#nprogress .spinner-icon{width:18px;height:18px;-webkit-box-sizing:border-box;box-sizing:border-box;border:2px solid transparent;border-top-color:#29d;border-left-color:#29d;border-radius:50%;-webkit-animation:nprogress-spinner .4s linear infinite;animation:nprogress-spinner .4s linear infinite}.nprogress-custom-parent{overflow:hidden;position:relative}.nprogress-custom-parent #nprogress .bar,.nprogress-custom-parent #nprogress .spinner{position:absolute}@-webkit-keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg)}to{-webkit-transform:rotate(1turn)}}@keyframes nprogress-spinner{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}to{-webkit-transform:rotate(1turn);transform:rotate(1turn)}} -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/js/chunk-130a7162.684ec14a.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"]=window["webpackJsonp"]||[]).push([["chunk-130a7162"],{"29af":function(a,t,s){"use strict";s.r(t);var e=function(){var a=this,t=a.$createElement,s=a._self._c||t;return s("el-table",{staticStyle:{width:"100%"},attrs:{data:a.tableData,height:"100%","default-sort":{prop:"_id",order:"descending"}}},[s("el-table-column",{attrs:{type:"expand"},scopedSlots:a._u([{key:"default",fn:function(t){return[s("el-form",{staticClass:"demo-table-expand",attrs:{"label-position":"left",inline:""}},[s("el-form-item",{attrs:{label:"TASK ID"}},[s("i",{staticClass:"fas fa-flag"}),s("span",[a._v(" "+a._s(t.row._id))])]),s("el-form-item",{attrs:{label:"Status"}},[s("i",{staticClass:"fas fa-check-double"}),s("span",[a._v(" "+a._s(t.row.show_status))])]),s("el-form-item",{attrs:{label:"Souce"}},[s("i",{staticClass:"fas fa-id-card"}),a._v(" : "),s("i",{staticClass:"fas fa-chevron-left"}),s("span",[a._v(a._s(t.row.src_name))]),s("i",{staticClass:"fas fa-chevron-right"}),s("br"),s("i",{staticClass:"fas fa-barcode"}),a._v(" : "),s("i",{staticClass:"fas fa-chevron-left"}),s("span",[a._v(a._s(t.row.src_id))]),s("i",{staticClass:"fas fa-chevron-right"})]),s("el-form-item",{attrs:{label:"Destination"}},[s("i",{staticClass:"fas fa-id-card"}),a._v(" : "),s("i",{staticClass:"fas fa-chevron-left"}),s("span",[a._v(a._s(t.row.dst_name))]),s("i",{staticClass:"fas fa-chevron-right"}),s("br"),s("i",{staticClass:"fas fa-barcode"}),a._v(" : "),s("i",{staticClass:"fas fa-chevron-left"}),s("span",[a._v(a._s(t.row.dst_id))]),s("i",{staticClass:"fas fa-chevron-right"})]),s("el-form-item",{attrs:{label:"Task Time"}},[s("span",[s("i",{staticClass:"far fa-calendar-alt"}),a._v(" : "+a._s(t.row.create_time)+" (created)")]),s("br"),s("span",[s("i",{staticClass:"far fa-calendar-plus"}),a._v(" : "+a._s(t.row.start_time)+" (started)")]),s("br"),s("span",[s("i",{staticClass:"far fa-calendar-check"}),a._v(" : "+a._s(t.row.finished_time)+" (finished)")])]),s("el-form-item",{attrs:{label:"Endpoint INFO"}},[s("i",{staticClass:"fas fa-id-card"}),a._v(" : "),s("i",{staticClass:"fas fa-chevron-left"}),s("span",[a._v(a._s(t.row.src_name))]),s("i",{staticClass:"fas fa-chevron-right"}),s("br"),s("i",{staticClass:"fas fa-barcode"}),a._v(" : "),s("i",{staticClass:"fas fa-chevron-left"}),s("span",[a._v(a._s(t.row.dst_endpoint_id))]),s("i",{staticClass:"fas fa-chevron-right"}),s("br"),s("i",{staticClass:"fas fa-external-link-alt"}),s("span",[a._v(" : "),s("el-link",{attrs:{href:t.row.dst_endpoint_link,target:"_blank"}},[a._v("GDrive Link")])],1),s("br"),s("i",{staticClass:"fas fa-file-import"}),s("span",[a._v(" : "+a._s(t.row.task_total_prog_num)+" (total objects)")]),s("br"),s("i",{staticClass:"fas fa-archive"}),s("span",[a._v(" : "+a._s(t.row.show_size)+" (total size)")])])],1)]}}])}),s("el-table-column",{attrs:{fixed:"",label:"ID",prop:"_id",sortable:"",width:"100"}}),s("el-table-column",{attrs:{label:"Name",prop:"src_name",formatter:a.formatter,width:"900"}}),s("el-table-column",{attrs:{label:"Objects",prop:"task_total_prog_num",width:"200"}}),s("el-table-column",{attrs:{label:"Size",prop:"show_size",width:"200"}})],1)},i=[],r={data:function(){return{tableData:[]}},mounted:function(){var a=this;this.$request({url:"/tasklist",method:"get"}).then((function(t){a.tableData=t.data})).catch((function(a){console.log(a)}))},methods:{formatter:function(a){return a.src_name}}},l=r,n=(s("f1ec"),s("2877")),o=Object(n["a"])(l,e,i,!1,null,null,null);t["default"]=o.exports},b044:function(a,t,s){},f1ec:function(a,t,s){"use strict";var e=s("b044"),i=s.n(e);i.a}}]); -------------------------------------------------------------------------------- /iCopy/utils/purge_payload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import re, time, pymongo 5 | from utils import load 6 | from utils.load import _lang, _text 7 | import subprocess 8 | from telegram.utils.request import Request as TGRequest 9 | from telegram import Bot 10 | from threading import Thread 11 | from multiprocessing import Manager 12 | 13 | myclient = pymongo.MongoClient( 14 | f"{load.cfg['database']['db_connect_method']}://{load.user}:{load.passwd}@{load.cfg['database']['db_addr']}", 15 | port=load.cfg["database"]["db_port"], 16 | connect=False, 17 | ) 18 | mydb = myclient[load.cfg["database"]["db_name"]] 19 | fav_col = mydb["fav_col"] 20 | 21 | _cfg = load.cfg 22 | purging_process = subprocess.Popen 23 | 24 | request = TGRequest(con_pool_size=8) 25 | bot = Bot(token=f"{_cfg['tg']['token']}", request=request) 26 | 27 | def purge_run(command): 28 | global purging_process 29 | purging_process = subprocess.Popen( 30 | command, 31 | stdout=subprocess.PIPE, 32 | stderr=subprocess.STDOUT, 33 | shell=False, 34 | encoding="utf-8", 35 | errors="ignore", 36 | universal_newlines=True, 37 | ) 38 | while True: 39 | line = purging_process.stdout.readline().rstrip() 40 | if not line: 41 | break 42 | yield line 43 | purging_process.communicate() 44 | 45 | def purge_fav(ns, purge_chat_id, purge_message_id, fav_id, fav_name): 46 | cloner = _cfg["general"]["cloner"] 47 | option1 = "delete" 48 | option2 = "rmdirs" 49 | remote = _cfg["general"]["remote"] 50 | src_id = fav_id 51 | src_block = remote + ":" + "{" + src_id + "}" 52 | checkers = "--checkers=" + f"{_cfg['general']['parallel_c']}" 53 | transfers = "--transfers=" + f"{_cfg['general']['parallel_t']}" 54 | rmdirs = "--rmdirs" 55 | flags = ["--drive-trashed-only", "--drive-use-trash=false", "-P"] 56 | flags += _cfg["general"]["run_args"] 57 | sa_sleep = "--drive-pacer-min-sleep=" + f"{_cfg['general']['min_sleep']}" 58 | 59 | command1 = [cloner, option1, src_block, rmdirs, checkers, transfers, sa_sleep] 60 | command1 += flags 61 | 62 | command2 = [cloner, option2, src_block, checkers, transfers, sa_sleep] 63 | command2 += flags 64 | 65 | purge_process(ns, command1, command2, purge_chat_id, purge_message_id, fav_id, fav_name) 66 | 67 | ns.purge = 0 68 | 69 | def purge_process(ns, command1, command2, purge_chat_id, purge_message_id, fav_id, fav_name): 70 | for output in purge_run(command=command1): 71 | if ns.purge == 1: 72 | purging_process.kill() 73 | 74 | for output in purge_run(command=command2): 75 | if ns.purge == 1: 76 | purging_process.kill() 77 | 78 | if ns.purge == 0: 79 | last_purge_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 80 | 81 | fav_col.update_one( 82 | {"G_id": fav_id}, {"$set": {"last_purge_time": last_purge_time,}}, 83 | ) 84 | 85 | purged_msg = ( 86 | "༺ ✪iCopy✪ ༻\n\n" 87 | + fav_name 88 | + "\n[" 89 | + fav_id 90 | + "]\n\n" 91 | + _text[_lang]["purging_done"] 92 | ) 93 | 94 | bot.edit_message_text( 95 | chat_id=purge_chat_id, message_id=purge_message_id, text=purged_msg, 96 | ) 97 | 98 | if ns.purge == 1: 99 | bot.edit_message_text( 100 | chat_id=purge_chat_id, 101 | message_id=purge_message_id, 102 | text=_text[_lang]["is_killed_by_user"], 103 | ) 104 | -------------------------------------------------------------------------------- /iCopy/web/dash.py: -------------------------------------------------------------------------------- 1 | import json, logging, hashlib 2 | from sanic import Sanic, response 3 | from sanic_cors import CORS, cross_origin 4 | from utils import load 5 | from web import cook_resp as _resp 6 | 7 | logging.basicConfig( 8 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.DEBUG 9 | ) 10 | 11 | app = Sanic(__name__) 12 | CORS(app) 13 | _cfg = load.cfg 14 | 15 | async def get_vaild_info(): 16 | mydb = _resp.mydb 17 | login_col = mydb["login_col"] 18 | vaild_usr_doc = login_col.find_one({"_id": "login_info"}) 19 | vaild_username = vaild_usr_doc["username"] 20 | vaild_password = vaild_usr_doc["password"] 21 | vaild_info = {"vaild_username":vaild_username,"vaild_password":vaild_password} 22 | return vaild_info 23 | 24 | async def auth(token): 25 | result = {} 26 | vaild_info = await get_vaild_info() 27 | vaild_username = vaild_info['vaild_username'] 28 | vaild_password = vaild_info['vaild_password'] 29 | vaild_token = hashlib.md5((str(vaild_username) + str(vaild_password)).encode('utf-8')).hexdigest() 30 | if token != vaild_token: 31 | result['code'] = 50008 32 | result['message'] = "login faild" 33 | 34 | return response.json(result) 35 | 36 | else: 37 | return True 38 | 39 | # ### login&auth route.v1 40 | @app.route("/v1/login", methods=["POST",]) 41 | async def login(request): 42 | result = {} 43 | vaild_info = await get_vaild_info() 44 | current_username = request.json['username'] 45 | current_password = request.json['password'] 46 | vaild_username = vaild_info['vaild_username'] 47 | vaild_password = vaild_info['vaild_password'] 48 | if vaild_username == current_username and vaild_password == current_password: 49 | token = hashlib.md5((str(vaild_username) + str(vaild_password)).encode('utf-8')).hexdigest() 50 | result['code'] = 20000 51 | result['data'] = {"token":token} 52 | result['message'] = "login success" 53 | 54 | else: 55 | result['code'] = 20001 56 | result['message'] = "login faild" 57 | 58 | return response.json(result) 59 | 60 | @app.route("/v1/userinfo") 61 | async def userinfo(request): 62 | result = {} 63 | vaild_info = await get_vaild_info() 64 | vaild_username = vaild_info['vaild_username'] 65 | await auth(request.args['token']) 66 | result['code'] = 20000 67 | result['data'] = {"name":vaild_username,"avatar":"https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif"} 68 | result['message'] = "" 69 | 70 | return response.json(result) 71 | 72 | # ### api route.v1 73 | 74 | @app.route("/v1/drivelist") 75 | async def drivelist(request): 76 | x_auth = await auth(request.headers['X-Token']) 77 | if x_auth != True: 78 | return x_auth 79 | 80 | drivelist = _resp.get_drive_list() 81 | 82 | return response.json(drivelist, ensure_ascii=False) 83 | 84 | 85 | @app.route("/v1/favlist") 86 | async def sanic_fav_info(request): 87 | x_auth = await auth(request.headers['X-Token']) 88 | if x_auth != True: 89 | return x_auth 90 | 91 | fav_info = _resp.cook_fav_info() 92 | 93 | return response.json(fav_info, ensure_ascii=False) 94 | 95 | @app.route("/v1/tasklist") 96 | async def sanic_task_info(request): 97 | x_auth = await auth(request.headers['X-Token']) 98 | if x_auth != True: 99 | return x_auth 100 | 101 | task_info = _resp.cook_task_info() 102 | 103 | return response.json(task_info, ensure_ascii=False) 104 | 105 | @app.route("/v1/currenttask") 106 | async def taskdetail(request): 107 | x_auth = await auth(request.headers['X-Token']) 108 | if x_auth != True: 109 | return x_auth 110 | 111 | task_sum = load.task_list.find() 112 | y = len(list(task_sum)) 113 | x = "Current Task SUM" 114 | 115 | return response.json({x: y}) 116 | 117 | app.static("/", "./web/vue-admin-simple/dist/") 118 | app.static("/", "./web/vue-admin-simple/dist/index.html", content_type="text/html; charset=utf-8") 119 | web_port = _cfg['web']['port'] 120 | def dashboard(): 121 | app.run(host="0.0.0.0", port=web_port) -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/static/css/chunk-238c903c.3c7f5ad9.css: -------------------------------------------------------------------------------- 1 | .wscn-http404-container[data-v-c095f994]{-webkit-transform:translate(-50%,-50%);transform:translate(-50%,-50%);position:absolute;top:40%;left:50%}.wscn-http404[data-v-c095f994]{position:relative;width:1200px;padding:0 50px;overflow:hidden}.wscn-http404 .pic-404[data-v-c095f994]{position:relative;float:left;width:600px;overflow:hidden}.wscn-http404 .pic-404__parent[data-v-c095f994]{width:100%}.wscn-http404 .pic-404__child[data-v-c095f994]{position:absolute}.wscn-http404 .pic-404__child.left[data-v-c095f994]{width:80px;top:17px;left:220px;opacity:0;-webkit-animation-name:cloudLeft-data-v-c095f994;animation-name:cloudLeft-data-v-c095f994;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1s;animation-delay:1s}.wscn-http404 .pic-404__child.mid[data-v-c095f994]{width:46px;top:10px;left:420px;opacity:0;-webkit-animation-name:cloudMid-data-v-c095f994;animation-name:cloudMid-data-v-c095f994;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1.2s;animation-delay:1.2s}.wscn-http404 .pic-404__child.right[data-v-c095f994]{width:62px;top:100px;left:500px;opacity:0;-webkit-animation-name:cloudRight-data-v-c095f994;animation-name:cloudRight-data-v-c095f994;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-delay:1s;animation-delay:1s}@-webkit-keyframes cloudLeft-data-v-c095f994{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@keyframes cloudLeft-data-v-c095f994{0%{top:17px;left:220px;opacity:0}20%{top:33px;left:188px;opacity:1}80%{top:81px;left:92px;opacity:1}to{top:97px;left:60px;opacity:0}}@-webkit-keyframes cloudMid-data-v-c095f994{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@keyframes cloudMid-data-v-c095f994{0%{top:10px;left:420px;opacity:0}20%{top:40px;left:360px;opacity:1}70%{top:130px;left:180px;opacity:1}to{top:160px;left:120px;opacity:0}}@-webkit-keyframes cloudRight-data-v-c095f994{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}@keyframes cloudRight-data-v-c095f994{0%{top:100px;left:500px;opacity:0}20%{top:120px;left:460px;opacity:1}80%{top:180px;left:340px;opacity:1}to{top:200px;left:300px;opacity:0}}.wscn-http404 .bullshit[data-v-c095f994]{position:relative;float:left;width:300px;padding:30px 0;overflow:hidden}.wscn-http404 .bullshit__oops[data-v-c095f994]{font-size:32px;line-height:40px;color:#1482f0;margin-bottom:20px;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__headline[data-v-c095f994],.wscn-http404 .bullshit__oops[data-v-c095f994]{font-weight:700;opacity:0;-webkit-animation-name:slideUp-data-v-c095f994;animation-name:slideUp-data-v-c095f994;-webkit-animation-duration:.5s;animation-duration:.5s}.wscn-http404 .bullshit__headline[data-v-c095f994]{font-size:20px;line-height:24px;color:#222;margin-bottom:10px;-webkit-animation-delay:.1s;animation-delay:.1s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-c095f994]{font-size:13px;line-height:21px;color:grey;margin-bottom:30px;-webkit-animation-delay:.2s;animation-delay:.2s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}.wscn-http404 .bullshit__info[data-v-c095f994],.wscn-http404 .bullshit__return-home[data-v-c095f994]{opacity:0;-webkit-animation-name:slideUp-data-v-c095f994;animation-name:slideUp-data-v-c095f994;-webkit-animation-duration:.5s;animation-duration:.5s}.wscn-http404 .bullshit__return-home[data-v-c095f994]{display:block;float:left;width:110px;height:36px;background:#1482f0;border-radius:100px;text-align:center;color:#fff;font-size:14px;line-height:36px;cursor:pointer;-webkit-animation-delay:.3s;animation-delay:.3s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards}@-webkit-keyframes slideUp-data-v-c095f994{0%{-webkit-transform:translateY(60px);transform:translateY(60px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}}@keyframes slideUp-data-v-c095f994{0%{-webkit-transform:translateY(60px);transform:translateY(60px);opacity:0}to{-webkit-transform:translateY(0);transform:translateY(0);opacity:1}} -------------------------------------------------------------------------------- /iCopy/web/vue-admin-simple/dist/index.html: -------------------------------------------------------------------------------- 1 | iCopy WEB DASHBOARD
-------------------------------------------------------------------------------- /iCopy/utils/dedupe_payload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import re, time, pymongo 5 | from utils import load 6 | from utils.load import _lang, _text 7 | import subprocess 8 | from telegram import ParseMode 9 | from telegram.utils.request import Request as TGRequest 10 | from telegram import Bot 11 | from multiprocessing import Manager 12 | 13 | myclient = pymongo.MongoClient( 14 | f"{load.cfg['database']['db_connect_method']}://{load.user}:{load.passwd}@{load.cfg['database']['db_addr']}", 15 | port=load.cfg["database"]["db_port"], 16 | connect=False, 17 | ) 18 | mydb = myclient[load.cfg["database"]["db_name"]] 19 | task_list = mydb["task_list"] 20 | fav_col = mydb["fav_col"] 21 | 22 | _cfg = load.cfg 23 | deduping_process = subprocess.Popen 24 | 25 | request = TGRequest(con_pool_size=8) 26 | bot = Bot(token=f"{_cfg['tg']['token']}", request=request) 27 | 28 | def dedupe_run(command): 29 | global deduping_process 30 | deduping_process = subprocess.Popen( 31 | command, 32 | stdout=subprocess.PIPE, 33 | stderr=subprocess.STDOUT, 34 | shell=False, 35 | encoding="utf-8", 36 | errors="ignore", 37 | universal_newlines=True, 38 | ) 39 | while True: 40 | line = deduping_process.stdout.readline().rstrip() 41 | if not line: 42 | break 43 | yield line 44 | deduping_process.communicate() 45 | 46 | def dedupe_task( 47 | ns, 48 | dedu_mode, 49 | dedu_chat_id, 50 | dedu_message_id, 51 | dedu_task_id, 52 | dedu_link, 53 | dedu_id, 54 | dedu_name, 55 | ): 56 | cloner = _cfg["general"]["cloner"] 57 | option = "dedupe" 58 | mode_suffix = "--dedupe-mode" 59 | mode = dedu_mode 60 | remote = _cfg["general"]["remote"] 61 | src_id = dedu_id 62 | src_block = remote + ":" + "{" + src_id + "}" 63 | checkers = "--checkers=" + f"{_cfg['general']['parallel_c']}" 64 | transfers = "--transfers=" + f"{_cfg['general']['parallel_t']}" 65 | flags = ["-P"] 66 | flags += _cfg["general"]["run_args"] 67 | sa_sleep_suffix = "--drive-pacer-min-sleep" 68 | sa_sleep = _cfg["general"]["min_sleep"] 69 | 70 | command = [ 71 | cloner, 72 | option, 73 | mode_suffix, 74 | mode, 75 | src_block, 76 | checkers, 77 | transfers, 78 | sa_sleep_suffix, 79 | sa_sleep, 80 | ] 81 | command += flags 82 | 83 | dedupe_process(ns, command, dedu_mode, dedu_chat_id, dedu_message_id, dedu_task_id, dedu_link, dedu_id, dedu_name) 84 | 85 | ns.dedupe = 0 86 | 87 | def dedupe_process(ns, command, dedu_mode, dedu_chat_id, dedu_message_id, dedu_task_id, dedu_link, dedu_id, dedu_name): 88 | for output in dedupe_run(command): 89 | if ns.dedupe == 1: 90 | deduping_process.kill() 91 | 92 | if ns.dedupe == 0: 93 | last_dedupe_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 94 | 95 | if dedu_task_id == 0: 96 | fav_col.update_one( 97 | {"G_id": dedu_id}, {"$set": {"last_dedupe_time": last_dedupe_time,}}, 98 | ) 99 | 100 | deduped_msg = ( 101 | " ༺ ✪iCopy✪ ༻ | " 102 | + "🏳️" 103 | + " FAVORITES" 104 | + "\n\n" 105 | + '{}'.format(dedu_link, dedu_name) 106 | + "\n[" 107 | + dedu_id 108 | + "]\n\n" 109 | + _text[_lang]["deduping_done"] 110 | ) 111 | 112 | else: 113 | task_list.update_one( 114 | {"_id": int(dedu_task_id)}, {"$set": {"last_dedupe_time": last_dedupe_time,}}, 115 | ) 116 | 117 | deduped_msg = ( 118 | " ༺ ✪iCopy✪ ༻ | " 119 | + "🏳️" + _text[_lang]["current_task_id"] 120 | + str(dedu_task_id) 121 | + "\n\n" 122 | + '{}'.format(dedu_link, dedu_name) 123 | + "\n[" 124 | + dedu_id 125 | + "]\n\n" 126 | + _text[_lang]["deduping_done"] 127 | ) 128 | 129 | bot.edit_message_text( 130 | chat_id=dedu_chat_id, 131 | message_id=dedu_message_id, 132 | text=deduped_msg, 133 | parse_mode=ParseMode.HTML, 134 | disable_web_page_preview=True, 135 | ) 136 | elif ns.dedupe == 1: 137 | bot.edit_message_text( 138 | chat_id=dedu_chat_id, 139 | message_id=dedu_message_id, 140 | text=_text[_lang]["is_killed_by_user"], 141 | ) 142 | 143 | -------------------------------------------------------------------------------- /iCopy.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "name": "iCopy.ipynb", 7 | "provenance": [], 8 | "collapsed_sections": [], 9 | "authorship_tag": "ABX9TyMIOr05NhpujkN2XObP9vC3", 10 | "include_colab_link": true 11 | }, 12 | "kernelspec": { 13 | "name": "python3", 14 | "display_name": "Python 3" 15 | }, 16 | "accelerator": "GPU" 17 | }, 18 | "cells": [ 19 | { 20 | "cell_type": "markdown", 21 | "metadata": { 22 | "id": "view-in-github", 23 | "colab_type": "text" 24 | }, 25 | "source": [ 26 | "\"Open" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "metadata": { 32 | "id": "NZUNVC5FMrc1", 33 | "colab_type": "code", 34 | "cellView": "form", 35 | "colab": {} 36 | }, 37 | "source": [ 38 | "#@markdown

Run this cell to install all required modules

\n", 39 | "!sudo add-apt-repository ppa:deadsnakes/ppa -y\n", 40 | "!sudo apt update\n", 41 | "!sudo apt install unzip\n", 42 | "!sudo apt install python3.8\n", 43 | "!sudo apt-get install python3.8-venv\n", 44 | "!sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 3\n", 45 | "!curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py\n", 46 | "!python3 get-pip.py --force-reinstall\n", 47 | "!pip3 install -U pip\n", 48 | "!git clone https://github.com/Godcic/iCopy-Heroku.git\n", 49 | "%cd /content/iCopy-Heroku\n", 50 | "!pip3 install -r requirements.txt\n", 51 | "!bash <(wget -qO- https://git.io/JJYE0)\n", 52 | "from IPython.display import HTML, clear_output\n", 53 | "clear_output()\n", 54 | "print(\"Successfully Installed\")\n" 55 | ], 56 | "execution_count": null, 57 | "outputs": [] 58 | }, 59 | { 60 | "cell_type": "code", 61 | "metadata": { 62 | "id": "QsbKzy_7my1L", 63 | "colab_type": "code", 64 | "cellView": "form", 65 | "colab": {} 66 | }, 67 | "source": [ 68 | "#@markdown

Fill all the Vars and Run the cell

\n", 69 | "BOT_TOKEN = \"\" #@param {type:\"string\"}\n", 70 | "USER_ID = \"\" #@param {type:\"string\"}\n", 71 | "DB_CONNECT_METHOD =\"mongodb+srv\"#@param {type:\"string\"}\n", 72 | "DB_ADDRESS = \"\"#@param {type:\"string\"}\n", 73 | "DB_PORT=\"27017\"#@param {type:\"string\"}\n", 74 | "DB_NAME=\"iCopy\"#@param {type:\"string\"}\n", 75 | "DB_USERNAME=\"\"#@param {type:\"string\"}\n", 76 | "DB_PASS=\"\"#@param {type:\"string\"}\n", 77 | "LANGUAGE=\"cn\"#@param {type:\"string\"}\n", 78 | "CLONER=\"fclone\"#@param {type:\"string\"}\n", 79 | "OPTION=\"copy\"#@param {type:\"string\"}\n", 80 | "RCLONE_RMT=\"iCopy\"#@param {type:\"string\"}\n", 81 | "PARALLEL_CHECKERS=\"2500\"#@param {type:\"string\"}\n", 82 | "PARALLEL_TRANSFERS=\"2500\"#@param {type:\"string\"}\n", 83 | "MIN_SLEEP=\"1ms\"#@param {type:\"string\"}\n", 84 | "SA_INIT_FILE=\"\"#@param {type:\"string\"}\n", 85 | "SA_ZIP_URL=\"\"#@param {type:\"string\"}\n", 86 | "RUN_ARGS=\"['--log-level=DEBUG', '--log-file=/content/icopy_cloner_debug.log']\"\n", 87 | "SA_PATH = \"/content/iCopy-Heroku/iCopy/accounts\"\n", 88 | "import os\n", 89 | "os.environ['RCLONE_CONFIG']=\"/content/iCopy-Heroku/iCopy/config/rclone.conf\"\n", 90 | "%cd /content/iCopy-Heroku/iCopy/config/\n", 91 | "!echo -e \"[tg]\\n\"token = \"\\\"$BOT_TOKEN\\\"\\n\"usr_id = \"\\\"$USER_ID\\\"\\n[database]\\n\"db_connect_method = \"\\\"$DB_CONNECT_METHOD\\\"\\n\"db_addr = \"\\\"$DB_ADDRESS\\\"\\n\"db_port = \"$DB_PORT\"\"\\n\"db_name = \"\\\"$DB_NAME\\\"\\n\"db_user = \"\\\"$DB_USERNAME\\\"\\n\"db_passwd = \"\\\"$DB_PASS\\\"\\n[general]\\n\"language = \"\\\"$LANGUAGE\\\" \\n\"cloner = \"\\\"$CLONER\\\"\\n\"option = \"\\\"$OPTION\\\" \\n\"remote = \"\\\"$RCLONE_RMT\\\"\\n\"parallel_c = \"\\\"$PARALLEL_CHECKERS\\\"\\n\"parallel_t = \"\\\"$PARALLEL_TRANSFERS\\\"\\n\"min_sleep = \"\\\"$MIN_SLEEP\\\"\\n\"sa_path = \"\\\"$SA_PATH\\\"\\nrun_args = $RUN_ARGS\\n[web]\\ndashboard = 0\\nport = 8000\" >> conf.toml\n", 92 | "!echo \"[$RCLONE_RMT]\\ntype = drive\\nscope = drive\\n\"service_account_file = /app/iCopy/accounts/$SA_INIT_FILE\"\\n\"service_account_file_path = $SA_PATH\"\" >> rclone.conf\n", 93 | "%cd ..\n", 94 | "!wget --no-check-certificate -q $SA_ZIP_URL -O accounts.zip\n", 95 | "!unzip -qq accounts.zip -d /content/iCopy-Heroku/iCopy/" 96 | ], 97 | "execution_count": null, 98 | "outputs": [] 99 | }, 100 | { 101 | "cell_type": "code", 102 | "metadata": { 103 | "id": "tJvoyAzBguoO", 104 | "colab_type": "code", 105 | "cellView": "form", 106 | "colab": {} 107 | }, 108 | "source": [ 109 | "#@markdown

Run this cell to start the Bot

\n", 110 | "%cd /content/iCopy-Heroku/iCopy/\n", 111 | "!chmod 777 iCopy.py\n", 112 | "!python3 iCopy.py" 113 | ], 114 | "execution_count": null, 115 | "outputs": [] 116 | } 117 | ] 118 | } -------------------------------------------------------------------------------- /iCopy/workflow/regex_workflow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from utils.load import _lang, _text, ns 5 | from utils import ( 6 | load, 7 | get_functions as _func, 8 | restricted as _r, 9 | keyboard as _KB, 10 | task_box as _box, 11 | size_payload as _s_payload, 12 | callback_stage as _stage, 13 | ) 14 | from telegram.ext import CallbackQueryHandler, ConversationHandler 15 | from threading import Thread 16 | from multiprocessing import Process as _mp 17 | 18 | ns.size = 0 19 | ns.dedupe = 0 20 | ns.purge = 0 21 | 22 | src_name_list = [] 23 | src_id_list = [] 24 | regex_in_update = None 25 | regex_in_context = None 26 | 27 | 28 | @_r.restricted 29 | def regex_entry(update, context): 30 | global src_id_list 31 | global src_name_list 32 | src_id_list = [] 33 | src_name_list = [] 34 | tmp_src_name_list = "" 35 | get_share_link = update.effective_message.text 36 | src_id_list = _func.cook_to_id(get_share_link) 37 | for item in src_id_list: 38 | src_name_list += _func.get_src_name_from_id( 39 | update, taget_id=item, list_name=tmp_src_name_list 40 | ) 41 | tmp_src_name_list = "" 42 | 43 | update.effective_message.reply_text( 44 | _text[_lang]["menu_msg"], reply_markup=_KB.regex_in_keyboard(), 45 | ) 46 | 47 | global regex_in_update 48 | global regex_in_context 49 | regex_in_update = update 50 | regex_in_context = context 51 | 52 | return _stage.REGEX_IN 53 | 54 | 55 | def regex_callback(update, context): 56 | if "quick" == update.callback_query.data: 57 | load.bot.edit_message_text( 58 | chat_id=update.callback_query.message.chat_id, 59 | message_id=update.callback_query.message.message_id, 60 | text=_text[_lang]["mode_select_msg"].replace( 61 | "replace", _text[_lang]["quick_mode"] 62 | ), 63 | reply_markup=None, 64 | ) 65 | 66 | regex_in_chat_id = regex_in_update.effective_message.chat_id 67 | regex_in_message_id = regex_in_update.effective_message.message_id 68 | tmp_task_list = [] 69 | mode = "quick" 70 | is_quick = {"_id": "fav_quick"} 71 | is_quick_cur = load.fav_col.find(is_quick) 72 | 73 | if is_quick_cur is not None: 74 | for doc in is_quick_cur: 75 | dst_id = doc["G_id"] 76 | dst_name = doc["G_name"] 77 | 78 | for item in src_name_list: 79 | src_id = item["G_id"] 80 | src_name = item["G_name"] 81 | 82 | tmp_task_list.append( 83 | { 84 | "mode_type": mode, 85 | "src_id": src_id, 86 | "src_name": src_name, 87 | "dst_id": dst_id, 88 | "dst_name": dst_name, 89 | "chat_id": regex_in_chat_id, 90 | "raw_message_id": regex_in_message_id, 91 | } 92 | ) 93 | 94 | Thread( 95 | target=_box.cook_task_to_db, 96 | args=(regex_in_update, regex_in_context, tmp_task_list), 97 | ).start() 98 | tmp_task_list = [] 99 | return ConversationHandler.END 100 | 101 | if "copy" == update.callback_query.data: 102 | update.callback_query.edit_message_text( 103 | _text[_lang]["mode_select_msg"].replace( 104 | "replace", _text[_lang]["copy_mode"] 105 | ) 106 | + "\n" 107 | + _text[_lang]["request_dst_target"], 108 | reply_markup=_KB.dst_keyboard(update, context), 109 | ) 110 | 111 | return _stage.REGEX_GET_DST 112 | 113 | if "size" == update.callback_query.data: 114 | 115 | for item in src_id_list: 116 | size_msg = load.bot.edit_message_text( 117 | chat_id=update.callback_query.message.chat_id, 118 | message_id=update.callback_query.message.message_id, 119 | text=_text[_lang]["ready_to_size"], 120 | reply_markup=None, 121 | ) 122 | size_chat_id = size_msg.chat_id 123 | size_message_id = size_msg.message_id 124 | 125 | # to payload 126 | progress = _mp( 127 | target=_s_payload.simple_size, 128 | args=( 129 | ns, 130 | update, 131 | context, 132 | item, 133 | size_chat_id, 134 | size_message_id, 135 | src_name_list, 136 | ), 137 | ) 138 | progress.start() 139 | 140 | context.bot.edit_message_text( 141 | chat_id=size_chat_id, 142 | message_id=size_message_id, 143 | text=_text[_lang]["sizing"], 144 | ) 145 | 146 | return ConversationHandler.END 147 | 148 | 149 | def regex_copy_end(update, context): 150 | 151 | load.bot.edit_message_text( 152 | chat_id=update.callback_query.message.chat_id, 153 | message_id=update.callback_query.message.message_id, 154 | text=_text[_lang]["mode_select_msg"].replace( 155 | "replace", _text[_lang]["copy_mode"] 156 | ), 157 | reply_markup=None, 158 | ) 159 | 160 | mode = "copy" 161 | regex_in_chat_id = regex_in_update.effective_message.chat_id 162 | regex_in_message_id = regex_in_update.effective_message.message_id 163 | tmp_task_list = [] 164 | 165 | is_dstinfo = update.callback_query.data 166 | dstinfo = is_dstinfo.split("id+name") 167 | dst_id = dstinfo[0] 168 | dst_name = dstinfo[1] 169 | 170 | for item in src_name_list: 171 | src_id = item["G_id"] 172 | src_name = item["G_name"] 173 | 174 | tmp_task_list.append( 175 | { 176 | "mode_type": mode, 177 | "src_id": src_id, 178 | "src_name": src_name, 179 | "dst_id": dst_id, 180 | "dst_name": dst_name, 181 | "chat_id": regex_in_chat_id, 182 | "raw_message_id": regex_in_message_id, 183 | } 184 | ) 185 | 186 | Thread( 187 | target=_box.cook_task_to_db, 188 | args=(regex_in_update, regex_in_context, tmp_task_list), 189 | ).start() 190 | dstinfo = "" 191 | return ConversationHandler.END 192 | 193 | -------------------------------------------------------------------------------- /iCopy/iCopy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os, sys, logging 5 | from telegram import Bot 6 | from telegram.utils.request import Request as TGRequest 7 | from utils import load 8 | from telegram.ext import ( 9 | Updater, 10 | CommandHandler, 11 | MessageHandler, 12 | Filters, 13 | CallbackQueryHandler, 14 | ConversationHandler, 15 | ) 16 | from utils import ( 17 | get_set as _set, 18 | get_functions as _func, 19 | task_box as _box, 20 | task_payload as _payload, 21 | callback_stage as _stage, 22 | __version__, 23 | ) 24 | 25 | from workflow import ( 26 | start_workflow as _start, 27 | quick_workflow as _quick, 28 | copy_workflow as _copy, 29 | size_workflow as _size, 30 | regex_workflow as _regex, 31 | purge_workflow as _purge, 32 | dedupe_workflow as _dedupe, 33 | ) 34 | from multiprocessing import Process as _mp, Manager 35 | from threading import Thread 36 | from utils.load import ns 37 | from web import dash 38 | 39 | logging.basicConfig( 40 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO 41 | ) 42 | logger = logging.getLogger(__name__) 43 | 44 | # ############################### Main #################################### 45 | 46 | def main(): 47 | ### bot define 48 | request = TGRequest(con_pool_size=8) 49 | bot = Bot(token=f"{load.cfg['tg']['token']}", request=request) 50 | updater = Updater(bot=bot, use_context=True) 51 | 52 | ### judge is restart 53 | is_restart = load.db_counters.find_one({"_id": "is_restart"}) 54 | if is_restart is not None: 55 | if is_restart["status"] == 0: 56 | pass 57 | else: 58 | _func.check_restart(bot) 59 | 60 | else: 61 | load.db_counters.update( 62 | {"_id": "is_restart"}, {"status": 0}, upsert=True, 63 | ) 64 | 65 | dp = updater.dispatcher 66 | 67 | # Entry Conversation 68 | conv_handler = ConversationHandler( 69 | entry_points=[ 70 | # Entry Points 71 | CommandHandler("set", _set._setting), 72 | CommandHandler("menu", _start.menu), 73 | CommandHandler("quick", _quick.quick), 74 | CommandHandler("copy", _copy.copy), 75 | CommandHandler("task", _box.taskinfo), 76 | CommandHandler("size", _size.size), 77 | CommandHandler("purge", _purge.purge), 78 | CommandHandler("dedupe", _dedupe.dedupe), 79 | MessageHandler( 80 | Filters.regex(pattern=load.regex_entry_pattern), _regex.regex_entry 81 | ), 82 | ], 83 | 84 | states={ 85 | _stage.SET_FAV_MULTI: [ 86 | # fav settings function 87 | MessageHandler(Filters.text, _set._multi_settings_recieved), 88 | ], 89 | _stage.CHOOSE_MODE: [ 90 | # call function judged via callback pattern 91 | CallbackQueryHandler(_quick.quick, pattern="quick"), 92 | CallbackQueryHandler(_copy.copy, pattern="copy"), 93 | ], 94 | _stage.GET_LINK: [ 95 | # get Shared_Link states 96 | MessageHandler(Filters.text, _func.get_share_link), 97 | ], 98 | _stage.IS_COVER_QUICK: [ 99 | # cover quick setting 100 | CallbackQueryHandler(_func.modify_quick_in_db, pattern="cover_quick"), 101 | CallbackQueryHandler(_func.cancel, pattern="not_cover_quick"), 102 | MessageHandler(Filters.text, _func.cancel), 103 | ], 104 | _stage.GET_DST: [ 105 | # request DST 106 | CallbackQueryHandler(_copy.request_srcinfo), 107 | ], 108 | _stage.COOK_ID: [ 109 | # request to COOK ID 110 | MessageHandler(Filters.text, _size.size_handle), 111 | ], 112 | _stage.REGEX_IN: [ 113 | # regex in choose mode 114 | CallbackQueryHandler(_regex.regex_callback, pattern=r"quick|copy|size"), 115 | ], 116 | _stage.REGEX_GET_DST: [ 117 | # regex copy end 118 | CallbackQueryHandler(_regex.regex_copy_end), 119 | ], 120 | _stage.COOK_FAV_TO_SIZE: [CallbackQueryHandler(_size.pre_cook_fav_to_size),], 121 | _stage.COOK_FAV_PURGE: [CallbackQueryHandler(_purge.pre_to_purge),], 122 | _stage.COOK_ID_DEDU: [CallbackQueryHandler(_dedupe.dedupe_mode),], 123 | _stage.COOK_FAV_DEDU: [CallbackQueryHandler(_dedupe.dedupe_fav_mode),], 124 | _stage.FAV_PRE_DEDU_INFO: [CallbackQueryHandler(_dedupe.pre_favdedu_info)], 125 | _stage.SET_WEB: [MessageHandler(Filters.text, _set.setWeb),], 126 | }, 127 | fallbacks=[CommandHandler("cancel", _func.cancel)], 128 | ) 129 | 130 | def stop_and_restart(): 131 | progress.terminate() 132 | load.myclient.close() 133 | updater.stop() 134 | os.execl(sys.executable, sys.executable, *sys.argv) 135 | 136 | def restart(update, context): 137 | restart_msg = update.message.reply_text(load._text[load._lang]["is_restarting"]) 138 | restart_chat_id = restart_msg.chat_id 139 | restart_msg_id = restart_msg.message_id 140 | load.db_counters.update_one( 141 | {"_id": "is_restart"}, 142 | { 143 | "$set": { 144 | "status": 1, 145 | "chat_id": restart_chat_id, 146 | "message_id": restart_msg_id, 147 | } 148 | }, 149 | True, 150 | ) 151 | Thread(target=stop_and_restart).start() 152 | 153 | dp.add_handler(conv_handler) 154 | dp.add_handler(CommandHandler("start", _start.start)) 155 | dp.add_handler(CommandHandler("reset", _box.task_reset)) 156 | dp.add_handler(CommandHandler("kill", _func.taskill)) 157 | dp.add_handler(CommandHandler("ver", _func._version)) 158 | 159 | dp.add_handler( 160 | CommandHandler( 161 | "restart", 162 | restart, 163 | filters=Filters.user(user_id=int(load.cfg["tg"]["usr_id"])), 164 | ) 165 | ) 166 | 167 | dp.add_error_handler(_func.error) 168 | 169 | updater.start_polling() 170 | logger.info("Fxxkr LAB iCopy " + __version__.__version__ + " Start") 171 | updater.idle() 172 | 173 | 174 | if __name__ == "__main__": 175 | ns.x = 0 176 | progress = _mp(target=_payload.task_buffer, args=(ns,)) 177 | progress.start() 178 | if load.cfg['web']['dashboard']: 179 | web = _mp(target=dash.dashboard) 180 | web.start() 181 | 182 | main() 183 | -------------------------------------------------------------------------------- /iCopy/workflow/dedupe_workflow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import re 5 | from utils.load import _lang, _text, ns 6 | from utils import ( 7 | load, 8 | restricted as _r, 9 | keyboard as _KB, 10 | dedupe_payload as _d_payload, 11 | callback_stage as _stage, 12 | ) 13 | from multiprocessing import Process as _mp 14 | from telegram.ext import ConversationHandler 15 | from drive.gdrive import GoogleDrive as _gd 16 | 17 | bot = load.bot 18 | check_task = {} 19 | dedufav_callback = "" 20 | ns.dedupe = 0 21 | 22 | @_r.restricted 23 | def dedupe(update, context): 24 | entry_cmd = update.effective_message.text 25 | match_cmd = re.search(r"^\/dedupe ([1-9]\d*)$", entry_cmd, flags=re.I) 26 | 27 | if entry_cmd == "/dedupe": 28 | update.effective_message.reply_text( 29 | _text[_lang]["mode_select_msg"].replace( 30 | "replace", _text[_lang]["dedupe_mode"] 31 | ) 32 | + "\n" 33 | + _text[_lang]["request_target_folder"], 34 | reply_markup=_KB.dst_keyboard(update, context), 35 | ) 36 | 37 | return _stage.COOK_FAV_DEDU 38 | 39 | elif match_cmd: 40 | limit_query = load.db_counters.find_one({"_id": "task_list_id"}) 41 | check_query = match_cmd.group(1) 42 | 43 | if int(check_query) <= limit_query["future_id"]: 44 | 45 | global check_task 46 | check_task = load.task_list.find_one({"_id": int(check_query)}) 47 | 48 | if check_task["status"] == 1: 49 | update.effective_message.reply_text( 50 | _text[_lang]["mode_select_msg"].replace( 51 | "replace", _text[_lang]["dedupe_mode"] 52 | ) 53 | + "\n" 54 | + _text[_lang]["request_dedupe_mode"], 55 | reply_markup=_KB.dedupe_mode_keyboard(), 56 | ) 57 | 58 | return _stage.COOK_ID_DEDU 59 | 60 | elif check_task["status"] == 0: 61 | update.effective_message.reply_text( 62 | _text[_lang]["task_is_in_queue"] 63 | + "\n" 64 | + _text[_lang]["finished_could_be_dedupe"], 65 | ) 66 | 67 | return ConversationHandler.END 68 | 69 | elif check_task["status"] == 2: 70 | update.effective_message.reply_text( 71 | _text[_lang]["doing"] 72 | + "\n" 73 | + _text[_lang]["finished_could_be_dedupe"], 74 | ) 75 | 76 | return ConversationHandler.END 77 | 78 | else: 79 | update.effective_message.reply_text(_text[_lang]["over_limit_to_dedupe"],) 80 | 81 | return ConversationHandler.END 82 | 83 | else: 84 | update.effective_message.reply_text(_text[_lang]["global_command_error"]) 85 | 86 | 87 | def dedupe_mode(update, context): 88 | 89 | get_callback = update.callback_query.data 90 | dedu_msg = bot.edit_message_text( 91 | chat_id=update.callback_query.message.chat_id, 92 | message_id=update.callback_query.message.message_id, 93 | text=_text[_lang]["ready_to_dedupe"], 94 | reply_markup=None, 95 | ) 96 | 97 | dedu_chat_id = dedu_msg.chat_id 98 | dedu_message_id = dedu_msg.message_id 99 | dedu_mode = get_callback 100 | 101 | if "dst_endpoint_id" and "dst_endpoint_link" in check_task: 102 | dedu_task_id = check_task["_id"] 103 | dedu_name = check_task["src_name"] 104 | dedu_id = check_task["dst_endpoint_id"] 105 | dedu_link = check_task["dst_endpoint_link"] 106 | 107 | else: 108 | dst_endpoint_id = _gd.get_dst_endpoint_id( 109 | _gd(), check_task["dst_id"], check_task["src_name"] 110 | ) 111 | if dst_endpoint_id: 112 | dst_endpoint_link = r"https://drive.google.com/open?id={}".format( 113 | dst_endpoint_id["id"] 114 | ) 115 | 116 | load.task_list.update_one( 117 | {"_id": int(check_task["_id"])}, 118 | { 119 | "$set": { 120 | "dst_endpoint_id": dst_endpoint_id["id"], 121 | "dst_endpoint_link": dst_endpoint_link, 122 | }, 123 | }, 124 | ) 125 | 126 | dedu_task_id = check_task["_id"] 127 | dedu_name = check_task["src_name"] 128 | dedu_id = dst_endpoint_id 129 | dedu_link = dst_endpoint_link 130 | 131 | progress = _mp( 132 | target=_d_payload.dedupe_task, 133 | args=( 134 | ns, 135 | dedu_mode, 136 | dedu_chat_id, 137 | dedu_message_id, 138 | dedu_task_id, 139 | dedu_link, 140 | dedu_id, 141 | dedu_name, 142 | ), 143 | ) 144 | progress.start() 145 | 146 | bot.edit_message_text( 147 | chat_id=dedu_chat_id, message_id=dedu_message_id, text=_text[_lang]["deduping"], 148 | ) 149 | 150 | return ConversationHandler.END 151 | 152 | 153 | def dedupe_fav_mode(update, context): 154 | global dedufav_callback 155 | dedufav_callback = update.callback_query.data 156 | update.callback_query.edit_message_text( 157 | _text[_lang]["mode_select_msg"].replace("replace", _text[_lang]["dedupe_mode"]) 158 | + "\n" 159 | + _text[_lang]["request_dedupe_mode"], 160 | reply_markup=_KB.dedupe_mode_keyboard(), 161 | ) 162 | 163 | return _stage.FAV_PRE_DEDU_INFO 164 | 165 | 166 | def pre_favdedu_info(update, context): 167 | dedu_mode = update.callback_query.data 168 | 169 | dedu_msg = update.callback_query.edit_message_text( 170 | text=_text[_lang]["ready_to_dedupe"], 171 | reply_markup=None, 172 | ) 173 | 174 | dedu_chat_id = dedu_msg.chat_id 175 | dedu_message_id = dedu_msg.message_id 176 | 177 | dedu_task_id = 0 178 | 179 | dedufav_info = dedufav_callback.split("id+name") 180 | dedu_id = dedufav_info[0] 181 | dedu_name = dedufav_info[1] 182 | dedu_link = r"https://drive.google.com/open?id={}".format( 183 | dedufav_info[0] 184 | ) 185 | 186 | progress = _mp( 187 | target=_d_payload.dedupe_task, 188 | args=( 189 | ns, 190 | dedu_mode, 191 | dedu_chat_id, 192 | dedu_message_id, 193 | dedu_task_id, 194 | dedu_link, 195 | dedu_id, 196 | dedu_name, 197 | ), 198 | ) 199 | progress.start() 200 | 201 | bot.edit_message_text( 202 | chat_id=dedu_chat_id, message_id=dedu_message_id, text=_text[_lang]["deduping"], 203 | ) 204 | 205 | return ConversationHandler.END 206 | -------------------------------------------------------------------------------- /iCopy/utils/task_box.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import time, pymongo, re 5 | from utils import load,task_payload as _payload 6 | from telegram.ext import ConversationHandler 7 | from utils.load import _lang, _text 8 | from telegram import ParseMode 9 | 10 | future = load.db_counters.find_one({"_id": "task_list_id"}) 11 | future_id = 0 12 | waititem = "" 13 | waitlist = [] 14 | 15 | if future != None: 16 | future_id = future['future_id'] 17 | 18 | def cook_task_to_db(update, context, tmp_task_list): 19 | 20 | for item in tmp_task_list: 21 | global future_id 22 | future_id += 1 23 | item["_id"] = future_id 24 | item["status"] = 0 25 | item["error"] = 0 26 | item["create_time"] = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 27 | item["finished_time"] = "" 28 | item["start_time"] = "" 29 | item["task_current_prog_num"] = 0 30 | item["task_total_prog_num"] = 0 31 | item["task_current_prog_size"] = 0 32 | item["task_total_prog_size"] = 0 33 | item["task_current_prog_size_tail"] = "" 34 | item["task_total_prog_size_tail"] = "" 35 | item["dst_endpoint_link"] = "" 36 | item["dst_endpoint_id"] = "" 37 | item["is_reset"] = 0 38 | 39 | insert_callback = load.task_list.insert_many(tmp_task_list) 40 | if insert_callback.inserted_ids: 41 | load.db_counters.update({"_id": "task_list_id"},{"future_id":future_id},upsert=True) 42 | update.effective_message.reply_text( 43 | _text[_lang]["add_task_successful"] 44 | ) 45 | 46 | 47 | def taskinfo(update, context): 48 | entry_cmd = update.effective_message.text 49 | match_cmd = re.search(r'^\/task ([1-9]\d*)$', entry_cmd, flags=re.I) 50 | if " " in entry_cmd: 51 | entry_cmd = entry_cmd.replace(" ","") 52 | 53 | if entry_cmd == "/task": 54 | current_task = load.task_list.find_one({"status":2}) 55 | if current_task is not None: 56 | current_task_id = current_task['_id'] 57 | current_task_src_name = current_task['src_name'] 58 | current_task_dst_name = current_task['dst_name'] 59 | update.effective_message.reply_text( 60 | _text[_lang]["is_current_task"] 61 | + _text[_lang]["current_task_id"] 62 | + str(current_task_id) 63 | + "\n" 64 | + _text[_lang]["current_task_src_name"] 65 | + current_task_src_name 66 | + "\n" 67 | + _text[_lang]["current_task_dst_name"] 68 | + current_task_dst_name 69 | ) 70 | 71 | return ConversationHandler.END 72 | 73 | else: 74 | update.effective_message.reply_text( 75 | _text[_lang]['is_not_current_task'] 76 | ) 77 | 78 | return ConversationHandler.END 79 | 80 | elif entry_cmd[5:] == "list": 81 | global waititem 82 | global waitlist 83 | task_list = load.task_list.find({"status":0}).limit(10) 84 | 85 | if task_list != []: 86 | for doc in task_list: 87 | waititem = _text[_lang]["current_task_id"] + str(doc['_id']) + _text[_lang]["current_task_src_name"] + doc['src_name'] + "\n--------------------\n" 88 | waitlist.append(waititem) 89 | 90 | task_wait_num = len(waitlist) 91 | waitlist = "".join(waitlist) 92 | 93 | update.effective_message.reply_text(str(task_wait_num) +_text[_lang]["show_wait_list"] + waitlist) 94 | 95 | waitlist = [] 96 | 97 | return ConversationHandler.END 98 | 99 | else: 100 | update.effective_message.reply_text( 101 | _text[_lang]["show_wait_list_null"] 102 | ) 103 | 104 | return ConversationHandler.END 105 | 106 | elif match_cmd: 107 | limit_query = load.db_counters.find_one({"_id":"task_list_id"}) 108 | check_query = match_cmd.group(1) 109 | 110 | if int(check_query) <= limit_query['future_id']: 111 | 112 | check_task = load.task_list.find_one({"_id": int(check_query)}) 113 | 114 | if check_task["status"] == 1: 115 | if "task_current_prog_num" and "task_current_prog_size_tail" in check_task: 116 | if check_task["error"] == 0: 117 | task_status_now = _text[_lang]["done"] 118 | elif check_task["error"] == 1: 119 | task_status_now == _text[_lang]["is_interrupted_error"] 120 | elif check_task["error"] == 9: 121 | task_status_now == _text[_lang]["is_killed_by_user"] 122 | 123 | check_msg = ( 124 | _text[_lang]["current_task_id"] 125 | + str(check_query) 126 | + "\n" 127 | + '{}'.format(check_task["dst_endpoint_link"], check_task["src_name"]) 128 | + "\n" 129 | + _text[_lang]["task_status"] 130 | + task_status_now 131 | + "\n" 132 | + _text[_lang]["total_file_num"] 133 | + str(check_task["task_current_prog_num"]) 134 | + "/" 135 | + str(check_task["task_total_prog_num"]) 136 | + "\n" 137 | + _text[_lang]["total_file_size"] 138 | + str(check_task["task_current_prog_size"]) 139 | + check_task["task_current_prog_size_tail"] 140 | + "/" 141 | + str(check_task["task_total_prog_size"]) 142 | + check_task["task_total_prog_size_tail"] 143 | ) 144 | 145 | else: 146 | check_msg = _text[_lang]["support_error"] 147 | 148 | elif check_task["status"] == 0: 149 | check_msg = _text[_lang]["task_is_in_queue"] + "\n" + _text[_lang]["finished_could_be_check"] 150 | 151 | elif check_task["status"] == 2: 152 | check_msg = _text[_lang]["doing"] + "\n" + _text[_lang]["finished_could_be_check"] 153 | 154 | update.effective_message.reply_text(check_msg,parse_mode=ParseMode.HTML,disable_web_page_preview=True) 155 | 156 | else: 157 | update.effective_message.reply_text(_text[_lang]["over_limit_to_check"]) 158 | 159 | 160 | else: 161 | return ConversationHandler.END 162 | 163 | def task_reset(update, context): 164 | entry_cmd = update.effective_message.text 165 | match_cmd = re.search(r'^\/RESET ([1-9]\d*)$', entry_cmd, flags=re.I) 166 | 167 | if "/reset" == entry_cmd: 168 | check_query = load.db_counters.find_one({"_id": "last_task"}) 169 | load.task_list.update_one({"_id": check_query['task_id']}, {"$set": {"status": 0,"is_reset": 1}}) 170 | update.effective_message.reply_text( 171 | _text[_lang]["reset_successful"].replace("replace",str(check_query['task_id'])) 172 | ) 173 | 174 | elif match_cmd: 175 | limit_query = load.db_counters.find_one({"_id":"task_list_id"}) 176 | check_query = match_cmd.group(1) 177 | 178 | if int(check_query) <= limit_query['future_id']: 179 | 180 | load.task_list.update_one({"_id": int(check_query)}, {"$set": {"status": 0,"is_reset": 1}}) 181 | update.effective_message.reply_text( 182 | _text[_lang]["reset_successful"].replace("replace",check_query) 183 | ) 184 | 185 | else: 186 | update.effective_message.reply_text( 187 | _text[_lang]["over_limit_error"] 188 | ) 189 | 190 | else: 191 | update.effective_message.reply_text( 192 | _text[_lang]["global_command_error"] 193 | ) -------------------------------------------------------------------------------- /iCopy/utils/get_functions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import logging, re, json, requests 5 | from utils import ( 6 | load, 7 | messages as _msg, 8 | restricted as _r, 9 | get_set as _set, 10 | task_box as _box, 11 | task_payload as _payload, 12 | callback_stage as _stage, 13 | __version__, 14 | ) 15 | from workflow import copy_workflow as _copy 16 | from utils.load import _lang, _text 17 | from telegram.ext import ConversationHandler 18 | from drive.gdrive import GoogleDrive as _gd 19 | from telegram import ParseMode 20 | from threading import Thread 21 | from utils.load import ns 22 | 23 | 24 | logging.basicConfig( 25 | format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO 26 | ) 27 | logger = logging.getLogger(__name__) 28 | 29 | regex1 = r"[-\w]{11,}" 30 | regex2 = r"[-\w]" 31 | judge_folder_len = [28, 33] 32 | pick_quick = [] 33 | mode = "" 34 | count = 0 35 | 36 | def cook_to_id(get_share_link): 37 | share_id_list = [] 38 | unsupported_type = [] 39 | share_id = "" 40 | 41 | share_link = get_share_link.strip().replace(" ", "").splitlines() 42 | for item in share_link: 43 | if "drive.google.com" in item: 44 | share_id = re.findall(regex1, item) 45 | if len(share_id) <= 33: 46 | share_id = "".join(share_id) 47 | 48 | share_id_list.append(share_id) 49 | else: 50 | unsupported_type.append({"type": "link", "value": item}) 51 | 52 | else: 53 | if len(item) >= 11 and len(item) <= 33 and re.match(regex2, item): 54 | share_id_list.append(item) 55 | else: 56 | unsupported_type.append({"type": "id", "value": item}) 57 | 58 | return share_id_list 59 | 60 | 61 | def get_name_from_id(update, taget_id, list_name): 62 | cook_list = list(list_name) 63 | if len(taget_id) >= 11 and len(taget_id) < 28: 64 | cook_list.append( 65 | {"G_type": "G_drive", "G_id": taget_id, "G_name": load.all_drive[taget_id],} 66 | ) 67 | elif len(taget_id) in judge_folder_len: 68 | cook_list.append( 69 | { 70 | "G_type": "G_Folder", 71 | "G_id": taget_id, 72 | "G_name": _gd().file_get_name(file_id=taget_id), 73 | } 74 | ) 75 | else: 76 | update.effective_message.reply_text(_msg.get_fav_len_invaild(_lang, taget_id)) 77 | 78 | return ConversationHandler.END 79 | 80 | return cook_list 81 | 82 | def get_src_name_from_id(update, taget_id, list_name): 83 | cook_list = [] 84 | cook_list = list(list_name) 85 | if len(taget_id) >= 11 and len(taget_id) < 28: 86 | target_info = _gd.drive_get(_gd(),drive_id=taget_id) 87 | cook_list.append( 88 | {"G_type": "G_drive", "G_id": taget_id, "G_name": target_info['name'],} 89 | ) 90 | elif len(taget_id) in judge_folder_len: 91 | cook_list.append( 92 | { 93 | "G_type": "G_Folder", 94 | "G_id": taget_id, 95 | "G_name": _gd().file_get_name(file_id=taget_id), 96 | } 97 | ) 98 | else: 99 | update.effective_message.reply_text(_msg.get_fav_len_invaild(_lang, taget_id)) 100 | 101 | return ConversationHandler.END 102 | 103 | return cook_list 104 | 105 | 106 | def insert_to_db_quick(pick_quick, update): 107 | is_quick = {"_id": "fav_quick"} 108 | is_quick_cur = load.fav_col.find(is_quick) 109 | if list(is_quick_cur) == []: 110 | for item in pick_quick: 111 | item["_id"] = "fav_quick" 112 | load.fav_col.insert_one(item) 113 | 114 | update.effective_message.reply_text( 115 | _text[_lang]["insert_quick_success"], parse_mode=ParseMode.MARKDOWN_V2 116 | ) 117 | 118 | return ConversationHandler.END 119 | 120 | else: 121 | status = "is_cover" 122 | 123 | return status 124 | 125 | 126 | def modify_quick_in_db(update, context): 127 | pick_quick = _set.pick_quick 128 | for item in pick_quick: 129 | load.fav_col.update({"_id": "fav_quick"}, item, upsert=True) 130 | 131 | update.effective_message.reply_text( 132 | _text[_lang]["modify_quick_success"], parse_mode=ParseMode.MARKDOWN_V2 133 | ) 134 | 135 | return ConversationHandler.END 136 | 137 | 138 | def delete_in_db_quick(): 139 | load.fav_col.delete_one({"_id": "fav_quick"}) 140 | 141 | return 142 | 143 | 144 | def delete_in_db(delete_request): 145 | load.fav_col.delete_one(delete_request) 146 | 147 | return 148 | 149 | 150 | def get_share_link(update, context): 151 | get_share_link = update.effective_message.text 152 | tmp_src_name_list = "" 153 | tmp_task_list = [] 154 | src_name_list = [] 155 | src_id_list = cook_to_id(get_share_link) 156 | is_quick = {"_id": "fav_quick"} 157 | is_quick_cur = load.fav_col.find(is_quick) 158 | is_dstinfo = _copy.current_dst_info 159 | 160 | if is_dstinfo != "": 161 | dstinfo = is_dstinfo.split("id+name") 162 | dst_id = dstinfo[0] 163 | dst_name = dstinfo[1] 164 | else: 165 | for doc in is_quick_cur: 166 | dst_id = doc["G_id"] 167 | dst_name = doc["G_name"] 168 | 169 | for item in src_id_list: 170 | src_name_list += get_src_name_from_id(update, item, list_name=tmp_src_name_list) 171 | tmp_src_name_list = "" 172 | 173 | for item in src_name_list: 174 | src_id = item["G_id"] 175 | src_name = item["G_name"] 176 | 177 | tmp_task_list.append( 178 | { 179 | "mode_type": mode, 180 | "src_id": src_id, 181 | "src_name": src_name, 182 | "dst_id": dst_id, 183 | "dst_name": dst_name, 184 | "chat_id": update.message.chat_id, 185 | "raw_message_id": update.message.message_id, 186 | } 187 | ) 188 | 189 | Thread(target=_box.cook_task_to_db, args=(update, context, tmp_task_list)).start() 190 | _copy.current_dst_info = "" 191 | return ConversationHandler.END 192 | 193 | def taskill(update, context): 194 | entry_cmd = update.effective_message.text 195 | if "/kill" == entry_cmd : 196 | ns.x = 1 197 | 198 | elif context.args[0] == "task": 199 | ns.x = 1 200 | 201 | elif context.args[0] == "size": 202 | ns.size = 1 203 | 204 | elif context.args[0] == "purge": 205 | ns.purge = 1 206 | 207 | elif context.arg[0] == "dedupe": 208 | ns.dedupe = 1 209 | 210 | else: 211 | update.effective_message.reply_text(_text[_lang]["global_command_error"]) 212 | 213 | def getIDbypath(dst_id, src_name): 214 | global count 215 | if count < 10: 216 | try: 217 | dst_endpoint_id = _gd.get_dst_endpoint_id(_gd(), dst_id, src_name) 218 | if dst_endpoint_id: 219 | dst_endpoint_link = r"https://drive.google.com/open?id={}".format( 220 | dst_endpoint_id['id'] 221 | ) 222 | dst_info = {"dst_endpoint_id":dst_endpoint_id['id'],"dst_endpoint_link":dst_endpoint_link,"linkstatus":True} 223 | count = 0 224 | return dst_info 225 | 226 | except: 227 | count += 1 228 | return getIDbypath(dst_id, src_name) 229 | 230 | else: 231 | dst_info = {"dst_endpoint_id":" ","dst_endpoint_link":" ","linkstatus":False} 232 | count = 0 233 | return dst_info 234 | 235 | def check_restart(bot): 236 | check_restart = load.db_counters.find_one({"_id": "is_restart"}) 237 | chat_id = check_restart["chat_id"] 238 | message_id = check_restart["message_id"] 239 | load.db_counters.update_one({"_id": "is_restart"}, {"$set": {"status": 0,}}, True) 240 | bot.edit_message_text( 241 | chat_id=chat_id, message_id=message_id, text=_text[_lang]["restart_success"] 242 | ) 243 | 244 | def _version(update, context): 245 | update.message.reply_text( 246 | "Welcome to use iCopy Telegram BOT\n\n" 247 | "Current Version : " + __version__.__version__ + "\n\n" 248 | f"Latest Version : {_get_ver()}" 249 | ) 250 | 251 | def _get_ver(): 252 | _url = "https://api.github.com/repos/fxxkrlab/iCopy/releases" 253 | _r_ver = requests.get(_url).json() 254 | _latest_ver = _r_ver[0]["tag_name"] 255 | return _latest_ver 256 | 257 | @_r.restricted 258 | def cancel(update, context): 259 | user = update.effective_user.first_name 260 | logger.info("User %s canceled the conversation.", user) 261 | update.effective_message.reply_text( 262 | f"Bye! {update.effective_user.first_name} ," + _text[_lang]["cancel_msg"] 263 | ) 264 | return ConversationHandler.END 265 | 266 | def error(update, context): 267 | """Log Errors caused by Updates.""" 268 | logger.warning('Update "%s" caused error "%s"', update, context.error) 269 | -------------------------------------------------------------------------------- /iCopy/workflow/size_workflow.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import re 5 | from telegram.ext import ConversationHandler 6 | from utils.load import _lang, _text, ns 7 | from utils import ( 8 | load, 9 | restricted as _r, 10 | get_functions as _func, 11 | task_box as _box, 12 | size_payload as _s_payload, 13 | keyboard as _KB, 14 | callback_stage as _stage, 15 | ) 16 | from drive.gdrive import GoogleDrive as _gd 17 | from multiprocessing import Process as _mp 18 | 19 | bot = load.bot 20 | ns.size = 0 21 | 22 | @_r.restricted 23 | def size(update, context): 24 | entry_cmd = update.effective_message.text 25 | match_cmd = re.search(r"^\/size ([1-9]\d*)$", entry_cmd, flags=re.I) 26 | if "/size" == entry_cmd: 27 | update.effective_message.reply_text(_text[_lang]["request_share_link"]) 28 | 29 | return _stage.COOK_ID 30 | 31 | elif match_cmd: 32 | limit_query = load.db_counters.find_one({"_id": "task_list_id"}) 33 | check_query = match_cmd.group(1) 34 | size_msg = update.effective_message.reply_text(_text[_lang]["ready_to_size"]) 35 | size_chat_id = size_msg.chat_id 36 | size_message_id = size_msg.message_id 37 | 38 | if int(check_query) <= limit_query["future_id"]: 39 | 40 | check_task = load.task_list.find_one({"_id": int(check_query)}) 41 | 42 | if check_task["status"] == 1: 43 | if "dst_endpoint_link" and "dst_endpoint_id" in check_task: 44 | task_id = str(check_query) 45 | task_link = check_task["dst_endpoint_link"] 46 | endpoint_id = check_task["dst_endpoint_id"] 47 | endpoint_name = check_task["src_name"] 48 | 49 | progress = _mp( 50 | target=_s_payload.owner_size, 51 | args=( 52 | ns, 53 | size_chat_id, 54 | size_message_id, 55 | task_id, 56 | task_link, 57 | endpoint_id, 58 | endpoint_name, 59 | ), 60 | ) 61 | progress.start() 62 | 63 | context.bot.edit_message_text( 64 | chat_id=size_chat_id, 65 | message_id=size_message_id, 66 | text=_text[_lang]["sizing"], 67 | ) 68 | 69 | return ConversationHandler.END 70 | 71 | else: 72 | dst_endpoint_id = _gd.get_dst_endpoint_id( 73 | _gd(), check_task["dst_id"], check_task["src_name"] 74 | ) 75 | if dst_endpoint_id: 76 | dst_endpoint_link = r"https://drive.google.com/open?id={}".format( 77 | dst_endpoint_id["id"] 78 | ) 79 | 80 | load.task_list.update_one( 81 | {"_id": int(check_query)}, 82 | { 83 | "$set": { 84 | "dst_endpoint_id": dst_endpoint_id["id"], 85 | "dst_endpoint_link": dst_endpoint_link, 86 | }, 87 | }, 88 | ) 89 | 90 | task_id = str(check_query) 91 | task_link = dst_endpoint_link 92 | endpoint_id = dst_endpoint_id["id"] 93 | endpoint_name = check_task["src_name"] 94 | 95 | progress = _mp( 96 | target=_s_payload.owner_size, 97 | args=( 98 | ns, 99 | size_chat_id, 100 | size_message_id, 101 | task_id, 102 | task_link, 103 | endpoint_id, 104 | endpoint_name, 105 | ), 106 | ) 107 | progress.start() 108 | 109 | context.bot.edit_message_text( 110 | chat_id=size_chat_id, 111 | message_id=size_message_id, 112 | text=_text[_lang]["sizing"], 113 | ) 114 | 115 | return ConversationHandler.END 116 | 117 | else: 118 | bot.edit_message_text( 119 | chat_id=size_chat_id, 120 | message_id=size_message_id, 121 | text=_text[_lang]["support_error"], 122 | ) 123 | 124 | return ConversationHandler.END 125 | 126 | elif check_task["status"] == 0: 127 | bot.edit_message_text( 128 | chat_id=size_chat_id, 129 | message_id=size_message_id, 130 | text=_text[_lang]["task_is_in_queue"] 131 | + "\n" 132 | + _text[_lang]["finished_could_be_check"], 133 | ) 134 | 135 | return ConversationHandler.END 136 | 137 | elif check_task["status"] == 2: 138 | bot.edit_message_text( 139 | chat_id=size_chat_id, 140 | message_id=size_message_id, 141 | text=_text[_lang]["doing"] 142 | + "\n" 143 | + _text[_lang]["finished_could_be_check"], 144 | ) 145 | 146 | return ConversationHandler.END 147 | 148 | else: 149 | bot.edit_message_text( 150 | chat_id=size_chat_id, 151 | message_id=size_message_id, 152 | text=_text[_lang]["over_limit_to_check"], 153 | ) 154 | 155 | return ConversationHandler.END 156 | 157 | elif entry_cmd.split(" ")[1].strip() == "fav": 158 | update.effective_message.reply_text( 159 | _text[_lang]["request_target_folder"], 160 | reply_markup=_KB.dst_keyboard(update, context), 161 | ) 162 | 163 | return _stage.COOK_FAV_TO_SIZE 164 | 165 | 166 | def pre_cook_fav_to_size(update, context): 167 | get_callback = update.callback_query.data 168 | size_msg = bot.edit_message_text( 169 | chat_id=update.callback_query.message.chat_id, 170 | message_id=update.callback_query.message.message_id, 171 | text=_text[_lang]["ready_to_size"], 172 | reply_markup=None, 173 | ) 174 | size_chat_id = size_msg.chat_id 175 | size_message_id = size_msg.message_id 176 | 177 | fav_info_list = get_callback.split("id+name") 178 | fav_id = fav_info_list[0] 179 | fav_name = fav_info_list[1] 180 | 181 | task_id = 0 182 | task_link = r"https://drive.google.com/open?id={}".format(fav_id) 183 | endpoint_id = fav_id 184 | endpoint_name = fav_name 185 | 186 | progress = _mp( 187 | target=_s_payload.owner_size, 188 | args=( 189 | ns, 190 | size_chat_id, 191 | size_message_id, 192 | task_id, 193 | task_link, 194 | endpoint_id, 195 | endpoint_name, 196 | ), 197 | ) 198 | progress.start() 199 | 200 | context.bot.edit_message_text( 201 | chat_id=size_chat_id, 202 | message_id=size_message_id, 203 | text=_text[_lang]["sizing"], 204 | ) 205 | 206 | return ConversationHandler.END 207 | 208 | 209 | def size_handle(update, context): 210 | tmp_share_name_list = "" 211 | share_id_list = [] 212 | share_name_list = [] 213 | share_id = update.effective_message.text 214 | share_id_list = _func.cook_to_id(share_id) 215 | for item in share_id_list: 216 | size_msg = update.effective_message.reply_text(_text[_lang]["ready_to_size"]) 217 | 218 | size_chat_id = size_msg.chat_id 219 | size_message_id = size_msg.message_id 220 | share_name_list += _func.get_src_name_from_id( 221 | update, item, list_name=tmp_share_name_list 222 | ) 223 | tmp_share_name_list = "" 224 | progress = _mp( 225 | target=_s_payload.simple_size, 226 | args=( 227 | ns, 228 | update, 229 | context, 230 | item, 231 | size_chat_id, 232 | size_message_id, 233 | share_name_list, 234 | ), 235 | ) 236 | progress.start() 237 | 238 | context.bot.edit_message_text( 239 | chat_id=size_chat_id, 240 | message_id=size_message_id, 241 | text=_text[_lang]["sizing"], 242 | ) 243 | 244 | return ConversationHandler.END 245 | 246 | -------------------------------------------------------------------------------- /iCopy/utils/size_payload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import re, pymongo 5 | from utils import load 6 | from utils.load import _lang, _text 7 | from threading import Thread 8 | import subprocess 9 | from telegram import ParseMode 10 | from telegram.utils.request import Request as TGRequest 11 | from telegram import Bot 12 | from multiprocessing import Manager 13 | 14 | myclient = pymongo.MongoClient( 15 | f"{load.cfg['database']['db_connect_method']}://{load.user}:{load.passwd}@{load.cfg['database']['db_addr']}", 16 | port=load.cfg["database"]["db_port"], 17 | connect=False, 18 | ) 19 | mydb = myclient[load.cfg["database"]["db_name"]] 20 | task_list = mydb["task_list"] 21 | fav_col = mydb["fav_col"] 22 | 23 | _cfg = load.cfg 24 | 25 | request = TGRequest(con_pool_size=8) 26 | bot = Bot(token=f"{_cfg['tg']['token']}", request=request) 27 | 28 | simple_sizing = subprocess.Popen 29 | size_object = "" 30 | size_size = "" 31 | size_info = "" 32 | 33 | def simpe_size_run(command): 34 | global simple_sizing 35 | simple_sizing = subprocess.Popen( 36 | command, 37 | stdout=subprocess.PIPE, 38 | stderr=subprocess.STDOUT, 39 | shell=False, 40 | encoding="utf-8", 41 | errors="ignore", 42 | universal_newlines=True, 43 | ) 44 | while True: 45 | line = simple_sizing.stdout.readline().rstrip() 46 | if not line: 47 | break 48 | yield line 49 | simple_sizing.communicate() 50 | 51 | def simple_size(ns, update, context, item, size_chat_id, size_message_id, share_name_list): 52 | cloner = _cfg["general"]["cloner"] 53 | option = "size" 54 | remote = _cfg["general"]["remote"] 55 | src_id = item 56 | src_block = remote + ":" + "{" + src_id + "}" 57 | checkers = "--checkers=" + f"{_cfg['general']['parallel_c']}" 58 | flags = ["--size-only"] 59 | flags += _cfg["general"]["run_args"] 60 | sa_sleep = "--drive-pacer-min-sleep=" + f"{_cfg['general']['min_sleep']}" 61 | 62 | command = [cloner, option, src_block, checkers, sa_sleep] 63 | command += flags 64 | 65 | simple_size_process(ns,command, size_chat_id, size_message_id, share_name_list) 66 | 67 | ns.size = 0 68 | 69 | 70 | def simple_size_process(ns,command, size_chat_id, size_message_id, share_name_list): 71 | for output in simpe_size_run(command): 72 | if ns.size == 1: 73 | simple_sizing.kill() 74 | 75 | regex_total_object = r"^Total objects:" 76 | regex_total_size = r"Total size:" 77 | if output: 78 | size_total_object = re.findall(regex_total_object, output) 79 | size_total_size = re.findall(regex_total_size, output) 80 | 81 | global size_object 82 | global size_size 83 | 84 | if size_total_object: 85 | size_object = output[15:] 86 | if size_total_size: 87 | size_size_all = output[12:] 88 | size_size = size_size_all.split("(") 89 | 90 | if ns.size == 0: 91 | for item in share_name_list: 92 | item_type = item["G_type"] 93 | item_id = item["G_id"] 94 | item_name = item["G_name"] 95 | 96 | global size_info 97 | size_info = ( 98 | " ༺ ✪iCopy✪ ༻ [" 99 | + _text[_lang]["sizing_done"] 100 | + "]\n\n" 101 | + item_type 102 | + " : " 103 | + item_name 104 | + "\n" 105 | + item_id 106 | + "\n" 107 | + "--------------------\n" 108 | + _text[_lang]["total_file_num"] 109 | + str(size_object) 110 | + "\n" 111 | + _text[_lang]["total_file_size"] 112 | + str(size_size[0]) 113 | + "\n(" 114 | + str(size_size[1]) 115 | ) 116 | 117 | bot.edit_message_text( 118 | chat_id=size_chat_id, message_id=size_message_id, text=size_info 119 | ) 120 | 121 | if ns.size == 1: 122 | bot.edit_message_text( 123 | chat_id=size_chat_id, 124 | message_id=size_message_id, 125 | text=_text[_lang]["is_killed_by_user"], 126 | ) 127 | 128 | def owner_size( 129 | ns, size_chat_id, size_message_id, task_id, task_link, endpoint_id, endpoint_name 130 | ): 131 | cloner = _cfg["general"]["cloner"] 132 | option = "size" 133 | remote = _cfg["general"]["remote"] 134 | src_id = endpoint_id 135 | src_block = remote + ":" + "{" + src_id + "}" 136 | checkers = "--checkers=" + f"{_cfg['general']['parallel_c']}" 137 | flags = ["--size-only"] 138 | sa_sleep = "--drive-pacer-min-sleep=" + f"{_cfg['general']['min_sleep']}" 139 | 140 | command = [cloner, option, src_block, checkers, sa_sleep] 141 | command += flags 142 | 143 | owner_size_process( 144 | ns, 145 | command, 146 | size_chat_id, 147 | size_message_id, 148 | task_id, 149 | task_link, 150 | endpoint_id, 151 | endpoint_name, 152 | ) 153 | 154 | ns.size = 0 155 | 156 | 157 | def owner_size_process( 158 | ns, 159 | command, 160 | size_chat_id, 161 | size_message_id, 162 | task_id, 163 | task_link, 164 | endpoint_id, 165 | endpoint_name, 166 | ): 167 | for output in simpe_size_run(command): 168 | if ns.size == 1: 169 | simple_sizing.kill() 170 | 171 | regex_total_object = r"^Total objects:" 172 | regex_total_size = r"Total size:" 173 | if output: 174 | size_total_object = re.findall(regex_total_object, output) 175 | size_total_size = re.findall(regex_total_size, output) 176 | 177 | global size_object 178 | global size_size 179 | 180 | if size_total_object: 181 | size_object = output[15:] 182 | if size_total_size: 183 | size_size_all = output[12:] 184 | size_size = size_size_all.split("(") 185 | 186 | if ns.size == 0: 187 | size_size_re = r"([\d.]+[\s]?)([kMGTP]?Bytes)" 188 | get_size_size = re.search(size_size_re, str(size_size[0])) 189 | if get_size_size: 190 | size_size_num = get_size_size.group(1).strip() 191 | size_size_tail = get_size_size.group(2).strip() 192 | 193 | if task_id == 0: 194 | size_info = ( 195 | " ༺ ✪iCopy✪ ༻ | favorites | [" 196 | + _text[_lang]["sizing_done"] 197 | + "]\n\n" 198 | + '{}'.format(task_link, endpoint_name) 199 | + "\n" 200 | + "--------------------\n" 201 | + _text[_lang]["total_file_num"] 202 | + str(size_object) 203 | + "\n" 204 | + _text[_lang]["total_file_size"] 205 | + str(size_size[0]) 206 | + "\n(" 207 | + str(size_size[1]) 208 | ) 209 | 210 | fav_col.update_one( 211 | {"G_id": endpoint_id}, 212 | { 213 | "$set": { 214 | "fav_object": int(size_object), 215 | "fav_size": float(size_size_num), 216 | "fav_size_tail": size_size_tail, 217 | }, 218 | }, 219 | upsert=True, 220 | ) 221 | 222 | else: 223 | size_info = ( 224 | " ༺ ✪iCopy✪ ༻ | " 225 | + "🏳️" 226 | + _text[_lang]["current_task_id"] 227 | + str(task_id) 228 | + " | [" 229 | + _text[_lang]["sizing_done"] 230 | + "]\n\n" 231 | + '{}'.format(task_link, endpoint_name) 232 | + "\n" 233 | + "--------------------\n" 234 | + _text[_lang]["total_file_num"] 235 | + str(size_object) 236 | + "\n" 237 | + _text[_lang]["total_file_size"] 238 | + str(size_size[0]) 239 | + "\n(" 240 | + str(size_size[1]) 241 | ) 242 | 243 | task_list.update_one( 244 | {"_id": int(task_id)}, 245 | { 246 | "$set": { 247 | "task_current_prog_num": int(size_object), 248 | "task_total_prog_num": int(size_object), 249 | "task_current_prog_size": float(size_size_num), 250 | "task_total_prog_size": float(size_size_num), 251 | "task_current_prog_size_tail": size_size_tail[:1], 252 | "task_total_prog_size_tail": size_size_tail, 253 | } 254 | }, 255 | ) 256 | 257 | bot.edit_message_text( 258 | chat_id=size_chat_id, 259 | message_id=size_message_id, 260 | text=size_info, 261 | parse_mode=ParseMode.HTML, 262 | disable_web_page_preview=True, 263 | ) 264 | 265 | elif ns.size == 1: 266 | bot.edit_message_text( 267 | chat_id=size_chat_id, 268 | message_id=size_message_id, 269 | text=_text[_lang]["is_killed_by_user"], 270 | ) 271 | -------------------------------------------------------------------------------- /iCopy/config/text.toml: -------------------------------------------------------------------------------- 1 | 2 | [cn] 3 | start = "Hi! replace ~ 欢迎使用 iCopy" #replace==待替换占位符 4 | guide_to_menu = "请输入 '/menu' 选择模式" 5 | menu_msg = "请选择模式" 6 | quick_mode = "极速模式" 7 | copy_mode = "自定义模式" 8 | size_mode = "文件统计" 9 | purge_mode = "回收站清理" 10 | dedupe_mode = "文件去重" 11 | mode_select_msg = "本次任务您选择了 ┋replace┋" #replace==待替换占位符 12 | request_share_link = "请输入 Google Drive 分享链接 或 分享ID" 13 | request_dst_target = "请选择文件保存位置" 14 | is_cover = "覆盖" 15 | not_cover = "返回" 16 | is_cover_quick_msg = "QUICK\\_MODE 已存在指定快捷路径\n是否需要覆盖" 17 | insert_quick_success = "QUICK\\_MODE 指定快捷路径已设置成功" 18 | modify_quick_success = "QUICK\\_MODE 指定快捷路径已更新成功" 19 | null_fav_quick = "未设置QUICK\\_MODE 转存目录\n请设置后再次尝试" 20 | null_fav = "未设置COPY\\_MODE 转存目录\n请设置后再次尝试" 21 | ready_to_task = "转存任务准备中..." 22 | doing = "正在执行任务" 23 | done = "任务成功" 24 | killed = "任务终止" 25 | current_task_id = "任务编号 : " 26 | task_start_time = "任务开始时间 : " 27 | task_finished_time = "任务结束时间 : " 28 | task_files_size = "文件总大小 : " 29 | task_files_num = "任务文件数 : " 30 | task_status = "任务状态 : " 31 | elapsed_time = "总耗时 : " 32 | cancel_msg = "欢迎再次使用 iCopy" 33 | get_quick_count_invaild = "quick 模式下的快捷目录只允许存在一个" 34 | get_multi_fav_error = "请根据'/set'规则提交目录设置" 35 | get_single_fav_error = "请根据'/set rule'规则发送命令" 36 | get_multi_in_single = "多行设置请使用 '/set'" 37 | task_src_info = "[任务目标]:" 38 | task_dst_info = "[转存地址]:" 39 | is_set_dedupe = "设置收藏夹遇到错误" 40 | set_fav_success = "设置收藏夹成功" 41 | delete_fav_success = "删除收藏夹成功" 42 | delete_quick_success = "QUICK_MODE 指定快捷路径已删除" 43 | show_fav_list = "下列为已设置的收藏夹" 44 | show_fav_list_null = "未设置任何收藏夹" 45 | is_restarting = "iCopy机器人正在重启...\n请在30秒后开始使用" 46 | is_killed_by_user = "任务已被用户终止" 47 | is_current_task = "当前任务 : \n\n" 48 | current_task_src_name = "源文件 : " 49 | current_task_dst_name = "目标地 : " 50 | is_not_current_task = "当前没有任务" 51 | show_wait_list = "个任务正在队列中\n\n" 52 | show_wait_list_null = "等待队列为空" 53 | interrupted = "任务中断" 54 | is_interrupted_error = "任务意外中断" 55 | restart_success = "iCopy 重启完成" 56 | add_task_successful = "添加任务成功" 57 | purge_fav = "清空收藏夹 (不包含 quick目录)" 58 | reset_successful = "任务 ID : replace 已被成功重置" #replace==待替换占位符 59 | over_limit_error = "超出最大可重置任务序号" 60 | over_limit_to_check = "超出最大可查询任务序号" 61 | over_limit_to_dedupe = "超出最大可去重任务序号" 62 | global_command_error = "输入的命令或者命令格式不存在" 63 | ready_to_size = "正在准备统计" 64 | sizing = "正在统计中,请耐心等待\n如果同时存在复制任务,则会影响复制与统计双方的准确性" 65 | total_file_num = "总文件数 : " 66 | total_file_size = "总大小 : " 67 | sizing_done = "统计完成" 68 | task_is_in_queue = "任务正在队列中" 69 | finished_could_be_check = "只能查询已完成的任务" 70 | finished_could_be_dedupe = "只能对已完成的任务进行去重" 71 | support_error = "指定的任务不支持查询" 72 | request_target_folder = "请选择目标收藏夹" 73 | ready_to_purge = "正在准备清空回收站" 74 | purging = "正在清空回收站,请耐心等待\n如果同时存在复制任务,则会影响复制与清空回收站双方的准确性" 75 | purging_done = "已清空回收站" 76 | request_dedupe_mode = "请选择去重模式 规则" 77 | ready_to_dedupe = "正在准备文件去重" 78 | deduping = "正在执行去重文件,请耐心等待\n如果同时存在复制任务,则会影响复制与文件去重双方的准确性" 79 | deduping_done = "文件去重已完成" 80 | is_folder_not_drive = "被选择的收藏夹是一个'文件夹'不是'团队盘'\n无法清空回收站" 81 | set_web_account = "请设置iCopy WEB DASHBOARD 账号秘密\n格式 : 账号,密码" 82 | set_web_success = "设置成功" 83 | 84 | [eng] 85 | start = "Hi! replace . Welcome to use iCopy" #replace==symbolic placeholder 86 | guide_to_menu = "Please Input '/menu' to Choose run mode" 87 | menu_msg = "Pls Choose the Mode" 88 | quick_mode = "QUICK MODE" 89 | copy_mode = "COPY MODE" 90 | size_mode = "Size" 91 | purge_mode = "Empty Recycle bin" 92 | dedupe_mode = "dedupe mode" 93 | mode_select_msg = "┋replace┋ has be choosen" #replace==symbolic placeholder 94 | request_share_link = "Pls input Shared_Link or Shared_ID" 95 | request_dst_target = "Please select the destination path for the task" 96 | is_cover = "Cover it" 97 | not_cover = "Cancel" 98 | is_cover_quick_msg = "QUICK\\_MODE The specified directory already exists\nDo you need to cover it?" 99 | insert_quick_success = "QUICK\\_MODE specified path is setted successfully" 100 | modify_quick_success = "QUICK\\_MODE specified path is renewed successfully" 101 | null_fav_quick = "QUICK\\_MODE directory is not set\nPlease try again after setting" 102 | null_fav = "COPY\\_MODE directory is not set\nPlease try again after setting" 103 | ready_to_task = "Now is Ready to Task..." 104 | doing = "Transferring" 105 | done = "Done" 106 | killed = "Killed" 107 | current_task_id = "task id : " 108 | task_start_time = "Task start time : " 109 | task_finished_time = "Task end time : " 110 | task_files_size = "Total file size : " 111 | task_files_num = "Task file num : " 112 | task_status = "task_status : " 113 | elapsed_time = "Elapsed Time : " 114 | cancel_msg = "Welcome to enjoy with iCopy again" 115 | get_quick_count_invaild = "Only ONE Dst ID can be specified under quick mode" 116 | get_multi_fav_error = "pls submit DST ID according '/set'" 117 | get_single_fav_error = "pls submit single DST ID according '/set rule'" 118 | get_multi_in_single = "pls use '/set' while modify multi DST IDs" 119 | task_src_info = "[Resource From]:" 120 | task_dst_info = "[Transfer To]:" 121 | is_set_err = "SET FAV ERROR" 122 | set_fav_success = "set Favorites success" 123 | delete_fav_success = "Favorites deleted successfully" 124 | delete_quick_success = "QUICK\\_MODE specified path is deleted successfully" 125 | show_fav_list = "The following are favorites that have been set" 126 | show_fav_list_null = "Favorites are not set" 127 | is_restarting = "iCopy is restarting...\nPlease start using in 30 seconds" 128 | is_killed_by_user = "Task is killed by user" 129 | is_current_task = "Current Task : \n\n" 130 | current_task_src_name = "Source : " 131 | current_task_dst_name = "Destination : " 132 | is_not_current_task = "No Task is in Processing" 133 | show_wait_list = "task in the queue\n\n" 134 | show_wait_list_null = "There is no task in waiting" 135 | interrupted = "Interrupted" 136 | is_interrupted_error = "Unexpected interruption" 137 | restart_success = "iCopy restart is complete" 138 | add_task_successful = "Add Task Successful" 139 | purge_fav = "purge Favorites (except Quick path)" 140 | reset_successful = "Task ID: replace has been reset successfully" #replace==symbolic placeholder 141 | over_limit_error = "Exceeded maximum resettable task number" 142 | over_limit_to_check = "Exceeded maximum task number which could be check" 143 | over_limit_to_dedupe = "Exceeded maximum task number which could be dedupe" 144 | global_command_error = "The input command or command format does not exist" 145 | ready_to_size = "Preparing statistics" 146 | sizing = "In statistics, pls wait \nIf there is a copy task, it will affect the accuracy of both copy and statistics" 147 | total_file_num = "Total Files : " 148 | total_file_size = "Total Size : " 149 | sizing_done = "Size Finished" 150 | task_is_in_queue = "Task is in the queue" 151 | finished_could_be_check = "You could only check the task which is finished" 152 | finished_could_be_dedupe = "You could only dedupe the task which is finished" 153 | support_error = "The specified task does not support queries" 154 | request_target_folder = "Pls choose the TARGET drive or folder" 155 | ready_to_purge = "Ready to Purge" 156 | purging = "In empty recycle bin, pls wait \nIf there is a copy task, it will affect the accuracy of both copy and recyle bin empting" 157 | purging_done = "The recycle bin has been emptied" 158 | request_dedupe_mode = "Please select to dedupe mode rule" 159 | ready_to_dedupe = "Ready to dedupe" 160 | deduping = "In deduping task, pls wait \nIf there is a copy task, it will affect the accuracy of both copy and dedupe" 161 | deduping_done = "The task is be deduped" 162 | is_folder_not_drive = "The selected favorites is a 'folder' not a 'shared drive'\n Can not be purged" 163 | set_web_account = "Please set your iCopy WEB DASHBOARD Account&Password follow the example\nexample : account,password" 164 | set_web_success = "Set Successfully" 165 | 166 | [jp] 167 | start = "Hi! replace . iCopyへようこそ" #replace==置き換え指示記号 168 | guide_to_menu = "実行モードを選択するには、'/menu' を入力してください" 169 | menu_msg = "ご覧のモードを選んでください" 170 | quick_mode = "「クイック」" 171 | copy_mode = "「カスタマイズ」" 172 | size_mode = "「サイズ」" 173 | purge_mode = "「ごみ箱を空にする」" 174 | dedupe_mode = "「重複を取り除く」" 175 | mode_select_msg = "┋replace┋が選択されました" #replace==置き換え指示記号 176 | request_share_link = "共有リンクまたは共有IDを入力してください" 177 | request_dst_target = "ファイルの保存場所を選択してください" 178 | is_cover = "カバー" 179 | not_cover = "キャンセル" 180 | is_cover_quick_msg = "QUICK_MODE指定されたフォルダーはすでに存在します\n新しいフォルダで上書きしますか?" 181 | insert_quick_success = "QUICK_MODEで指定されたフォルダーが正常に設定されました" 182 | modify_quick_success = "QUICK_MODEで指定されたフォルダーが更新されました" 183 | null_fav_quick = "QUICK\\_MODEフォルダーが設定されていません\n設定後にもう一度お試しください" 184 | null_fav = "COPY\\_MODEフォルダーが設定されていません\n設定後にもう一度お試しください" 185 | ready_to_task = "任務準備中..." 186 | doing = "タスク実行中" 187 | done = "任務成功" 188 | killed = "任務中止" 189 | current_task_id = "タスク番号 : " 190 | task_start_time = "タスク開始時刻 : " 191 | task_finished_time = "タスク終了時間 : " 192 | task_files_size = "ファイルサイズ : " 193 | task_files_num = "タスクファイル数 : " 194 | task_status = "タスク状態 : " 195 | elapsed_time = "合計時間 : " 196 | cancel_msg = "再びiCopyへようこそ" 197 | get_quick_count_invaild = "「クイックモード」で一つフォルダーしか指定されることができない" 198 | get_multi_fav_error = "'/set'にルールをよって、フォルダーのIDを入力してください" 199 | get_single_fav_error = "'/set rule'にルールをよって、フォルダーのIDを入力してください" 200 | get_multi_in_single = "複数のフォルダーを設定する場合は、「/set」コマンドを使用してください" 201 | task_src_info = "[任務目標]:" 202 | task_dst_info = "[保存場所]:" 203 | is_set_dedupe = "お気に入りを設定するとエラーが発生します" 204 | set_fav_success = "お気に入りを設定しました" 205 | delete_fav_success = "お気に入り削除に成功" 206 | delete_quick_success = "QUICK_MODEで指定されたフォルダーが削除されました" 207 | show_fav_list = "以下は既設のお気に入りである" 208 | show_fav_list_null = "お気に入りは設定いない" 209 | is_restarting = "iCopyが再起動しています...\n30秒後に使用を開始してください" 210 | is_killed_by_user = "タスクはユーザによって終了される" 211 | is_current_task = "進行中のタスク : \n\n" 212 | current_task_src_name = "源フォルダー : " 213 | current_task_dst_name = "目標フォルダー : " 214 | is_not_current_task = "進行中のタスクがない" 215 | show_wait_list = "タスクが待機しています\n\n" 216 | show_wait_list_null = "待機中のタスクはありません" 217 | interrupted = "タスク中断" 218 | is_interrupted_error = "予期しない中断" 219 | restart_success = "iCopyの再開が完了する" 220 | add_task_successful = "タスク追加成功" 221 | purge_fav = "お気に入りをクリア(クイックフォルダーを除く)" 222 | reset_successful = "タスク ID: replace がリセットされた"#replace==置き換え指示記号 223 | over_limit_error = "最大リセット可能タスク番号を超える" 224 | over_limit_to_check = "最大チェック可能タスク番号を超える" 225 | over_limit_to_dedupe = "最大重複除外可能タスク番号を超える" 226 | global_command_error = "入力するコマンドやコマンドフォーマットは存在しない" 227 | ready_to_size = "統計を準備している" 228 | sizing = "統計中ですので、お待ちください\nコピータスクが同時に存在する場合,コピーと統計の双方の正確さに影響する" 229 | total_file_num = "総 ファイル 数 : " 230 | total_file_size = "総 サイズ : " 231 | sizing_done = "統計終了" 232 | task_is_in_queue = "タスクが列に並んでいる" 233 | finished_could_be_check = "既に完了した任務しか調べられない" 234 | finished_could_be_dedupe = "すでに完了したタスクのみが重複ファイルの除去を行うことができる" 235 | support_error = "指定の任務の照会を支持しない" 236 | request_target_folder = "目標フォルダーを選んでください" 237 | ready_to_purge = "ごみ箱を空にする準備をしています" 238 | purging = "ごみ箱をクリアしています。\n同時にコピーの任務がある場合、コピーとごみ箱を空にすることの両方の正確さに影響します" 239 | purging_done = "既にごみ箱を空にした" 240 | request_dedupe_mode = "重複除外モードのルールを選択してください" 241 | ready_to_dedupe = "重複ファイルを除去する準備をしています" 242 | deduping = "重複ファイルを除去しています。\n同時にコピーの任務がある場合、コピーと重複除外の両方の正確さに影響します" 243 | deduping_done = "重複ファイルの除去が完了しました" 244 | is_folder_not_drive = "選択されたお気に入りは「フォルダ」ではなく「シェアドライバ」で\nゴミ箱を空にできない" 245 | set_web_account = "例に従って、iCopy WEB DASHBOARDアカウントとパスワードを設定してください\n例 : アカウント,パスワード" 246 | set_web_success = "正しく設定されました" -------------------------------------------------------------------------------- /iCopy/docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # iCopy v0.2 CHANGELOG 2 | 3 | ## version 0.2.2-Post.2 4 | 5 | + Fixbugs: 6 | + FIXBUG : Msg ParseMode Error 7 | 8 | ## version 0.2.2 9 | 10 | + Update : 11 | + ADD : source link in task message 12 | 13 | + Fixbugs: 14 | + FIXBUG : syntax error 15 | + FIXBUG : local variable error 16 | 17 | ## version 0.2.1-Post.5 18 | 19 | + Fixbugs: 20 | + FIXBUG : local variable assignment error 21 | + FIXBUG : dict error 22 | + FIXBUG : SQL Error 23 | 24 | ## version 0.2.1-beta.1 25 | 26 | NOTICE : Please replace the old "conf.toml" with the new one."conf.[web]dashboard" is the switch of WEB Service.conf.[web]port is the WEB service port. 27 | ( 0 = Off, 1 = On ) 28 | Default Account&Password : admin,admin 29 | Upgrade method : 30 | 31 | ```bash 32 | 1 git pull 33 | 2 pip3 install -r requirements.txt 34 | 3 edit and replace the new conf.toml 35 | 4 python3 iCopy.py 36 | ``` 37 | 38 | + Update : 39 | + ADD : iCopy WEB DASHBOARD 40 | + ADD : "/set web" command for set web account&password 41 | + ADD : WEB Section in "conf.toml" 42 | + ADD : API v1 43 | + ADD : logs for dedupe,purge,size 44 | + ADD : COMMAND doc in docs 45 | + FIX : run_args 46 | + FIX : Command & Flags 47 | 48 | + Fixbugs : 49 | + FIXBUG : local variable assignment error 50 | 51 | ## version 0.2.0-beta.6.7 52 | 53 | + Update : 54 | + ADD : renew requirements.txt. 55 | + ADD : "\_\_version\_\_" 56 | 57 | + Fixbugs : 58 | + Compatible with the old version of the database format 59 | + Fix floatify format error when the size is 0 60 | 61 | ## version 0.2.0-beta.6.6 62 | 63 | + Update : 64 | + Block "googleapiclient.discovery" warning prompt. 65 | + Deprecated "cache_discovery" 66 | 67 | ## version 0.2.0-beta.6.5 68 | 69 | + Fixbugs: 70 | + FIX : local variable assignment error 71 | + FIX : try to change restart function args 72 | 73 | ## version 0.2.0-beta.6.4 74 | 75 | + Fixbugs: 76 | + FIX : "/set" function "import as name" error 77 | 78 | ## version 0.2.0-beta.6.3 79 | 80 | + Update : 81 | + ADD : "/kill size","/kill purge","/kill dedupe" was added to terminate the execution of these tasks 82 | 83 | + Root Command: 84 | 85 | + start - nothing just say hello 86 | + menu - main entry point 87 | quick - quick mode 88 | copy - full mode 89 | set - customize settings 90 | task - task query 91 | reset - restore task 92 | size - just size task 93 | dedupe - dedupe drives and folders 94 | purge - delete files and folder in specified fav trash bin 95 | cancel - cancel TG conversation 96 | kill - kill task 97 | ver - check iCopy version 98 | restart - restart iCopy 99 | 100 | + Child Command: 101 | 102 | + set - customize settings 103 | ┖ set - batch way 104 | ┖ set rule - rules 105 | ┖ fav|quick +/- id - single way 106 | ┖ set purge - purge favorites 107 | + size - size query 108 | ┖ size - size the shared resource 109 | ┖ size id - size specified task 110 | ┖ size fav - size specified favorites 111 | + dedupe - dedupe drives and folders 112 | ┖ dedupe - dedupe specified favorites 113 | ┖ dedupe id - dedupe specified task 114 | + task - task query 115 | ┖ task - task in processing 116 | ┖ task list - future 10 tasks 117 | ┖ task id - show the specified task 118 | + reset - restore task 119 | ┖ reset - restore current task 120 | ┖ reset id - restore the specified task 121 | + kill - kill task 122 | ┖ kill - kill current transferring task 123 | ┖ kill task - kill current transferring task 124 | ┖ kill size - kill sizing task 125 | ┖ kill purge - kill purge task 126 | ┖ kill dedupe - kill dedupe task 127 | 128 | ## version 0.2.0-beta.6.2 129 | 130 | + Update : 131 | + ADD : Add "rmdir" operation to the "/Purge" function to clear the empty folder in the root directory. 132 | 133 | + Fixbugs : 134 | + FIX : "/dedupe" sendMsg error. 135 | + FIX : Insert DATABASE error while dedupe payload finished. 136 | 137 | ## version 0.2.0-beta.6.1 138 | 139 | + Update : 140 | + ADD : "/dedupe" Now you can choose favorites to dedupe 141 | + CHANGE : Move the stage tag uniformly to new file “utils/callback_stage.py" 142 | 143 | + Fixbugs : 144 | + Judge select favorites if is shared drive when you use "/purge" mode. 145 | + Separately define bot variables in asynchronous-process to prevent errors in connecting Telegram. 146 | 147 | + Root Command: 148 | 149 | + start - nothing just say hello 150 | + menu - main entry point 151 | quick - quick mode 152 | copy - full mode 153 | set - customize settings 154 | task - task query 155 | reset - restore task 156 | size - just size task 157 | dedupe - dedupe drives and folders 158 | purge - delete files and folder in specified fav trash bin 159 | cancel - cancel TG conversation 160 | kill - kill task which is in processing 161 | ver - check iCopy version 162 | restart - restart iCopy 163 | 164 | + Child Command: 165 | 166 | + set - customize settings 167 | ┖ set - batch way 168 | ┖ set rule - rules 169 | ┖ fav|quick +/- id - single way 170 | ┖ set purge - purge favorites 171 | + size - size query 172 | ┖ size - size the shared resource 173 | ┖ size id - size specified task 174 | ┖ size fav - size specified favorites 175 | + dedupe - dedupe drives and folders 176 | ┖ dedupe - dedupe specified favorites 177 | ┖ dedupe id - dedupe specified task via task id 178 | + task - task query 179 | ┖ task - task in processing 180 | ┖ task list - future 10 tasks 181 | ┖ task id - show the specified task 182 | + reset - restore task 183 | ┖ reset - restore current task 184 | ┖ reset id - restore the specified task 185 | 186 | ## version 0.2.0-beta.6 187 | 188 | + Update : 189 | + ADD : insert more details into Database and more initializated data 190 | + ADD : feedback dst endpoint link when task end normally 191 | + ADD : "/task id" only support the task which is start after v0.2.0b6 192 | + ADD : mark tasks that have been reset in the database 193 | + ADD : "/size id" & "/size fav" 194 | + ADD : "/purge" to empty shared drive trash bin 195 | + ADD : "/dedupe id" to dedupe task 196 | + ADD : Record the last time of size and dedupe 197 | 198 | + Fix : 199 | + FIX : Update RegEX Rules 200 | 201 | + Root Command: 202 | 203 | + start - nothing just say hello 204 | + menu - main entry point 205 | quick - quick mode 206 | copy - full mode 207 | set - customize settings 208 | task - task query 209 | reset - restore task 210 | size - just size task 211 | dedupe - dedupe specified task 212 | purge - delete files and folder in specified fav trash bin 213 | cancel - cancel TG conversation 214 | kill - kill task which is in processing 215 | ver - check iCopy version 216 | restart - restart iCopy 217 | 218 | + Child Command: 219 | 220 | + set - customize settings 221 | ┖ set - batch way 222 | ┖ set rule - rules 223 | ┖ fav|quick +/- id - single way 224 | ┖ set purge - purge favorites 225 | + size - size query 226 | ┖ size - size the shared resource 227 | ┖ size id - size specified task 228 | ┖ size fav - size specified favorites 229 | + task - task query 230 | ┖ task - task in processing 231 | ┖ task list - future 10 tasks 232 | ┖ task id - show the specified task 233 | + reset - restore task 234 | ┖ reset - restore current task 235 | ┖ reset id - restore the specified task 236 | 237 | ## version 0.2.0-beta.5.1 238 | 239 | + Update : 240 | + ADD : More task info into Database 241 | 242 | + Fixbugs : 243 | + FIX : delete "directly in" mode keyboard after selection is choosen 244 | + FIX : Purge local var after Conversation END 245 | The task will not be committed twice now 246 | + FIX : "/task list" error 247 | Now "/task list" will display up to 10 tasks pending 248 | 249 | + Root Command: 250 | 251 | + start - nothing just say hello 252 | + menu - main entry point 253 | quick - quick mode 254 | copy - full mode 255 | set - customize settings 256 | task - task query 257 | reset - restore task 258 | size - just size task 259 | cancel - cancel TG conversation 260 | kill - kill task which is in processing 261 | ver - check iCopy version 262 | restart - restart iCopy 263 | 264 | + Child Command: 265 | 266 | + set - customize settings 267 | ┖ set - batch way 268 | ┖ set rule - rules 269 | ┖ fav|quick +/- id - single way 270 | ┖ set purge - purge favorites 271 | + task - task query 272 | ┖ task - task in processing 273 | ┖ task list - future 10 tasks 274 | + reset - restore task 275 | ┖ reset - restore current task 276 | ┖ reset id - restore the specified task 277 | 278 | ## version 0.2.0-beta.5 279 | 280 | + Update : 281 | + ADD : directly input sharelink then choose mode. 282 | 283 | + Fixbugs : 284 | + FIX : get shared drive name failed when the shared drive is temporarily granted permission for an outside party. 285 | + FIX : Fix the error of repeated tasks when entering multiple tasks at the same time. 286 | + FIX : "reset" notice msg error 287 | 288 | ## version 0.2.0-beta.4.1 289 | 290 | + Fixbugs : 291 | + FIX : "/reset task_id" Database operation error 292 | 293 | ## version 0.2.0-beta.4 294 | 295 | + Update : 296 | + ADD "/size" a function to get simple size info 297 | 298 | + Fixbugs : 299 | + FIX : "/reset" send notice msg error 300 | + FIX : get shared drive name failed when the shared drive is temporarily granted permission for an outside party. 301 | 302 | *** 303 | 304 | ## version 0.2.0-beta.3 305 | 306 | Notice : The new "conf.toml" should be replaced or you could modify the "conf.toml" by referring to the "example" one. 307 | 308 | + Update : 309 | + ADD "/set purge" 310 | Allow to Purge Favorties Setting Now. 311 | this will not delete quick mode setting. 312 | + Now '--drive-server-side-across-configs' is Built in the iCopy. Remove from conf.toml 313 | + '--ignore-checksum' is write in conf.toml default 314 | + ADD "/reset" and "/reset id" command. 315 | You could restore task with the command 316 | 317 | *** 318 | 319 | ## version 0.2.0-beta.2 320 | 321 | Update : send confirm msg after task added 322 | Update : '/start' is not in Conversation Handle any more 323 | Update : Use '/menu' to select run mode instead of '/start' 324 | 325 | *** 326 | 327 | ## version 0.2.0-beta.1 328 | 329 | The first beta version of v0.2 330 | β1 is a relatively stable without bugs version 331 | The following Command is Supported 332 | 333 | + Root Command: 334 | 335 | + start - main entry point 336 | quick - quick mode 337 | copy - full mode 338 | set - customize settings 339 | task - task query 340 | cancel - cancel TG conversation 341 | kill - kill task which is in processing 342 | ver - check iCopy version 343 | restart - restart iCopy 344 | 345 | + Child Command: 346 | 347 | + set - ustomize settings 348 | ┖ set - batch way 349 | ┖ set rule - rules 350 | ┖ set fav|quick +/- id - single way 351 | task - task query 352 | ┖ task - task in processing 353 | ┖ task list - future 10 tasks 354 | 355 | *** 356 | 357 | ## version 0.2.0-alpha.1 ~ alpha.15 358 | 359 | iCopy rebuild basework finished 360 | 361 | ## version 0.1.7-beta.3 362 | 363 | Archived version 364 | ... 365 | -------------------------------------------------------------------------------- /iCopy/utils/get_set.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | from utils import ( 5 | load, 6 | get_functions as _func, 7 | messages as _msg, 8 | restricted as _r, 9 | keyboard as _KB, 10 | callback_stage as _stage, 11 | ) 12 | from telegram.ext import ConversationHandler 13 | from telegram import ParseMode 14 | from utils.load import _lang, _text 15 | from drive.gdrive import GoogleDrive as _gd 16 | ''' 17 | ( 18 | SET_FAV_MULTI, 19 | CHOOSE_MODE, 20 | GET_LINK, 21 | IS_COVER_QUICK, 22 | GET_DST, 23 | COOK_ID, 24 | REGEX_IN, 25 | REGEX_GET_DST, 26 | COOK_FAV_TO_SIZE, 27 | COOK_FAV_PURGE, 28 | COOK_ID_DEDU, 29 | COOK_FAV_DEDU, 30 | ) = range(12) 31 | ''' 32 | pick_quick = [] 33 | pick_fav = [] 34 | unpick_fav = [] 35 | judge_folder_len = [28, 33] 36 | showlist = [] 37 | showitem = "" 38 | 39 | 40 | @_r.restricted 41 | def _setting(update, context): 42 | entry_cmd = update.effective_message.text 43 | if " " in entry_cmd: 44 | entry_cmd = entry_cmd.replace(" ", "") 45 | 46 | if "/set" == entry_cmd.strip(): 47 | update.effective_message.reply_text( 48 | _msg.set_multi_fav_guide(_lang), parse_mode=ParseMode.MARKDOWN_V2 49 | ) 50 | 51 | return _stage.SET_FAV_MULTI 52 | 53 | elif "purge" == entry_cmd[4:]: 54 | fav_count = load.db_counters.find_one({"_id": "fav_count_list"}) 55 | if fav_count is not None and fav_count['fav_sum'] != 0: 56 | fav_sum = fav_count['fav_sum'] 57 | query = { "fav_type": {"$regex": "^fav"} } 58 | del_query = load.fav_col.delete_many(query) 59 | fav_sum -= int(del_query.deleted_count) 60 | load.db_counters.update( 61 | {"_id": "fav_count_list"}, 62 | {"fav_sum": fav_sum}, 63 | upsert=True, 64 | ) 65 | 66 | update.effective_message.reply_text( 67 | _text[_lang]["purge_fav"] 68 | ) 69 | 70 | return ConversationHandler.END 71 | 72 | else: 73 | update.effective_message.reply_text( 74 | _text[_lang]["show_fav_list_null"] 75 | ) 76 | 77 | return ConversationHandler.END 78 | 79 | elif "web" == entry_cmd[4:]: 80 | update.effective_message.reply_text( 81 | _text[_lang]["set_web_account"] 82 | ) 83 | 84 | return _stage.SET_WEB 85 | 86 | elif "/setlist" == entry_cmd: 87 | global showitem 88 | global showlist 89 | fav_count = load.db_counters.find_one({"_id": "fav_count_list"}) 90 | fav_list = load.fav_col.find({"fav_type": "fav"}) 91 | if fav_count is not None: 92 | if fav_count["fav_sum"] == 0: 93 | update.effective_message.reply_text(_text[_lang]["show_fav_list_null"]) 94 | 95 | return ConversationHandler.END 96 | 97 | elif fav_count["fav_sum"] != 0: 98 | for item in fav_list: 99 | showitem = ( 100 | "type : " 101 | + item["G_type"] 102 | + " | name : " 103 | + item["G_name"] 104 | + "\nid : " 105 | + item["G_id"] 106 | + "\n" 107 | + "--------------------\n" 108 | ) 109 | showlist.append(showitem) 110 | 111 | showlist = "".join(showlist) 112 | 113 | update.effective_message.reply_text( 114 | _text[_lang]["show_fav_list"] + "\n\n" + showlist 115 | ) 116 | 117 | showlist = [] 118 | 119 | return ConversationHandler.END 120 | 121 | else: 122 | update.effective_message.reply_text(_text[_lang]["show_fav_list_null"]) 123 | 124 | return ConversationHandler.END 125 | 126 | ### set single DST ID ### 127 | elif "quick" or "fav" in entry_cmd: 128 | ### single quick (drive or folder) 129 | if len(entry_cmd.splitlines()) == 1: 130 | each = entry_cmd[4:] 131 | if "quick" == each[:5]: 132 | if "quick+" == each[:6]: 133 | global pick_quick 134 | pick_quick = _func.get_name_from_id( 135 | update, each[6:], list_name=pick_quick 136 | ) 137 | insert_fav_quick = _func.insert_to_db_quick(pick_quick, update) 138 | if insert_fav_quick == "is_cover": 139 | update.effective_message.reply_text( 140 | _text[_lang]["is_cover_quick_msg"], 141 | parse_mode=ParseMode.MARKDOWN_V2, 142 | reply_markup=_KB.is_cover_keyboard(), 143 | ) 144 | 145 | return _stage.IS_COVER_QUICK 146 | 147 | elif "quick-" == each[:6]: 148 | _func.delete_in_db_quick 149 | update.effective_message.reply_text( 150 | _text[_lang]["delete_quick_success"] 151 | ) 152 | 153 | ### set fav folder(fav folder could be a drive or folder of GDrive) 154 | elif "fav" == each[:3]: 155 | fav_count = load.db_counters.find_one({"_id": "fav_count_list"}) 156 | fav_sum = 0 157 | 158 | if fav_count != None: 159 | fav_sum = fav_count["fav_sum"] 160 | 161 | if "+" == each[3]: 162 | global pick_fav 163 | pick_fav = _func.get_name_from_id( 164 | update, each[4:], list_name=pick_fav 165 | ) 166 | for item in pick_fav: 167 | item["fav_type"] = "fav" 168 | try: 169 | load.fav_col.insert_one(item) 170 | except: 171 | update.effective_message.reply_text( 172 | _text[_lang]["is_set_err"], 173 | ) 174 | else: 175 | fav_sum += 1 176 | load.db_counters.update( 177 | {"_id": "fav_count_list"}, 178 | {"fav_sum": fav_sum}, 179 | upsert=True, 180 | ) 181 | 182 | update.effective_message.reply_text(_text[_lang]["set_fav_success"]) 183 | 184 | pick_fav = [] 185 | 186 | if "-" == each[3]: 187 | global unpick_fav 188 | unpick_fav.append(each[4:]) 189 | for item in unpick_fav: 190 | delete_request = {"G_id": item} 191 | _func.delete_in_db(delete_request) 192 | fav_count = load.fav_col.find({"fav_type": "fav"}) 193 | fav_sum = len(list(fav_count)) 194 | load.db_counters.update( 195 | {"_id": "fav_count_list"}, {"fav_sum": fav_sum}, upsert=True 196 | ) 197 | 198 | update.effective_message.reply_text( 199 | _text[_lang]["delete_fav_success"] 200 | ) 201 | 202 | unpick_fav = [] 203 | 204 | ### single rule 205 | elif "rule" == entry_cmd[4:8]: 206 | update.effective_message.reply_text( 207 | _msg.set_single_fav_guide(_lang), parse_mode=ParseMode.MARKDOWN_V2 208 | ) 209 | 210 | return ConversationHandler.END 211 | 212 | else: 213 | update.effective_message.reply_text( 214 | _text[_lang]["get_single_fav_error"], 215 | parse_mode=ParseMode.MARKDOWN_V2, 216 | ) 217 | 218 | return ConversationHandler.END 219 | 220 | return ConversationHandler.END 221 | 222 | else: 223 | update.effective_message.reply_text( 224 | _text[_lang]["get_multi_in_single"], parse_mode=ParseMode.MARKDOWN_V2 225 | ) 226 | 227 | return ConversationHandler.END 228 | 229 | else: 230 | update.effective_message.reply_text( 231 | _msg.set_help(_lang), parse_mode=ParseMode.MARKDOWN_V2 232 | ) 233 | return ConversationHandler.END 234 | 235 | 236 | ### set multi DST ID ### 237 | def _multi_settings_recieved(update, context): 238 | _tmp_quick_counter = 0 239 | fav_msg = update.effective_message.text 240 | fav_msg = fav_msg.replace(" ", "").splitlines() 241 | global pick_quick 242 | for each in fav_msg: 243 | print(each) 244 | ### modify quick DST 245 | if "quick+" == each[:6]: 246 | _tmp_quick_counter += 1 247 | if _tmp_quick_counter == 1: 248 | global pick_quick 249 | pick_quick += _func.get_name_from_id( 250 | update, each[6:], list_name=pick_quick 251 | ) 252 | insert_fav_quick = _func.insert_to_db_quick(pick_quick, update) 253 | if insert_fav_quick == "error": 254 | update.effective_message.reply_text( 255 | _text[_lang]["is_cover_quick_msg"], 256 | parse_mode=ParseMode.MARKDOWN_V2, 257 | reply_markup=_KB.is_cover_keyboard(), 258 | ) 259 | 260 | return _stage.IS_COVER_QUICK 261 | 262 | elif _tmp_quick_counter < 1: 263 | pass 264 | elif _tmp_quick_counter > 1: 265 | print("error!") 266 | update.effective_message.reply_text( 267 | _text[_lang]["get_quick_count_invaild"] 268 | ) 269 | elif "quick-" == each[:6]: 270 | _func.delete_in_db_quick 271 | update.effective_message.reply_text(_text[_lang]["delete_quick_success"]) 272 | 273 | ### set fav folder(fav folder could be a drive or folder of GDrive) 274 | 275 | elif "fav" == each[:3]: 276 | fav_count = load.db_counters.find_one({"_id": "fav_count_list"}) 277 | fav_sum = 0 278 | 279 | if fav_count != None: 280 | fav_sum = fav_count["fav_sum"] 281 | 282 | if "+" == each[3]: 283 | global pick_fav 284 | pick_fav += _func.get_name_from_id(update, each[4:], list_name=pick_fav) 285 | for item in pick_fav: 286 | item["fav_type"] = "fav" 287 | try: 288 | load.fav_col.insert_one(item) 289 | except: 290 | update.effective_message.reply_text(_text[_lang]["is_set_err"],) 291 | else: 292 | fav_sum += 1 293 | load.db_counters.update( 294 | {"_id": "fav_count_list"}, {"fav_sum": fav_sum}, upsert=True 295 | ) 296 | 297 | update.effective_message.reply_text(_text[_lang]["set_fav_success"]) 298 | pick_fav = [] 299 | 300 | if "-" == each[3]: 301 | global unpick_fav 302 | unpick_fav.append(each[4:]) 303 | for item in unpick_fav: 304 | delete_request = {"G_id": item} 305 | _func.delete_in_db(delete_request) 306 | fav_count = load.fav_col.find({"fav_type": "fav"}) 307 | fav_sum = len(list(fav_count)) 308 | load.db_counters.update( 309 | {"_id": "fav_count_list"}, {"fav_sum": fav_sum}, upsert=True 310 | ) 311 | 312 | update.effective_message.reply_text(_text[_lang]["delete_fav_success"]) 313 | 314 | unpick_fav = [] 315 | 316 | else: 317 | if "/cancel" == update.effective_message.text: 318 | 319 | return _func.cancel(update, context) 320 | else: 321 | update.effective_message.reply_text(_text[_lang]["get_multi_fav_error"]) 322 | 323 | return ConversationHandler.END 324 | 325 | # ### set web acc/passwd 326 | def setWeb(update, context): 327 | accpw_list = [] 328 | raw_accpw = update.effective_message.text 329 | accpw_list = raw_accpw.split(",") 330 | web_acc = accpw_list[0] 331 | web_pw = accpw_list[1] 332 | load.login_col.update_one({"_id": "login_info"},{"$set": {"username": web_acc,"password": web_pw,},},) 333 | update.effective_message.reply_text( 334 | _text[_lang]["set_web_success"] 335 | ) 336 | 337 | return ConversationHandler.END 338 | -------------------------------------------------------------------------------- /iCopy/utils/task_payload.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import re, time, pymongo 5 | from utils import load, process_bar as _bar, get_functions as _func 6 | from multiprocessing import Process as _mp, Manager 7 | from utils.load import _lang, _text 8 | import subprocess 9 | from threading import Timer 10 | from drive.gdrive import GoogleDrive as _gd 11 | from telegram import ParseMode 12 | from telegram.utils.request import Request as TGRequest 13 | from telegram import Bot 14 | 15 | myclient = pymongo.MongoClient( 16 | f"{load.cfg['database']['db_connect_method']}://{load.user}:{load.passwd}@{load.cfg['database']['db_addr']}", 17 | port=load.cfg["database"]["db_port"], 18 | connect=False, 19 | ) 20 | mydb = myclient[load.cfg["database"]["db_name"]] 21 | task_list = mydb["task_list"] 22 | db_counters = mydb["counters"] 23 | 24 | _cfg = load.cfg 25 | 26 | request = TGRequest(con_pool_size=8) 27 | bot = Bot(token=f"{_cfg['tg']['token']}", request=request) 28 | 29 | message_info = "" 30 | prog_bar = "" 31 | current_working_file = "" 32 | old_working_file = "" 33 | now_elapsed_time = "" 34 | context_old = "" 35 | icopyprocess = subprocess.Popen 36 | interruption = 0 37 | dst_id = "" 38 | src_name = "" 39 | 40 | 41 | def task_buffer(ns): 42 | global dst_id 43 | global src_name 44 | while True: 45 | wait_list = task_list.find({"status": 0}) 46 | for task in wait_list: 47 | if _cfg["general"]["cloner"] == "fclone": 48 | flags = ["--drive-server-side-across-configs", "--check-first", '-P', '--ignore-checksum' , '--stats=1s'] 49 | 50 | command = [] 51 | 52 | cloner = _cfg["general"]["cloner"] 53 | option = _cfg["general"]["option"] 54 | remote = _cfg["general"]["remote"] 55 | src_id = task["src_id"] 56 | src_name = task["src_name"] 57 | if "/" in src_name: 58 | src_name = src_name.replace("/", "|") 59 | if "'" in src_name: 60 | src_name = src_name.replace("'", "") 61 | if '"' in src_name: 62 | src_name = src_name.replace('"', "") 63 | 64 | dst_id = task["dst_id"] 65 | src_block = remote + ":" + "{" + src_id + "}" 66 | dst_block = remote + ":" + "{" + dst_id + "}" + "/" + src_name 67 | checkers = "--checkers=" + f"{_cfg['general']['parallel_c']}" 68 | transfers = "--transfers=" + f"{_cfg['general']['parallel_t']}" 69 | sa_sleep = "--drive-pacer-min-sleep=" + f"{_cfg['general']['min_sleep']}" 70 | 71 | flags += _cfg["general"]["run_args"] 72 | flags += [checkers, transfers, sa_sleep] 73 | 74 | command = [cloner, option, src_block, dst_block] 75 | 76 | command += flags 77 | 78 | chat_id = task["chat_id"] 79 | 80 | task_process(chat_id, command, task, ns, src_name) 81 | 82 | ns.x = 0 83 | 84 | flags = [] 85 | 86 | global old_working_line 87 | global current_working_line 88 | old_working_line = 0 89 | current_working_line = 0 90 | time.sleep(3) 91 | 92 | time.sleep(5) 93 | 94 | 95 | def task_process(chat_id, command, task, ns, src_name): 96 | # mark is in processing in db 97 | task_list.update_one({"_id": task["_id"]}, {"$set": {"status": 2,}}) 98 | db_counters.update({"_id": "last_task"}, {"task_id": task["_id"]}, upsert=True) 99 | chat_id = chat_id 100 | message = bot.send_message(chat_id=chat_id, text=_text[_lang]["ready_to_task"]) 101 | message_id = message.message_id 102 | dst_info = {} 103 | interval = 0.1 104 | timeout = 180 105 | xtime = 0 106 | old_working_line = 0 107 | current_working_line = 0 108 | task_current_prog_num = 0 109 | task_total_prog_num = 0 110 | task_percent = 0 111 | task_current_prog_size = "0" 112 | task_total_prog_size = "0 Bytes" 113 | task_in_size_speed = "-" 114 | task_in_file_speed = "-" 115 | task_eta_in_file = "-" 116 | task_current_prog_size_tail = "" 117 | task_total_prog_size_tail = "" 118 | dst_id = task['dst_id'] 119 | src_link = r"https://drive.google.com/open?id={}".format(task["src_id"]) 120 | start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 121 | 122 | for toutput in run(command): 123 | if ns.x == 1: 124 | global icopyprocess 125 | icopyprocess.kill() 126 | 127 | regex_working_file = r"^ * " 128 | regex_elapsed_time = r"^Elapsed time:" 129 | regex_total_files = ( 130 | r"Transferred:\s+(\d+) / (\d+), (\d+)%(?:,\s*([\d.]+\sFiles/s))?" 131 | ) 132 | regex_total_size = ( 133 | r"Transferred:[\s]+([\d.]+\s*)([kMGTP]?) / ([\d.]+[\s]?)([kMGTP]?Bytes)," 134 | r"\s*(?:\-|(\d+)\%),\s*([\d.]+\s*[kMGTP]?Bytes/s),\s*ETA\s*([\-0-9hmsdwy]+)" 135 | ) 136 | 137 | output = toutput 138 | 139 | if output: 140 | task_total_files = re.search(regex_total_files, output) 141 | task_total_size = re.search(regex_total_size, output) 142 | task_elapsed_time = re.findall(regex_elapsed_time, output) 143 | task_working_file = re.findall(regex_working_file, output) 144 | 145 | if task_total_files: 146 | task_current_prog_num = task_total_files.group(1) 147 | task_total_prog_num = task_total_files.group(2) 148 | task_percent = int(task_total_files.group(3)) 149 | task_in_file_speed = task_total_files.group(4) 150 | 151 | if task_total_size: 152 | task_current_prog_size = task_total_size.group(1).strip() 153 | task_current_prog_size_tail = task_total_size.group(2) 154 | task_total_prog_size = task_total_size.group(3).strip() 155 | task_total_prog_size_tail = task_total_size.group(4) 156 | task_in_size_speed = task_total_size.group(6) 157 | task_eta_in_file = task_total_size.group(7) 158 | 159 | if task_elapsed_time: 160 | global now_elapsed_time 161 | now_elapsed_time = output.replace(" ", "").split(":")[1] 162 | 163 | if task_working_file: 164 | global current_working_file 165 | current_working_line += 1 166 | current_working_file = ( 167 | output.lstrip("* ").rsplit(":")[0].rstrip("Transferred") 168 | ) 169 | 170 | global prog_bar 171 | prog_bar = _bar.status(0) 172 | if task_percent != 0: 173 | prog_bar = _bar.status(task_percent) 174 | 175 | global message_info 176 | message_info = ( 177 | _text[_lang]["task_src_info"] 178 | + "\n" 179 | + "📃" 180 | + '{}'.format(src_link, src_name) 181 | + "\n" 182 | + "----------------------------------------" 183 | + "\n" 184 | + _text[_lang]["task_dst_info"] 185 | + "\n" 186 | + "📁" 187 | + task["dst_name"] 188 | + ":" 189 | + "\n" 190 | + " ┕─📃" 191 | + src_name 192 | + "\n" 193 | + "----------------------------------------" 194 | + "\n\n" 195 | + _text[_lang]["task_start_time"] 196 | + start_time 197 | + "\n\n" 198 | + _text[_lang]["task_files_size"] 199 | + str(task_current_prog_size) 200 | + task_current_prog_size_tail 201 | + "/" 202 | + str(task_total_prog_size) 203 | + task_total_prog_size_tail 204 | + "\n" 205 | + _text[_lang]["task_files_num"] 206 | + str(task_current_prog_num) 207 | + "/" 208 | + str(task_total_prog_num) 209 | + "\n" 210 | + _text[_lang]["task_status"] 211 | + "\n\n" 212 | + str(task_in_size_speed) 213 | + " | " 214 | + str(task_in_file_speed) 215 | + "\n\n" 216 | + str(task_percent) 217 | + "%" 218 | + str(prog_bar) 219 | ) 220 | 221 | if ( 222 | int(time.time()) - xtime > interval 223 | and old_working_line != current_working_line 224 | ): 225 | Timer( 226 | 0, 227 | task_message_box, 228 | args=( 229 | bot, 230 | chat_id, 231 | message_id, 232 | " ༺ ✪iCopy✪ ༻ \n" 233 | + _text[_lang]["doing"] 234 | + " | " 235 | + "🏳️" 236 | + _text[_lang]["current_task_id"] 237 | + str(task["_id"]) 238 | + "\n\n" 239 | + message_info 240 | + "\n\n" 241 | + current_working_file[:30] 242 | + "\n" 243 | + "ETA : " 244 | + str(task_eta_in_file), 245 | ), 246 | ).start() 247 | old_working_line = current_working_line 248 | global old_working_file 249 | old_working_file = current_working_file 250 | time.sleep(3.5) 251 | xtime = time.time() 252 | 253 | if ( 254 | int(time.time()) - xtime > timeout 255 | and current_working_file == old_working_file 256 | and task_percent > 5 257 | ): 258 | global interruption 259 | interruption = 1 260 | break 261 | 262 | old_working_file = "" 263 | finished_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 264 | 265 | if ns.x == 0: 266 | time.sleep(4) 267 | prog_bar = _bar.status(100) 268 | dst_info = _func.getIDbypath(dst_id, src_name) 269 | dst_endpoint_id = dst_info['dst_endpoint_id'] 270 | dst_endpoint_link = dst_info['dst_endpoint_link'] 271 | bot.edit_message_text( 272 | chat_id=chat_id, 273 | message_id=message_id, 274 | text=" ༺ ✪iCopy✪ ༻ \n" 275 | + _text[_lang]["done"] 276 | + " | " 277 | + "🏳️" 278 | + _text[_lang]["current_task_id"] 279 | + str(task["_id"]) 280 | + "\n\n" 281 | + message_info.replace( 282 | " ┕─📃" + src_name, 283 | " ┕─📃" 284 | + '{}'.format(dst_endpoint_link, src_name), 285 | ) 286 | + "\n" 287 | + _text[_lang]["task_finished_time"] 288 | + finished_time 289 | + "\n" 290 | + _text[_lang]["elapsed_time"] 291 | + str(now_elapsed_time), 292 | parse_mode=ParseMode.HTML, 293 | disable_web_page_preview=True, 294 | ) 295 | check_is_reset = task_list.find_one({"_id": task["_id"]}) 296 | if check_is_reset['is_reset'] == 0: 297 | if task_total_prog_size != 0 and task_current_prog_size != 0: 298 | task_list.update_one( 299 | {"_id": task["_id"]}, 300 | { 301 | "$set": { 302 | "status": 1, 303 | "start_time": start_time, 304 | "finished_time": finished_time, 305 | "task_current_prog_num": int(task_current_prog_num), 306 | "task_total_prog_num": int(task_total_prog_num), 307 | "task_current_prog_size": float(task_current_prog_size), 308 | "task_total_prog_size": float(task_total_prog_size), 309 | "task_current_prog_size_tail": task_current_prog_size_tail, 310 | "task_total_prog_size_tail": task_total_prog_size_tail, 311 | "dst_endpoint_link": dst_endpoint_link, 312 | "dst_endpoint_id": dst_endpoint_id, 313 | } 314 | }, 315 | ) 316 | else: 317 | task_list.update_one( 318 | {"_id": task["_id"]}, 319 | { 320 | "$set": { 321 | "status": 1, 322 | "start_time": start_time, 323 | "finished_time": finished_time, 324 | "task_current_prog_num": int(task_current_prog_num), 325 | "task_total_prog_num": int(task_total_prog_num), 326 | "task_current_prog_size": int(task_current_prog_size), 327 | "task_total_prog_size": int(task_total_prog_size), 328 | "task_current_prog_size_tail": task_current_prog_size_tail, 329 | "task_total_prog_size_tail": task_total_prog_size_tail, 330 | "dst_endpoint_link": dst_endpoint_link, 331 | "dst_endpoint_id": dst_endpoint_id, 332 | } 333 | }, 334 | ) 335 | 336 | elif check_is_reset['is_reset'] == 1: 337 | if "task_current_prog_num" and "task_total_prog_num" in check_is_reset: 338 | task_list.update_one( 339 | {"_id": task["_id"]}, 340 | { 341 | "$set": { 342 | "status": 1, 343 | "start_time": start_time, 344 | "finished_time": finished_time, 345 | "task_current_prog_num": int(task_current_prog_num) + check_is_reset['task_current_prog_num'], 346 | "task_total_prog_num": int(task_total_prog_num) + check_is_reset['task_total_prog_num'], 347 | "task_current_prog_size": 0, 348 | "task_total_prog_size": 0, 349 | "task_current_prog_size_tail": "", 350 | "task_total_prog_size_tail": "", 351 | "dst_endpoint_link": dst_endpoint_link, 352 | "dst_endpoint_id": dst_endpoint_id, 353 | } 354 | }, 355 | ) 356 | else: 357 | task_list.update_one( 358 | {"_id": task["_id"]}, 359 | { 360 | "$set": { 361 | "status": 1, 362 | "start_time": start_time, 363 | "finished_time": finished_time, 364 | "task_current_prog_num": int(task_current_prog_num), 365 | "task_total_prog_num": int(task_total_prog_num), 366 | "task_current_prog_size": 0, 367 | "task_total_prog_size": 0, 368 | "task_current_prog_size_tail": "", 369 | "task_total_prog_size_tail": "", 370 | "dst_endpoint_link": dst_endpoint_link, 371 | "dst_endpoint_id": dst_endpoint_id, 372 | } 373 | }, 374 | ) 375 | 376 | if ns.x == 1: 377 | bot.edit_message_text( 378 | chat_id=chat_id, 379 | message_id=message_id, 380 | text=" ༺ ✪iCopy✪ ༻ \n" 381 | + _text[_lang]["killed"] 382 | + " | " 383 | + "🏳️" 384 | + _text[_lang]["current_task_id"] 385 | + str(task["_id"]) 386 | + "\n\n" 387 | + message_info.replace( 388 | "\n\n" + _text[_lang]["task_start_time"] + start_time, "" 389 | ).replace( 390 | "\n" 391 | + _text[_lang]["task_status"] 392 | + "\n\n" 393 | + str(task_in_size_speed) 394 | + " | " 395 | + str(task_in_file_speed), 396 | "", 397 | ) 398 | + "\n" 399 | + _text[_lang]["is_killed_by_user"], 400 | ) 401 | 402 | task_list.update_one( 403 | {"_id": task["_id"]}, 404 | { 405 | "$set": { 406 | "status": 1, 407 | "error": 9, 408 | "start_time": start_time, 409 | "finished_time": finished_time, 410 | } 411 | }, 412 | ) 413 | 414 | if interruption == 1: 415 | bot.edit_message_text( 416 | chat_id=chat_id, 417 | message_id=message_id, 418 | text=" ༺ ✪iCopy✪ ༻ \n" 419 | + _text[_lang]["interrupted"] 420 | + " | " 421 | + "🏳️" 422 | + _text[_lang]["current_task_id"] 423 | + str(task["_id"]) 424 | + "\n\n" 425 | + message_info.replace( 426 | "\n\n" + _text[_lang]["task_start_time"] + start_time, "" 427 | ).replace( 428 | "\n" 429 | + _text[_lang]["task_status"] 430 | + "\n\n" 431 | + str(task_in_size_speed) 432 | + " | " 433 | + str(task_in_file_speed), 434 | "", 435 | ) 436 | + "\n" 437 | + _text[_lang]["is_interrupted_error"], 438 | ) 439 | 440 | task_list.update_one( 441 | {"_id": task["_id"]}, 442 | { 443 | "$set": { 444 | "status": 1, 445 | "error": 1, 446 | "start_time": start_time, 447 | "finished_time": finished_time, 448 | } 449 | }, 450 | ) 451 | 452 | prog_bar = _bar.status(0) 453 | 454 | 455 | def task_message_box(bot, chat_id, message_id, context): 456 | global context_old 457 | context_old = "iCopy" 458 | if context_old != context: 459 | bot.edit_message_text(chat_id=chat_id, message_id=message_id, text=context, parse_mode=ParseMode.HTML, disable_web_page_preview=True,) 460 | context_old = context 461 | 462 | 463 | def run(command): 464 | global icopyprocess 465 | icopyprocess = subprocess.Popen( 466 | command, 467 | stdout=subprocess.PIPE, 468 | stderr=subprocess.STDOUT, 469 | shell=False, 470 | encoding="utf-8", 471 | errors="ignore", 472 | universal_newlines=True, 473 | ) 474 | while True: 475 | line = icopyprocess.stdout.readline().rstrip() 476 | if not line: 477 | break 478 | yield line 479 | icopyprocess.communicate() 480 | 481 | --------------------------------------------------------------------------------