├── Aptfile
├── Procfile
├── README.md
├── Set.sh
├── app.json
├── iCopy
├── Bot.py
├── config
│ └── text.toml
├── docs
│ ├── CHANGELOG.md
│ ├── TODO.md
│ └── develop_update.md
├── drive
│ ├── __init__.py
│ └── gdrive.py
├── utils
│ ├── __init__.py
│ ├── __version__.py
│ ├── callback_stage.py
│ ├── dedupe_payload.py
│ ├── get_functions.py
│ ├── get_set.py
│ ├── keyboard.py
│ ├── load.py
│ ├── messages.py
│ ├── process_bar.py
│ ├── purge_payload.py
│ ├── restricted.py
│ ├── size_payload.py
│ ├── task_box.py
│ └── task_payload.py
└── workflow
│ ├── __init__.py
│ ├── copy_workflow.py
│ ├── dedupe_workflow.py
│ ├── purge_workflow.py
│ ├── quick_workflow.py
│ ├── regex_workflow.py
│ ├── size_workflow.py
│ └── start_workflow.py
├── requirements.txt
└── runtime.txt
/Aptfile:
--------------------------------------------------------------------------------
1 | wget
2 | wget2
3 | p7zip-full
4 | unzip
5 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | worker: source Set.sh
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # iCopy Version 0.2.0-Beta.6.7
2 |
3 | [
](https://bbs.jsu.net/c/official-project/icopy/6)
4 |
5 | [](https://bbs.jsu.net/c/official-project/icopy/6)
6 | [](https://bbs.jsu.net/c/official-project/icopy/6)
7 | [](https://bbs.jsu.net/c/official-project/icopy/6)
8 | [](https://bbs.jsu.net/c/official-project/icopy/6)
9 | [](https://github.com/mongodb/mongo)
10 | [](https://github.com/Nenokkadine/FClone-Bot)
11 |
12 | ## Deployment to Heroku
13 |
14 | [](https://dashboard.heroku.com/new?template=https://github.com/Nenokkadine/Fclone-Bot/tree/master)
15 |
16 | ## Credits
17 |
18 | iCopy - [fxxkrlab](https://github.com/fxxkrlab/iCopy)
19 | Fclone - [Mawaya](https://github.com/mawaya/rclone)
20 |
--------------------------------------------------------------------------------
/Set.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 | cd ./iCopy/config
3 | echo "[tg]
4 |
5 | "token = "\"$BOT_TOKEN\"
6 |
7 | "usr_id = "\"$USER_ID\"
8 |
9 | [database]
10 |
11 | "db_connect_method = "\"$DB_CONNECT_METHOD\"
12 |
13 | "db_addr = "\"$DB_ADDRESS\"
14 |
15 | "db_port = "$DB_PORT""
16 |
17 | "db_name = "\"$DB_NAME\"
18 |
19 | "db_user = "\"$DB_USERNAME\"
20 |
21 | "db_passwd = "\"$DB_PASS\"
22 |
23 | [general]
24 |
25 | "language = "\"$LANGUAGE\"
26 |
27 | "cloner = "\"$CLONER\"
28 |
29 | "option = "\"$OPTION\"
30 |
31 | "remote = "\"$RCLONE_RMT\"
32 |
33 | "parallel_c = "\"$PARALLEL_CHECKERS\"
34 |
35 | "parallel_t = "\"$PARALLEL_TRANSFERS\"
36 |
37 | "min_sleep = "\"$MIN_SLEEP\"
38 |
39 | "sa_path = "\"$SA_PATH\"
40 |
41 | run_args = $RUN_ARGS
42 | " >> conf.toml
43 | echo "[$RCLONE_RMT]
44 | type = drive
45 | scope = drive
46 | "service_account_file = /app/iCopy/accounts/$SA_INIT_FILE"
47 | "service_account_file_path = $SA_PATH"
48 | "team_drive = $FOLDER_ID"
49 | " >> rclone.conf
50 | cd ..
51 | wget --no-check-certificate -q $SA_ZIP_URL -O accounts.zip
52 | unzip -qq accounts.zip -d /app/iCopy/
53 | chmod 777 Bot.py
54 | python3 Bot.py
55 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "iCopy",
3 | "description": "Fclone Telegram Bot",
4 | "repository": "https://github.com/Nenokkadine/Fclone-Bot",
5 | "keywords": ["Fclone","Icopy"],
6 | "logo" : "https://f002.backblazeb2.com/file/jsuforum-upload/optimized/1X/cff2835c1652bb57a18aac42a3eee34b51cd9b89_2_1380x386.gif",
7 | "env": {
8 | "BOT_TOKEN": {
9 | "description": "Get from Telegram Botfather"
10 | },
11 | "USER_ID": {
12 | "description": "get from telegram get_id_bot"
13 | },
14 | "DB_CONNECT_METHOD": {
15 | "description": "DB Connect Method",
16 | "value": "mongodb+srv"
17 | },
18 | "DB_ADDRESS": {
19 | "description": "MongoDB Host Address"
20 | },
21 | "DB_PORT": {
22 | "description": "MongoDB Port, Default is 27017",
23 | "value": "27017"
24 | },
25 | "DB_NAME": {
26 | "description": "U Can give any name, Default is iCopy",
27 | "value": "iCopy"
28 | },
29 | "DB_USERNAME": {
30 | "description": "MongoDB Username"
31 | },
32 | "DB_PASS": {
33 | "description": "MongoDB Password"
34 | },
35 | "LANGUAGE": {
36 | "description": "English by Default Support cn and jp",
37 | "value": "eng"
38 | },
39 | "CLONER": {
40 | "description": "Dont Change this",
41 | "value": "fclone"
42 | },
43 | "OPTION": {
44 | "description": "Copy or Sync Default is Copy",
45 | "value": "copy"
46 | },
47 | "RCLONE_RMT": {
48 | "description": "Give a Rclone Remote Name Default is also fine",
49 | "value": "icopy"
50 | },
51 | "PARALLEL_CHECKERS": {
52 | "description": "Dont Give More than 300 Heroku Free Dyno may Crash",
53 | "value": "250"
54 | },
55 | "PARALLEL_TRANSFERS": {
56 | "description": "Dont Give More than 300 Heroku Free Dyno may Crash",
57 | "value": "250"
58 | },
59 | "MIN_SLEEP": {
60 | "description": "customize drive-pacer-min-sleep",
61 | "value": "1ms"
62 | },
63 | "SA_INIT_FILE": {
64 | "description": "Give a service account File Name. ex 1.json "
65 | },
66 | "RCLONE_CONFIG": {
67 | "description": "Dont Change",
68 | "value": "/app/iCopy/config/rclone.conf"
69 | },
70 | "SA_ZIP_URL": {
71 | "description": "Service Accounts ZIP URL.It Should be Zipped Such that it should have a folder named accounts with SA in it "
72 | },
73 | "TEAM_DRIVE_ID": {
74 | "description": "Destination Team Drive ID"
75 | },
76 | "RUN_ARGS": {
77 | "description": "Only Change this if U Know",
78 | "value": "['-P', '--ignore-checksum' , '--stats=1s', '--log-level=DEBUG', '--log-file=/app/icopy_cloner_debug.log']"
79 | },
80 | "SA_PATH": {
81 | "description": "Dont Change",
82 | "value": "/app/iCopy/accounts"
83 | }
84 | },
85 | "buildpacks": [
86 | {
87 | "url": "heroku/python"
88 | },
89 | {
90 | "url": "https://github.com/heroku/heroku-buildpack-apt.git"
91 | },
92 | {
93 | "url": "https://github.com/opendoor-labs/heroku-buildpack-p7zip.git"
94 | },
95 | {
96 | "url" : "https://github.com/Nenokkadine/Fpack.git"
97 | }
98 | ],
99 | "formation": {
100 | "worker": {
101 | "quantity": 1,
102 | "size": "free"
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/iCopy/Bot.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 | },
126 | fallbacks=[CommandHandler("cancel", _func.cancel)],
127 | )
128 |
129 | def stop_and_restart():
130 | progress.terminate()
131 | load.myclient.close()
132 | updater.stop()
133 | os.execl(sys.executable, sys.executable, *sys.argv)
134 |
135 | def restart(update, context):
136 | restart_msg = update.message.reply_text(load._text[load._lang]["is_restarting"])
137 | restart_chat_id = restart_msg.chat_id
138 | restart_msg_id = restart_msg.message_id
139 | load.db_counters.update_one(
140 | {"_id": "is_restart"},
141 | {
142 | "$set": {
143 | "status": 1,
144 | "chat_id": restart_chat_id,
145 | "message_id": restart_msg_id,
146 | }
147 | },
148 | True,
149 | )
150 | Thread(target=stop_and_restart).start()
151 |
152 | dp.add_handler(conv_handler)
153 | dp.add_handler(CommandHandler("start", _start.start))
154 | dp.add_handler(CommandHandler("reset", _box.task_reset))
155 | dp.add_handler(CommandHandler("kill", _func.taskill))
156 | dp.add_handler(CommandHandler("ver", _func._version))
157 |
158 | dp.add_handler(
159 | CommandHandler(
160 | "restart",
161 | restart,
162 | filters=Filters.user(user_id=int(load.cfg["tg"]["usr_id"])),
163 | )
164 | )
165 |
166 | dp.add_error_handler(_func.error)
167 |
168 | updater.start_polling()
169 | logger.info("Fxxkr LAB iCopy " + __version__.__version__ + " Start")
170 | updater.idle()
171 |
172 |
173 | if __name__ == "__main__":
174 | ns.x = 0
175 | progress = _mp(target=_payload.task_buffer, args=(ns,))
176 | progress.start()
177 | #web = _mp(target=dash.dashboard)
178 | #web.start()
179 | main()
180 |
--------------------------------------------------------------------------------
/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 |
82 | [eng]
83 | start = "Hi! replace . Welcome to use iCopy" #replace==symbolic placeholder
84 | guide_to_menu = "Please Input '/menu' to Choose run mode"
85 | menu_msg = "Pls Choose the Mode"
86 | quick_mode = "QUICK MODE"
87 | copy_mode = "COPY MODE"
88 | size_mode = "Size"
89 | purge_mode = "Empty Recycle bin"
90 | dedupe_mode = "dedupe mode"
91 | mode_select_msg = "┋replace┋ has be choosen" #replace==symbolic placeholder
92 | request_share_link = "Pls input Shared_Link or Shared_ID"
93 | request_dst_target = "Please select the destination path for the task"
94 | is_cover = "Cover it"
95 | not_cover = "Cancel"
96 | is_cover_quick_msg = "QUICK\\_MODE The specified directory already exists\nDo you need to cover it?"
97 | insert_quick_success = "QUICK\\_MODE specified path is setted successfully"
98 | modify_quick_success = "QUICK\\_MODE specified path is renewed successfully"
99 | null_fav_quick = "QUICK\\_MODE directory is not set\nPlease try again after setting"
100 | null_fav = "COPY\\_MODE directory is not set\nPlease try again after setting"
101 | ready_to_task = "Now is Ready to Task..."
102 | doing = "Transferring"
103 | done = "Done"
104 | killed = "Killed"
105 | current_task_id = "task id : "
106 | task_start_time = "Task start time : "
107 | task_finished_time = "Task end time : "
108 | task_files_size = "Total file size : "
109 | task_files_num = "Task file num : "
110 | task_status = "task_status : "
111 | elapsed_time = "Elapsed Time : "
112 | cancel_msg = "Welcome to enjoy with iCopy again"
113 | get_quick_count_invaild = "Only ONE Dst ID can be specified under quick mode"
114 | get_multi_fav_error = "pls submit DST ID according '/set'"
115 | get_single_fav_error = "pls submit single DST ID according '/set rule'"
116 | get_multi_in_single = "pls use '/set' while modify multi DST IDs"
117 | task_src_info = "[Resource From]:"
118 | task_dst_info = "[Transfer To]:"
119 | is_set_err = "SET FAV ERROR"
120 | set_fav_success = "set Favorites success"
121 | delete_fav_success = "Favorites deleted successfully"
122 | delete_quick_success = "QUICK\\_MODE specified path is deleted successfully"
123 | show_fav_list = "The following are favorites that have been set"
124 | show_fav_list_null = "Favorites are not set"
125 | is_restarting = "iCopy is restarting...\nPlease start using in 30 seconds"
126 | is_killed_by_user = "Task is killed by user"
127 | is_current_task = "Current Task : \n\n"
128 | current_task_src_name = "Source : "
129 | current_task_dst_name = "Destination : "
130 | is_not_current_task = "No Task is in Processing"
131 | show_wait_list = "task in the queue\n\n"
132 | show_wait_list_null = "There is no task in waiting"
133 | interrupted = "Interrupted"
134 | is_interrupted_error = "Unexpected interruption"
135 | restart_success = "iCopy restart is complete"
136 | add_task_successful = "Add Task Successful"
137 | purge_fav = "purge Favorites (except Quick path)"
138 | reset_successful = "Task ID: replace has been reset successfully" #replace==symbolic placeholder
139 | over_limit_error = "Exceeded maximum resettable task number"
140 | over_limit_to_check = "Exceeded maximum task number which could be check"
141 | over_limit_to_dedupe = "Exceeded maximum task number which could be dedupe"
142 | global_command_error = "The input command or command format does not exist"
143 | ready_to_size = "Preparing statistics"
144 | sizing = "In statistics, pls wait \nIf there is a copy task, it will affect the accuracy of both copy and statistics"
145 | total_file_num = "Total Files : "
146 | total_file_size = "Total Size : "
147 | sizing_done = "Size Finished"
148 | task_is_in_queue = "Task is in the queue"
149 | finished_could_be_check = "You could only check the task which is finished"
150 | finished_could_be_dedupe = "You could only dedupe the task which is finished"
151 | support_error = "The specified task does not support queries"
152 | request_target_folder = "Pls choose the TARGET drive or folder"
153 | ready_to_purge = "Ready to Purge"
154 | 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"
155 | purging_done = "The recycle bin has been emptied"
156 | request_dedupe_mode = "Please select to dedupe mode rule"
157 | ready_to_dedupe = "Ready to dedupe"
158 | deduping = "In deduping task, pls wait \nIf there is a copy task, it will affect the accuracy of both copy and dedupe"
159 | deduping_done = "The task is be deduped"
160 | is_folder_not_drive = "The selected favorites is a 'folder' not a 'shared drive'\n Can not be purged"
161 |
162 | [jp]
163 | start = "Hi! replace . iCopyへようこそ" #replace==置き換え指示記号
164 | guide_to_menu = "実行モードを選択するには、'/menu' を入力してください"
165 | menu_msg = "ご覧のモードを選んでください"
166 | quick_mode = "「クイック」"
167 | copy_mode = "「カスタマイズ」"
168 | size_mode = "「サイズ」"
169 | purge_mode = "「ごみ箱を空にする」"
170 | dedupe_mode = "「重複を取り除く」"
171 | mode_select_msg = "┋replace┋が選択されました" #replace==置き換え指示記号
172 | request_share_link = "共有リンクまたは共有IDを入力してください"
173 | request_dst_target = "ファイルの保存場所を選択してください"
174 | is_cover = "カバー"
175 | not_cover = "キャンセル"
176 | is_cover_quick_msg = "QUICK_MODE指定されたフォルダーはすでに存在します\n新しいフォルダで上書きしますか?"
177 | insert_quick_success = "QUICK_MODEで指定されたフォルダーが正常に設定されました"
178 | modify_quick_success = "QUICK_MODEで指定されたフォルダーが更新されました"
179 | null_fav_quick = "QUICK\\_MODEフォルダーが設定されていません\n設定後にもう一度お試しください"
180 | null_fav = "COPY\\_MODEフォルダーが設定されていません\n設定後にもう一度お試しください"
181 | ready_to_task = "任務準備中..."
182 | doing = "タスク実行中"
183 | done = "任務成功"
184 | killed = "任務中止"
185 | current_task_id = "タスク番号 : "
186 | task_start_time = "タスク開始時刻 : "
187 | task_finished_time = "タスク終了時間 : "
188 | task_files_size = "ファイルサイズ : "
189 | task_files_num = "タスクファイル数 : "
190 | task_status = "タスク状態 : "
191 | elapsed_time = "合計時間 : "
192 | cancel_msg = "再びiCopyへようこそ"
193 | get_quick_count_invaild = "「クイックモード」で一つフォルダーしか指定されることができない"
194 | get_multi_fav_error = "'/set'にルールをよって、フォルダーのIDを入力してください"
195 | get_single_fav_error = "'/set rule'にルールをよって、フォルダーのIDを入力してください"
196 | get_multi_in_single = "複数のフォルダーを設定する場合は、「/set」コマンドを使用してください"
197 | task_src_info = "[任務目標]:"
198 | task_dst_info = "[保存場所]:"
199 | is_set_dedupe = "お気に入りを設定するとエラーが発生します"
200 | set_fav_success = "お気に入りを設定しました"
201 | delete_fav_success = "お気に入り削除に成功"
202 | delete_quick_success = "QUICK_MODEで指定されたフォルダーが削除されました"
203 | show_fav_list = "以下は既設のお気に入りである"
204 | show_fav_list_null = "お気に入りは設定いない"
205 | is_restarting = "iCopyが再起動しています...\n30秒後に使用を開始してください"
206 | is_killed_by_user = "タスクはユーザによって終了される"
207 | is_current_task = "進行中のタスク : \n\n"
208 | current_task_src_name = "源フォルダー : "
209 | current_task_dst_name = "目標フォルダー : "
210 | is_not_current_task = "進行中のタスクがない"
211 | show_wait_list = "タスクが待機しています\n\n"
212 | show_wait_list_null = "待機中のタスクはありません"
213 | interrupted = "タスク中断"
214 | is_interrupted_error = "予期しない中断"
215 | restart_success = "iCopyの再開が完了する"
216 | add_task_successful = "タスク追加成功"
217 | purge_fav = "お気に入りをクリア(クイックフォルダーを除く)"
218 | reset_successful = "タスク ID: replace がリセットされた"#replace==置き換え指示記号
219 | over_limit_error = "最大リセット可能タスク番号を超える"
220 | over_limit_to_check = "最大チェック可能タスク番号を超える"
221 | over_limit_to_dedupe = "最大重複除外可能タスク番号を超える"
222 | global_command_error = "入力するコマンドやコマンドフォーマットは存在しない"
223 | ready_to_size = "統計を準備している"
224 | sizing = "統計中ですので、お待ちください\nコピータスクが同時に存在する場合,コピーと統計の双方の正確さに影響する"
225 | total_file_num = "総 ファイル 数 : "
226 | total_file_size = "総 サイズ : "
227 | sizing_done = "統計終了"
228 | task_is_in_queue = "タスクが列に並んでいる"
229 | finished_could_be_check = "既に完了した任務しか調べられない"
230 | finished_could_be_dedupe = "すでに完了したタスクのみが重複ファイルの除去を行うことができる"
231 | support_error = "指定の任務の照会を支持しない"
232 | request_target_folder = "目標フォルダーを選んでください"
233 | ready_to_purge = "ごみ箱を空にする準備をしています"
234 | purging = "ごみ箱をクリアしています。\n同時にコピーの任務がある場合、コピーとごみ箱を空にすることの両方の正確さに影響します"
235 | purging_done = "既にごみ箱を空にした"
236 | request_dedupe_mode = "重複除外モードのルールを選択してください"
237 | ready_to_dedupe = "重複ファイルを除去する準備をしています"
238 | deduping = "重複ファイルを除去しています。\n同時にコピーの任務がある場合、コピーと重複除外の両方の正確さに影響します"
239 | deduping_done = "重複ファイルの除去が完了しました"
240 | is_folder_not_drive = "選択されたお気に入りは「フォルダ」ではなく「シェアドライバ」で\nゴミ箱を空にできない"
--------------------------------------------------------------------------------
/iCopy/docs/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # iCopy v0.2 CHANGELOG
2 |
3 | ## version 0.2.0-beta.6.7
4 |
5 | + Update :
6 | + ADD : renew requirements.txt.
7 | + ADD : "\_\_version\_\_"
8 |
9 | + Fixbugs :
10 | + Compatible with the old version of the database format
11 | + Fix floatify format error when the size is 0
12 |
13 | ## version 0.2.0-beta.6.6
14 |
15 | + Update :
16 | + Block "googleapiclient.discovery" warning prompt.
17 | + Deprecated "cache_discovery"
18 |
19 | ## version 0.2.0-beta.6.5
20 |
21 | + Fixbugs:
22 | + FIX : local variable assignment error
23 | + FIX : try to change restart function args
24 |
25 | ## version 0.2.0-beta.6.4
26 |
27 | + Fixbugs:
28 | + FIX : "/set" function "import as name" error
29 |
30 | ## version 0.2.0-beta.6.3
31 |
32 | + Update :
33 | + ADD : "/kill size","/kill purge","/kill dedupe" was added to terminate the execution of these tasks
34 |
35 | + Root Command:
36 |
37 | + start - nothing just say hello
38 | + menu - main entry point
39 | quick - quick mode
40 | copy - full mode
41 | set - customize settings
42 | task - task query
43 | reset - restore task
44 | size - just size task
45 | dedupe - dedupe drives and folders
46 | purge - delete files and folder in specified fav trash bin
47 | cancel - cancel TG conversation
48 | kill - kill task
49 | ver - check iCopy version
50 | restart - restart iCopy
51 |
52 | + Child Command:
53 |
54 | + set - customize settings
55 | ┖ set - batch way
56 | ┖ set rule - rules
57 | ┖ fav|quick +/- id - single way
58 | ┖ set purge - purge favorites
59 | + size - size query
60 | ┖ size - size the shared resource
61 | ┖ size id - size specified task
62 | ┖ size fav - size specified favorites
63 | + dedupe - dedupe drives and folders
64 | ┖ dedupe - dedupe specified favorites
65 | ┖ dedupe id - dedupe specified task
66 | + task - task query
67 | ┖ task - task in processing
68 | ┖ task list - future 10 tasks
69 | ┖ task id - show the specified task
70 | + reset - restore task
71 | ┖ reset - restore current task
72 | ┖ reset id - restore the specified task
73 | + kill - kill task
74 | ┖ kill - kill current transferring task
75 | ┖ kill task - kill current transferring task
76 | ┖ kill size - kill sizing task
77 | ┖ kill purge - kill purge task
78 | ┖ kill dedupe - kill dedupe task
79 |
80 | ## version 0.2.0-beta.6.2
81 |
82 | + Update :
83 | + ADD : Add "rmdir" operation to the "/Purge" function to clear the empty folder in the root directory.
84 |
85 | + Fixbugs :
86 | + FIX : "/dedupe" sendMsg error.
87 | + FIX : Insert DATABASE error while dedupe payload finished.
88 |
89 | ## version 0.2.0-beta.6.1
90 |
91 | + Update :
92 | + ADD : "/dedupe" Now you can choose favorites to dedupe
93 | + CHANGE : Move the stage tag uniformly to new file “utils/callback_stage.py"
94 |
95 | + Fixbugs :
96 | + Judge select favorites if is shared drive when you use "/purge" mode.
97 | + Separately define bot variables in asynchronous-process to prevent errors in connecting Telegram.
98 |
99 | + Root Command:
100 |
101 | + start - nothing just say hello
102 | + menu - main entry point
103 | quick - quick mode
104 | copy - full mode
105 | set - customize settings
106 | task - task query
107 | reset - restore task
108 | size - just size task
109 | dedupe - dedupe drives and folders
110 | purge - delete files and folder in specified fav trash bin
111 | cancel - cancel TG conversation
112 | kill - kill task which is in processing
113 | ver - check iCopy version
114 | restart - restart iCopy
115 |
116 | + Child Command:
117 |
118 | + set - customize settings
119 | ┖ set - batch way
120 | ┖ set rule - rules
121 | ┖ fav|quick +/- id - single way
122 | ┖ set purge - purge favorites
123 | + size - size query
124 | ┖ size - size the shared resource
125 | ┖ size id - size specified task
126 | ┖ size fav - size specified favorites
127 | + dedupe - dedupe drives and folders
128 | ┖ dedupe - dedupe specified favorites
129 | ┖ dedupe id - dedupe specified task via task id
130 | + task - task query
131 | ┖ task - task in processing
132 | ┖ task list - future 10 tasks
133 | ┖ task id - show the specified task
134 | + reset - restore task
135 | ┖ reset - restore current task
136 | ┖ reset id - restore the specified task
137 |
138 | ## version 0.2.0-beta.6
139 |
140 | + Update :
141 | + ADD : insert more details into Database and more initializated data
142 | + ADD : feedback dst endpoint link when task end normally
143 | + ADD : "/task id" only support the task which is start after v0.2.0b6
144 | + ADD : mark tasks that have been reset in the database
145 | + ADD : "/size id" & "/size fav"
146 | + ADD : "/purge" to empty shared drive trash bin
147 | + ADD : "/dedupe id" to dedupe task
148 | + ADD : Record the last time of size and dedupe
149 |
150 | + Fix :
151 | + FIX : Update RegEX Rules
152 |
153 | + Root Command:
154 |
155 | + start - nothing just say hello
156 | + menu - main entry point
157 | quick - quick mode
158 | copy - full mode
159 | set - customize settings
160 | task - task query
161 | reset - restore task
162 | size - just size task
163 | dedupe - dedupe specified task
164 | purge - delete files and folder in specified fav trash bin
165 | cancel - cancel TG conversation
166 | kill - kill task which is in processing
167 | ver - check iCopy version
168 | restart - restart iCopy
169 |
170 | + Child Command:
171 |
172 | + set - customize settings
173 | ┖ set - batch way
174 | ┖ set rule - rules
175 | ┖ fav|quick +/- id - single way
176 | ┖ set purge - purge favorites
177 | + size - size query
178 | ┖ size - size the shared resource
179 | ┖ size id - size specified task
180 | ┖ size fav - size specified favorites
181 | + task - task query
182 | ┖ task - task in processing
183 | ┖ task list - future 10 tasks
184 | ┖ task id - show the specified task
185 | + reset - restore task
186 | ┖ reset - restore current task
187 | ┖ reset id - restore the specified task
188 |
189 | ## version 0.2.0-beta.5.1
190 |
191 | + Update :
192 | + ADD : More task info into Database
193 |
194 | + Fixbugs :
195 | + FIX : delete "directly in" mode keyboard after selection is choosen
196 | + FIX : Purge local var after Conversation END
197 | The task will not be committed twice now
198 | + FIX : "/task list" error
199 | Now "/task list" will display up to 10 tasks pending
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 | cancel - cancel TG conversation
212 | kill - kill task which is in processing
213 | ver - check iCopy version
214 | restart - restart iCopy
215 |
216 | + Child Command:
217 |
218 | + set - customize settings
219 | ┖ set - batch way
220 | ┖ set rule - rules
221 | ┖ fav|quick +/- id - single way
222 | ┖ set purge - purge favorites
223 | + task - task query
224 | ┖ task - task in processing
225 | ┖ task list - future 10 tasks
226 | + reset - restore task
227 | ┖ reset - restore current task
228 | ┖ reset id - restore the specified task
229 |
230 | ## version 0.2.0-beta.5
231 |
232 | + Update :
233 | + ADD : directly input sharelink then choose mode.
234 |
235 | + Fixbugs :
236 | + FIX : get shared drive name failed when the shared drive is temporarily granted permission for an outside party.
237 | + FIX : Fix the error of repeated tasks when entering multiple tasks at the same time.
238 | + FIX : "reset" notice msg error
239 |
240 | ## version 0.2.0-beta.4.1
241 |
242 | + Fixbugs :
243 | + FIX : "/reset task_id" Database operation error
244 |
245 | ## version 0.2.0-beta.4
246 |
247 | + Update :
248 | + ADD "/size" a function to get simple size info
249 |
250 | + Fixbugs :
251 | + FIX : "/reset" send notice msg error
252 | + FIX : get shared drive name failed when the shared drive is temporarily granted permission for an outside party.
253 |
254 | ***
255 |
256 | ## version 0.2.0-beta.3
257 |
258 | Notice : The new "conf.toml" should be replaced or you could modify the "conf.toml" by referring to the "example" one.
259 |
260 | + Update :
261 | + ADD "/set purge"
262 | Allow to Purge Favorties Setting Now.
263 | this will not delete quick mode setting.
264 | + Now '--drive-server-side-across-configs' is Built in the iCopy. Remove from conf.toml
265 | + '--ignore-checksum' is write in conf.toml default
266 | + ADD "/reset" and "/reset id" command.
267 | You could restore task with the command
268 |
269 | ***
270 |
271 | ## version 0.2.0-beta.2
272 |
273 | Update : send confirm msg after task added
274 | Update : '/start' is not in Conversation Handle any more
275 | Update : Use '/menu' to select run mode instead of '/start'
276 |
277 | ***
278 |
279 | ## version 0.2.0-beta.1
280 |
281 | The first beta version of v0.2
282 | β1 is a relatively stable without bugs version
283 | The following Command is Supported
284 |
285 | + Root Command:
286 |
287 | + start - main entry point
288 | quick - quick mode
289 | copy - full mode
290 | set - customize settings
291 | task - task query
292 | cancel - cancel TG conversation
293 | kill - kill task which is in processing
294 | ver - check iCopy version
295 | restart - restart iCopy
296 |
297 | + Child Command:
298 |
299 | + set - ustomize settings
300 | ┖ set - batch way
301 | ┖ set rule - rules
302 | ┖ set fav|quick +/- id - single way
303 | task - task query
304 | ┖ task - task in processing
305 | ┖ task list - future 10 tasks
306 |
307 | ***
308 |
309 | ## version 0.2.0-alpha.1 ~ alpha.15
310 |
311 | iCopy rebuild basework finished
312 |
313 | ## version 0.1.7-beta.3
314 |
315 | Archived version
316 | ...
317 |
--------------------------------------------------------------------------------
/iCopy/docs/TODO.md:
--------------------------------------------------------------------------------
1 | # TODO
2 |
3 | + before beta6
4 | null & fixbugs
5 |
6 | + before beta7
7 | del fav keyboard
8 | choose fav keyboard
9 | del Task info in MongoDB
10 |
11 | -------
12 |
13 | + future
14 | modify task info
15 | Support vaild folder len[28,33,72]
16 | Web DASHBOARD
17 |
--------------------------------------------------------------------------------
/iCopy/docs/develop_update.md:
--------------------------------------------------------------------------------
1 | # DEVELOP UPDATE CHANGELOG
2 |
3 |
4 |
--------------------------------------------------------------------------------
/iCopy/drive/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/f0rx4/FClone-Bot/f73d648a4a3a5c727bbb79b98af5cf61fb7d2498/iCopy/drive/__init__.py
--------------------------------------------------------------------------------
/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/utils/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/f0rx4/FClone-Bot/f73d648a4a3a5c727bbb79b98af5cf61fb7d2498/iCopy/utils/__init__.py
--------------------------------------------------------------------------------
/iCopy/utils/__version__.py:
--------------------------------------------------------------------------------
1 | ### local version
2 | __version__ = "v0.2.0-beta.6.7"
--------------------------------------------------------------------------------
/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 |
21 | ) = range(13)
--------------------------------------------------------------------------------
/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 | sa_sleep_suffix = "--drive-pacer-min-sleep"
67 | sa_sleep = _cfg["general"]["min_sleep"]
68 |
69 | command = [
70 | cloner,
71 | option,
72 | mode_suffix,
73 | mode,
74 | src_block,
75 | checkers,
76 | transfers,
77 | sa_sleep_suffix,
78 | sa_sleep,
79 | ]
80 | command += flags
81 |
82 | dedupe_process(ns, command, dedu_mode, dedu_chat_id, dedu_message_id, dedu_task_id, dedu_link, dedu_id, dedu_name)
83 |
84 | ns.dedupe = 0
85 |
86 | def dedupe_process(ns, command, dedu_mode, dedu_chat_id, dedu_message_id, dedu_task_id, dedu_link, dedu_id, dedu_name):
87 | for output in dedupe_run(command):
88 | if ns.dedupe == 1:
89 | deduping_process.kill()
90 |
91 | if ns.dedupe == 0:
92 | last_dedupe_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
93 |
94 | if dedu_task_id == 0:
95 | fav_col.update_one(
96 | {"G_id": dedu_id}, {"$set": {"last_dedupe_time": last_dedupe_time,}},
97 | )
98 |
99 | deduped_msg = (
100 | " ༺ ✪iCopy✪ ༻ | "
101 | + "🏳️"
102 | + " FAVORITES"
103 | + "\n\n"
104 | + '{}'.format(dedu_link, dedu_name)
105 | + "\n["
106 | + dedu_id
107 | + "]\n\n"
108 | + _text[_lang]["deduping_done"]
109 | )
110 |
111 | else:
112 | task_list.update_one(
113 | {"_id": int(dedu_task_id)}, {"$set": {"last_dedupe_time": last_dedupe_time,}},
114 | )
115 |
116 | deduped_msg = (
117 | " ༺ ✪iCopy✪ ༻ | "
118 | + "🏳️" + _text[_lang]["current_task_id"]
119 | + str(dedu_task_id)
120 | + "\n\n"
121 | + '{}'.format(dedu_link, dedu_name)
122 | + "\n["
123 | + dedu_id
124 | + "]\n\n"
125 | + _text[_lang]["deduping_done"]
126 | )
127 |
128 | bot.edit_message_text(
129 | chat_id=dedu_chat_id,
130 | message_id=dedu_message_id,
131 | text=deduped_msg,
132 | parse_mode=ParseMode.HTML,
133 | disable_web_page_preview=True,
134 | )
135 | elif ns.dedupe == 1:
136 | bot.edit_message_text(
137 | chat_id=dedu_chat_id,
138 | message_id=dedu_message_id,
139 | text=_text[_lang]["is_killed_by_user"],
140 | )
141 |
142 |
--------------------------------------------------------------------------------
/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 |
35 | def cook_to_id(get_share_link):
36 | share_id_list = []
37 | unsupported_type = []
38 | share_id = ""
39 |
40 | share_link = get_share_link.strip().replace(" ", "").splitlines()
41 | for item in share_link:
42 | if "drive.google.com" in item:
43 | share_id = re.findall(regex1, item)
44 | if len(share_id) <= 33:
45 | share_id = "".join(share_id)
46 |
47 | share_id_list.append(share_id)
48 | else:
49 | unsupported_type.append({"type": "link", "value": item})
50 |
51 | else:
52 | if len(item) >= 11 and len(item) <= 33 and re.match(regex2, item):
53 | share_id_list.append(item)
54 | else:
55 | unsupported_type.append({"type": "id", "value": item})
56 |
57 | return share_id_list
58 |
59 |
60 | def get_name_from_id(update, taget_id, list_name):
61 | cook_list = list(list_name)
62 | if len(taget_id) >= 11 and len(taget_id) < 28:
63 | cook_list.append(
64 | {"G_type": "G_drive", "G_id": taget_id, "G_name": load.all_drive[taget_id],}
65 | )
66 | elif len(taget_id) in judge_folder_len:
67 | cook_list.append(
68 | {
69 | "G_type": "G_Folder",
70 | "G_id": taget_id,
71 | "G_name": _gd().file_get_name(file_id=taget_id),
72 | }
73 | )
74 | else:
75 | update.effective_message.reply_text(_msg.get_fav_len_invaild(_lang, taget_id))
76 |
77 | return ConversationHandler.END
78 |
79 | return cook_list
80 |
81 | def get_src_name_from_id(update, taget_id, list_name):
82 | cook_list = []
83 | cook_list = list(list_name)
84 | if len(taget_id) >= 11 and len(taget_id) < 28:
85 | target_info = _gd.drive_get(_gd(),drive_id=taget_id)
86 | cook_list.append(
87 | {"G_type": "G_drive", "G_id": taget_id, "G_name": target_info['name'],}
88 | )
89 | elif len(taget_id) in judge_folder_len:
90 | cook_list.append(
91 | {
92 | "G_type": "G_Folder",
93 | "G_id": taget_id,
94 | "G_name": _gd().file_get_name(file_id=taget_id),
95 | }
96 | )
97 | else:
98 | update.effective_message.reply_text(_msg.get_fav_len_invaild(_lang, taget_id))
99 |
100 | return ConversationHandler.END
101 |
102 | return cook_list
103 |
104 |
105 | def insert_to_db_quick(pick_quick, update):
106 | is_quick = {"_id": "fav_quick"}
107 | is_quick_cur = load.fav_col.find(is_quick)
108 | if list(is_quick_cur) == []:
109 | for item in pick_quick:
110 | item["_id"] = "fav_quick"
111 | load.fav_col.insert_one(item)
112 |
113 | update.effective_message.reply_text(
114 | _text[_lang]["insert_quick_success"], parse_mode=ParseMode.MARKDOWN_V2
115 | )
116 |
117 | return ConversationHandler.END
118 |
119 | else:
120 | status = "is_cover"
121 |
122 | return status
123 |
124 |
125 | def modify_quick_in_db(update, context):
126 | pick_quick = _set.pick_quick
127 | for item in pick_quick:
128 | load.fav_col.update({"_id": "fav_quick"}, item, upsert=True)
129 |
130 | update.effective_message.reply_text(
131 | _text[_lang]["modify_quick_success"], parse_mode=ParseMode.MARKDOWN_V2
132 | )
133 |
134 | return ConversationHandler.END
135 |
136 |
137 | def delete_in_db_quick():
138 | load.fav_col.delete_one({"_id": "fav_quick"})
139 |
140 | return
141 |
142 |
143 | def delete_in_db(delete_request):
144 | load.fav_col.delete_one(delete_request)
145 |
146 | return
147 |
148 |
149 | def get_share_link(update, context):
150 | get_share_link = update.effective_message.text
151 | tmp_src_name_list = ""
152 | tmp_task_list = []
153 | src_name_list = []
154 | src_id_list = cook_to_id(get_share_link)
155 | is_quick = {"_id": "fav_quick"}
156 | is_quick_cur = load.fav_col.find(is_quick)
157 | is_dstinfo = _copy.current_dst_info
158 |
159 | if is_dstinfo != "":
160 | dstinfo = is_dstinfo.split("id+name")
161 | dst_id = dstinfo[0]
162 | dst_name = dstinfo[1]
163 | else:
164 | for doc in is_quick_cur:
165 | dst_id = doc["G_id"]
166 | dst_name = doc["G_name"]
167 |
168 | for item in src_id_list:
169 | src_name_list += get_src_name_from_id(update, item, list_name=tmp_src_name_list)
170 | tmp_src_name_list = ""
171 |
172 | for item in src_name_list:
173 | src_id = item["G_id"]
174 | src_name = item["G_name"]
175 |
176 | tmp_task_list.append(
177 | {
178 | "mode_type": mode,
179 | "src_id": src_id,
180 | "src_name": src_name,
181 | "dst_id": dst_id,
182 | "dst_name": dst_name,
183 | "chat_id": update.message.chat_id,
184 | "raw_message_id": update.message.message_id,
185 | }
186 | )
187 |
188 | Thread(target=_box.cook_task_to_db, args=(update, context, tmp_task_list)).start()
189 | _copy.current_dst_info = ""
190 | return ConversationHandler.END
191 |
192 | def taskill(update, context):
193 | entry_cmd = update.effective_message.text
194 | if "/kill" == entry_cmd :
195 | ns.x = 1
196 |
197 | elif context.args[0] == "task":
198 | ns.x = 1
199 |
200 | elif context.args[0] == "size":
201 | ns.size = 1
202 |
203 | elif context.args[0] == "purge":
204 | ns.purge = 1
205 |
206 | elif context.arg[0] == "dedupe":
207 | ns.dedupe = 1
208 |
209 | else:
210 | update.effective_message.reply_text(_text[_lang]["global_command_error"])
211 |
212 | def check_restart(bot):
213 | check_restart = load.db_counters.find_one({"_id": "is_restart"})
214 | chat_id = check_restart["chat_id"]
215 | message_id = check_restart["message_id"]
216 | load.db_counters.update_one({"_id": "is_restart"}, {"$set": {"status": 0,}}, True)
217 | bot.edit_message_text(
218 | chat_id=chat_id, message_id=message_id, text=_text[_lang]["restart_success"]
219 | )
220 |
221 | def _version(update, context):
222 | update.message.reply_text(
223 | "Welcome to use iCopy Telegram BOT\n\n"
224 | "Current Version : " + __version__.__version__ + "\n\n"
225 | f"Latest Version : {_get_ver()}"
226 | )
227 |
228 | def _get_ver():
229 | _url = "https://api.github.com/repos/fxxkrlab/iCopy/releases"
230 | _r_ver = requests.get(_url).json()
231 | _latest_ver = _r_ver[0]["tag_name"]
232 | return _latest_ver
233 |
234 | @_r.restricted
235 | def cancel(update, context):
236 | user = update.effective_user.first_name
237 | logger.info("User %s canceled the conversation.", user)
238 | update.effective_message.reply_text(
239 | f"Bye! {update.effective_user.first_name} ," + _text[_lang]["cancel_msg"]
240 | )
241 | return ConversationHandler.END
242 |
243 | def error(update, context):
244 | """Log Errors caused by Updates."""
245 | logger.warning('Update "%s" caused error "%s"', update, context.error)
246 |
--------------------------------------------------------------------------------
/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 | if "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 |
80 | if "/setlist" == entry_cmd:
81 | global showitem
82 | global showlist
83 | fav_count = load.db_counters.find_one({"_id": "fav_count_list"})
84 | fav_list = load.fav_col.find({"fav_type": "fav"})
85 | if fav_count is not None:
86 | if fav_count["fav_sum"] == 0:
87 | update.effective_message.reply_text(_text[_lang]["show_fav_list_null"])
88 |
89 | return ConversationHandler.END
90 |
91 | elif fav_count["fav_sum"] != 0:
92 | for item in fav_list:
93 | showitem = (
94 | "type : "
95 | + item["G_type"]
96 | + " | name : "
97 | + item["G_name"]
98 | + "\nid : "
99 | + item["G_id"]
100 | + "\n"
101 | + "--------------------\n"
102 | )
103 | showlist.append(showitem)
104 |
105 | showlist = "".join(showlist)
106 |
107 | update.effective_message.reply_text(
108 | _text[_lang]["show_fav_list"] + "\n\n" + showlist
109 | )
110 |
111 | showlist = []
112 |
113 | return ConversationHandler.END
114 |
115 | else:
116 | update.effective_message.reply_text(_text[_lang]["show_fav_list_null"])
117 |
118 | return ConversationHandler.END
119 |
120 | ### set single DST ID ###
121 | elif "quick" or "fav" in entry_cmd:
122 | ### single quick (drive or folder)
123 | if len(entry_cmd.splitlines()) == 1:
124 | each = entry_cmd[4:]
125 | if "quick" == each[:5]:
126 | if "quick+" == each[:6]:
127 | global pick_quick
128 | pick_quick = _func.get_name_from_id(
129 | update, each[6:], list_name=pick_quick
130 | )
131 | insert_fav_quick = _func.insert_to_db_quick(pick_quick, update)
132 | if insert_fav_quick == "is_cover":
133 | update.effective_message.reply_text(
134 | _text[_lang]["is_cover_quick_msg"],
135 | parse_mode=ParseMode.MARKDOWN_V2,
136 | reply_markup=_KB.is_cover_keyboard(),
137 | )
138 |
139 | return _stage.IS_COVER_QUICK
140 |
141 | elif "quick-" == each[:6]:
142 | _func.delete_in_db_quick
143 | update.effective_message.reply_text(
144 | _text[_lang]["delete_quick_success"]
145 | )
146 |
147 | ### set fav folder(fav folder could be a drive or folder of GDrive)
148 | elif "fav" == each[:3]:
149 | fav_count = load.db_counters.find_one({"_id": "fav_count_list"})
150 | fav_sum = 0
151 |
152 | if fav_count != None:
153 | fav_sum = fav_count["fav_sum"]
154 |
155 | if "+" == each[3]:
156 | global pick_fav
157 | pick_fav = _func.get_name_from_id(
158 | update, each[4:], list_name=pick_fav
159 | )
160 | for item in pick_fav:
161 | item["fav_type"] = "fav"
162 | try:
163 | load.fav_col.insert_one(item)
164 | except:
165 | update.effective_message.reply_text(
166 | _text[_lang]["is_set_err"],
167 | )
168 | else:
169 | fav_sum += 1
170 | load.db_counters.update(
171 | {"_id": "fav_count_list"},
172 | {"fav_sum": fav_sum},
173 | upsert=True,
174 | )
175 |
176 | update.effective_message.reply_text(_text[_lang]["set_fav_success"])
177 |
178 | pick_fav = []
179 |
180 | if "-" == each[3]:
181 | global unpick_fav
182 | unpick_fav.append(each[4:])
183 | for item in unpick_fav:
184 | delete_request = {"G_id": item}
185 | _func.delete_in_db(delete_request)
186 | fav_count = load.fav_col.find({"fav_type": "fav"})
187 | fav_sum = len(list(fav_count))
188 | load.db_counters.update(
189 | {"_id": "fav_count_list"}, {"fav_sum": fav_sum}, upsert=True
190 | )
191 |
192 | update.effective_message.reply_text(
193 | _text[_lang]["delete_fav_success"]
194 | )
195 |
196 | unpick_fav = []
197 |
198 | ### single rule
199 | elif "rule" == entry_cmd[4:8]:
200 | update.effective_message.reply_text(
201 | _msg.set_single_fav_guide(_lang), parse_mode=ParseMode.MARKDOWN_V2
202 | )
203 |
204 | return ConversationHandler.END
205 |
206 | else:
207 | update.effective_message.reply_text(
208 | _text[_lang]["get_single_fav_error"],
209 | parse_mode=ParseMode.MARKDOWN_V2,
210 | )
211 |
212 | return ConversationHandler.END
213 |
214 | return ConversationHandler.END
215 |
216 | else:
217 | update.effective_message.reply_text(
218 | _text[_lang]["get_multi_in_single"], parse_mode=ParseMode.MARKDOWN_V2
219 | )
220 |
221 | return ConversationHandler.END
222 |
223 | else:
224 | update.effective_message.reply_text(
225 | _msg.set_help(_lang), parse_mode=ParseMode.MARKDOWN_V2
226 | )
227 | return ConversationHandler.END
228 |
229 |
230 | ### set multi DST ID ###
231 | def _multi_settings_recieved(update, context):
232 | _tmp_quick_counter = 0
233 | fav_msg = update.effective_message.text
234 | fav_msg = fav_msg.replace(" ", "").splitlines()
235 | global pick_quick
236 | for each in fav_msg:
237 | print(each)
238 | ### modify quick DST
239 | if "quick+" == each[:6]:
240 | _tmp_quick_counter += 1
241 | if _tmp_quick_counter == 1:
242 | global pick_quick
243 | pick_quick += _func.get_name_from_id(
244 | update, each[6:], list_name=pick_quick
245 | )
246 | insert_fav_quick = _func.insert_to_db_quick(pick_quick, update)
247 | if insert_fav_quick == "error":
248 | update.effective_message.reply_text(
249 | _text[_lang]["is_cover_quick_msg"],
250 | parse_mode=ParseMode.MARKDOWN_V2,
251 | reply_markup=_KB.is_cover_keyboard(),
252 | )
253 |
254 | return _stage.IS_COVER_QUICK
255 |
256 | elif _tmp_quick_counter < 1:
257 | pass
258 | elif _tmp_quick_counter > 1:
259 | print("error!")
260 | update.effective_message.reply_text(
261 | _text[_lang]["get_quick_count_invaild"]
262 | )
263 | elif "quick-" == each[:6]:
264 | _func.delete_in_db_quick
265 | update.effective_message.reply_text(_text[_lang]["delete_quick_success"])
266 |
267 | ### set fav folder(fav folder could be a drive or folder of GDrive)
268 |
269 | elif "fav" == each[:3]:
270 | fav_count = load.db_counters.find_one({"_id": "fav_count_list"})
271 | fav_sum = 0
272 |
273 | if fav_count != None:
274 | fav_sum = fav_count["fav_sum"]
275 |
276 | if "+" == each[3]:
277 | global pick_fav
278 | pick_fav += _func.get_name_from_id(update, each[4:], list_name=pick_fav)
279 | for item in pick_fav:
280 | item["fav_type"] = "fav"
281 | try:
282 | load.fav_col.insert_one(item)
283 | except:
284 | update.effective_message.reply_text(_text[_lang]["is_set_err"],)
285 | else:
286 | fav_sum += 1
287 | load.db_counters.update(
288 | {"_id": "fav_count_list"}, {"fav_sum": fav_sum}, upsert=True
289 | )
290 |
291 | update.effective_message.reply_text(_text[_lang]["set_fav_success"])
292 | pick_fav = []
293 |
294 | if "-" == each[3]:
295 | global unpick_fav
296 | unpick_fav.append(each[4:])
297 | for item in unpick_fav:
298 | delete_request = {"G_id": item}
299 | _func.delete_in_db(delete_request)
300 | fav_count = load.fav_col.find({"fav_type": "fav"})
301 | fav_sum = len(list(fav_count))
302 | load.db_counters.update(
303 | {"_id": "fav_count_list"}, {"fav_sum": fav_sum}, upsert=True
304 | )
305 |
306 | update.effective_message.reply_text(_text[_lang]["delete_fav_success"])
307 |
308 | unpick_fav = []
309 |
310 | else:
311 | if "/cancel" == update.effective_message.text:
312 |
313 | return _func.cancel(update, context)
314 | else:
315 | update.effective_message.reply_text(_text[_lang]["get_multi_fav_error"])
316 |
317 | return ConversationHandler.END
318 |
--------------------------------------------------------------------------------
/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/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 |
40 | ### drive().list
41 | all_drive = gdrive.GoogleDrive().drive_list()
42 |
43 | ### ns
44 | manager = Manager()
45 | ns = manager.Namespace()
46 |
47 | ### Restore Unexpected Interrupted Task status 2 --> 0
48 | task_list.update_one(
49 | {"status": 2}, {"$set": {"status": 0,}},
50 | )
51 |
52 | ### regex entry pattern
53 | 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.\-_]+)&?)"
54 |
55 | ### define bot
56 | request = TGRequest(con_pool_size=8)
57 | bot = Bot(token=f"{cfg['tg']['token']}", request=request)
--------------------------------------------------------------------------------
/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/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/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 = "rmdir"
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 | sa_sleep = "--drive-pacer-min-sleep=" + f"{_cfg['general']['min_sleep']}"
57 |
58 | command1 = [cloner, option1, src_block, rmdirs, checkers, transfers, sa_sleep]
59 | command1 += flags
60 |
61 | command2 = [cloner, option2, src_block, checkers, transfers, sa_sleep]
62 | command2 += flags
63 |
64 | purge_process(ns, command1, command2, purge_chat_id, purge_message_id, fav_id, fav_name)
65 |
66 | ns.purge = 0
67 |
68 | def purge_process(ns, command1, command2, purge_chat_id, purge_message_id, fav_id, fav_name):
69 | for output in purge_run(command=command1):
70 | if ns.purge == 1:
71 | purging_process.kill()
72 |
73 | for output in purge_run(command=command2):
74 | if ns.purge == 1:
75 | purging_process.kill()
76 |
77 | if ns.purge == 0:
78 | last_purge_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
79 |
80 | fav_col.update_one(
81 | {"G_id": fav_id}, {"$set": {"last_purge_time": last_purge_time,}},
82 | )
83 |
84 | purged_msg = (
85 | "༺ ✪iCopy✪ ༻\n\n"
86 | + fav_name
87 | + "\n["
88 | + fav_id
89 | + "]\n\n"
90 | + _text[_lang]["purging_done"]
91 | )
92 |
93 | bot.edit_message_text(
94 | chat_id=purge_chat_id, message_id=purge_message_id, text=purged_msg,
95 | )
96 |
97 | if ns.purge == 1:
98 | bot.edit_message_text(
99 | chat_id=purge_chat_id,
100 | message_id=purge_message_id,
101 | text=_text[_lang]["is_killed_by_user"],
102 | )
103 |
--------------------------------------------------------------------------------
/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/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 | sa_sleep = "--drive-pacer-min-sleep=" + f"{_cfg['general']['min_sleep']}"
60 |
61 | command = [cloner, option, src_block, checkers, sa_sleep]
62 | command += flags
63 |
64 | simple_size_process(ns,command, size_chat_id, size_message_id, share_name_list)
65 |
66 | ns.size = 0
67 |
68 |
69 | def simple_size_process(ns,command, size_chat_id, size_message_id, share_name_list):
70 | for output in simpe_size_run(command):
71 | if ns.size == 1:
72 | simple_sizing.kill()
73 |
74 | regex_total_object = r"^Total objects:"
75 | regex_total_size = r"Total size:"
76 | if output:
77 | size_total_object = re.findall(regex_total_object, output)
78 | size_total_size = re.findall(regex_total_size, output)
79 |
80 | global size_object
81 | global size_size
82 |
83 | if size_total_object:
84 | size_object = output[15:]
85 | if size_total_size:
86 | size_size_all = output[12:]
87 | size_size = size_size_all.split("(")
88 |
89 | if ns.size == 0:
90 | for item in share_name_list:
91 | item_type = item["G_type"]
92 | item_id = item["G_id"]
93 | item_name = item["G_name"]
94 |
95 | global size_info
96 | size_info = (
97 | " ༺ ✪iCopy✪ ༻ ["
98 | + _text[_lang]["sizing_done"]
99 | + "]\n\n"
100 | + item_type
101 | + " : "
102 | + item_name
103 | + "\n"
104 | + item_id
105 | + "\n"
106 | + "--------------------\n"
107 | + _text[_lang]["total_file_num"]
108 | + str(size_object)
109 | + "\n"
110 | + _text[_lang]["total_file_size"]
111 | + str(size_size[0])
112 | + "\n("
113 | + str(size_size[1])
114 | )
115 |
116 | bot.edit_message_text(
117 | chat_id=size_chat_id, message_id=size_message_id, text=size_info
118 | )
119 |
120 | if ns.size == 1:
121 | bot.edit_message_text(
122 | chat_id=size_chat_id,
123 | message_id=size_message_id,
124 | text=_text[_lang]["is_killed_by_user"],
125 | )
126 |
127 | def owner_size(
128 | ns, size_chat_id, size_message_id, task_id, task_link, endpoint_id, endpoint_name
129 | ):
130 | cloner = _cfg["general"]["cloner"]
131 | option = "size"
132 | remote = _cfg["general"]["remote"]
133 | src_id = endpoint_id
134 | src_block = remote + ":" + "{" + src_id + "}"
135 | checkers = "--checkers=" + f"{_cfg['general']['parallel_c']}"
136 | flags = ["--size-only"]
137 | sa_sleep = "--drive-pacer-min-sleep=" + f"{_cfg['general']['min_sleep']}"
138 |
139 | command = [cloner, option, src_block, checkers, sa_sleep]
140 | command += flags
141 |
142 | owner_size_process(
143 | ns,
144 | command,
145 | size_chat_id,
146 | size_message_id,
147 | task_id,
148 | task_link,
149 | endpoint_id,
150 | endpoint_name,
151 | )
152 |
153 | ns.size = 0
154 |
155 |
156 | def owner_size_process(
157 | ns,
158 | command,
159 | size_chat_id,
160 | size_message_id,
161 | task_id,
162 | task_link,
163 | endpoint_id,
164 | endpoint_name,
165 | ):
166 | for output in simpe_size_run(command):
167 | if ns.size == 1:
168 | simple_sizing.kill()
169 |
170 | regex_total_object = r"^Total objects:"
171 | regex_total_size = r"Total size:"
172 | if output:
173 | size_total_object = re.findall(regex_total_object, output)
174 | size_total_size = re.findall(regex_total_size, output)
175 |
176 | global size_object
177 | global size_size
178 |
179 | if size_total_object:
180 | size_object = output[15:]
181 | if size_total_size:
182 | size_size_all = output[12:]
183 | size_size = size_size_all.split("(")
184 |
185 | if ns.size == 0:
186 | size_size_re = r"([\d.]+[\s]?)([kMGTP]?Bytes)"
187 | get_size_size = re.search(size_size_re, str(size_size[0]))
188 | if get_size_size:
189 | size_size_num = get_size_size.group(1).strip()
190 | size_size_tail = get_size_size.group(2).strip()
191 |
192 | if task_id == 0:
193 | size_info = (
194 | " ༺ ✪iCopy✪ ༻ | favorites | ["
195 | + _text[_lang]["sizing_done"]
196 | + "]\n\n"
197 | + '{}'.format(task_link, endpoint_name)
198 | + "\n"
199 | + "--------------------\n"
200 | + _text[_lang]["total_file_num"]
201 | + str(size_object)
202 | + "\n"
203 | + _text[_lang]["total_file_size"]
204 | + str(size_size[0])
205 | + "\n("
206 | + str(size_size[1])
207 | )
208 |
209 | fav_col.update_one(
210 | {"G_id": endpoint_id},
211 | {
212 | "$set": {
213 | "fav_object": int(size_object),
214 | "fav_size": float(size_size_num),
215 | "fav_size_tail": size_size_tail,
216 | },
217 | },
218 | upsert=True,
219 | )
220 |
221 | else:
222 | size_info = (
223 | " ༺ ✪iCopy✪ ༻ | "
224 | + "🏳️"
225 | + _text[_lang]["current_task_id"]
226 | + str(task_id)
227 | + " | ["
228 | + _text[_lang]["sizing_done"]
229 | + "]\n\n"
230 | + '{}'.format(task_link, endpoint_name)
231 | + "\n"
232 | + "--------------------\n"
233 | + _text[_lang]["total_file_num"]
234 | + str(size_object)
235 | + "\n"
236 | + _text[_lang]["total_file_size"]
237 | + str(size_size[0])
238 | + "\n("
239 | + str(size_size[1])
240 | )
241 |
242 | task_list.update_one(
243 | {"_id": int(task_id)},
244 | {
245 | "$set": {
246 | "task_current_prog_num": int(size_object),
247 | "task_total_prog_num": int(size_object),
248 | "task_current_prog_size": float(size_size_num),
249 | "task_total_prog_size": float(size_size_num),
250 | "task_current_prog_size_tail": size_size_tail[:1],
251 | "task_total_prog_size_tail": size_size_tail,
252 | }
253 | },
254 | )
255 |
256 | bot.edit_message_text(
257 | chat_id=size_chat_id,
258 | message_id=size_message_id,
259 | text=size_info,
260 | parse_mode=ParseMode.HTML,
261 | disable_web_page_preview=True,
262 | )
263 |
264 | elif ns.size == 1:
265 | bot.edit_message_text(
266 | chat_id=size_chat_id,
267 | message_id=size_message_id,
268 | text=_text[_lang]["is_killed_by_user"],
269 | )
270 |
--------------------------------------------------------------------------------
/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/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"]
49 | else:
50 | flags = ["--drive-server-side-across-configs"]
51 | command = []
52 |
53 | cloner = _cfg["general"]["cloner"]
54 | option = _cfg["general"]["option"]
55 | remote = _cfg["general"]["remote"]
56 | src_id = task["src_id"]
57 | src_name = task["src_name"]
58 | if "/" in src_name:
59 | src_name = src_name.replace("/", "|")
60 | if "'" in src_name:
61 | src_name = src_name.replace("'", "")
62 | if '"' in src_name:
63 | src_name = src_name.replace('"', "")
64 |
65 | dst_id = task["dst_id"]
66 | src_block = remote + ":" + "{" + src_id + "}"
67 | dst_block = remote + ":" + "{" + dst_id + "}" + "/" + src_name
68 | checkers = "--checkers=" + f"{_cfg['general']['parallel_c']}"
69 | transfers = "--transfers=" + f"{_cfg['general']['parallel_t']}"
70 | sa_sleep = "--drive-pacer-min-sleep=" + f"{_cfg['general']['min_sleep']}"
71 |
72 | flags += _cfg["general"]["run_args"]
73 | flags += [checkers, transfers, sa_sleep]
74 |
75 | command = [cloner, option, src_block, dst_block]
76 |
77 | command += flags
78 |
79 | chat_id = task["chat_id"]
80 |
81 | task_process(chat_id, command, task, ns)
82 |
83 | ns.x = 0
84 |
85 | flags = []
86 |
87 | global old_working_line
88 | global current_working_line
89 | old_working_line = 0
90 | current_working_line = 0
91 | time.sleep(3)
92 |
93 | time.sleep(5)
94 |
95 |
96 | def task_process(chat_id, command, task, ns):
97 | # mark is in processing in db
98 | task_list.update_one({"_id": task["_id"]}, {"$set": {"status": 2,}})
99 | db_counters.update({"_id": "last_task"}, {"task_id": task["_id"]}, upsert=True)
100 | chat_id = chat_id
101 | message = bot.send_message(chat_id=chat_id, text=_text[_lang]["ready_to_task"])
102 | message_id = message.message_id
103 |
104 | interval = 0.1
105 | timeout = 60
106 | xtime = 0
107 | old_working_line = 0
108 | current_working_line = 0
109 | task_current_prog_num = 0
110 | task_total_prog_num = 0
111 | task_percent = 0
112 | task_current_prog_size = "0"
113 | task_total_prog_size = "0 Bytes"
114 | task_in_size_speed = "-"
115 | task_in_file_speed = "-"
116 | task_eta_in_file = "-"
117 | task_current_prog_size_tail = ""
118 | task_total_prog_size_tail = ""
119 | start_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
120 |
121 | for toutput in run(command):
122 | if ns.x == 1:
123 | global icopyprocess
124 | icopyprocess.kill()
125 |
126 | regex_working_file = r"^ * "
127 | regex_elapsed_time = r"^Elapsed time:"
128 | regex_total_files = (
129 | r"Transferred:\s+(\d+) / (\d+), (\d+)%(?:,\s*([\d.]+\sFiles/s))?"
130 | )
131 | regex_total_size = (
132 | r"Transferred:[\s]+([\d.]+\s*)([kMGTP]?) / ([\d.]+[\s]?)([kMGTP]?Bytes),"
133 | r"\s*(?:\-|(\d+)\%),\s*([\d.]+\s*[kMGTP]?Bytes/s),\s*ETA\s*([\-0-9hmsdwy]+)"
134 | )
135 |
136 | output = toutput
137 |
138 | if output:
139 | task_total_files = re.search(regex_total_files, output)
140 | task_total_size = re.search(regex_total_size, output)
141 | task_elapsed_time = re.findall(regex_elapsed_time, output)
142 | task_working_file = re.findall(regex_working_file, output)
143 |
144 | if task_total_files:
145 | task_current_prog_num = task_total_files.group(1)
146 | task_total_prog_num = task_total_files.group(2)
147 | task_percent = int(task_total_files.group(3))
148 | task_in_file_speed = task_total_files.group(4)
149 |
150 | if task_total_size:
151 | task_current_prog_size = task_total_size.group(1).strip()
152 | task_current_prog_size_tail = task_total_size.group(2)
153 | task_total_prog_size = task_total_size.group(3).strip()
154 | task_total_prog_size_tail = task_total_size.group(4)
155 | task_in_size_speed = task_total_size.group(6)
156 | task_eta_in_file = task_total_size.group(7)
157 |
158 | if task_elapsed_time:
159 | global now_elapsed_time
160 | now_elapsed_time = output.replace(" ", "").split(":")[1]
161 |
162 | if task_working_file:
163 | global current_working_file
164 | current_working_line += 1
165 | current_working_file = (
166 | output.lstrip("* ").rsplit(":")[0].rstrip("Transferred")
167 | )
168 |
169 | global prog_bar
170 | prog_bar = _bar.status(0)
171 | if task_percent != 0:
172 | prog_bar = _bar.status(task_percent)
173 |
174 | global message_info
175 | message_info = (
176 | _text[_lang]["task_src_info"]
177 | + "\n"
178 | + "📃"
179 | + task["src_name"]
180 | + "\n"
181 | + "----------------------------------------"
182 | + "\n"
183 | + _text[_lang]["task_dst_info"]
184 | + "\n"
185 | + "📁"
186 | + task["dst_name"]
187 | + ":"
188 | + "\n"
189 | + " ┕─📃"
190 | + task["src_name"]
191 | + "\n"
192 | + "----------------------------------------"
193 | + "\n\n"
194 | + _text[_lang]["task_start_time"]
195 | + start_time
196 | + "\n\n"
197 | + _text[_lang]["task_files_size"]
198 | + str(task_current_prog_size)
199 | + task_current_prog_size_tail
200 | + "/"
201 | + str(task_total_prog_size)
202 | + task_total_prog_size_tail
203 | + "\n"
204 | + _text[_lang]["task_files_num"]
205 | + str(task_current_prog_num)
206 | + "/"
207 | + str(task_total_prog_num)
208 | + "\n"
209 | + _text[_lang]["task_status"]
210 | + "\n\n"
211 | + str(task_in_size_speed)
212 | + " | "
213 | + str(task_in_file_speed)
214 | + "\n\n"
215 | + str(task_percent)
216 | + "%"
217 | + str(prog_bar)
218 | )
219 |
220 | if (
221 | int(time.time()) - xtime > interval
222 | and old_working_line != current_working_line
223 | ):
224 | Timer(
225 | 0,
226 | task_message_box,
227 | args=(
228 | bot,
229 | chat_id,
230 | message_id,
231 | " ༺ ✪iCopy✪ ༻ \n"
232 | + _text[_lang]["doing"]
233 | + " | "
234 | + "🏳️"
235 | + _text[_lang]["current_task_id"]
236 | + str(task["_id"])
237 | + "\n\n"
238 | + message_info
239 | + "\n\n"
240 | + current_working_file[:30]
241 | + "\n"
242 | + "ETA : "
243 | + str(task_eta_in_file),
244 | ),
245 | ).start()
246 | old_working_line = current_working_line
247 | global old_working_file
248 | old_working_file = current_working_file
249 | time.sleep(3.5)
250 | xtime = time.time()
251 |
252 | if (
253 | int(time.time()) - xtime > timeout
254 | and current_working_file == old_working_file
255 | and task_percent > 5
256 | ):
257 | global interruption
258 | interruption = 1
259 | break
260 |
261 | old_working_file = ""
262 | finished_time = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
263 |
264 | dst_endpoint_id = _gd.get_dst_endpoint_id(_gd(), dst_id, src_name)
265 | if dst_endpoint_id:
266 | dst_endpoint_link = r"https://drive.google.com/open?id={}".format(
267 | dst_endpoint_id["id"]
268 | )
269 |
270 | if ns.x == 0:
271 | time.sleep(5)
272 | prog_bar = _bar.status(100)
273 | bot.edit_message_text(
274 | chat_id=chat_id,
275 | message_id=message_id,
276 | text=" ༺ ✪iCopy✪ ༻ \n"
277 | + _text[_lang]["done"]
278 | + " | "
279 | + "🏳️"
280 | + _text[_lang]["current_task_id"]
281 | + str(task["_id"])
282 | + "\n\n"
283 | + message_info.replace(
284 | " ┕─📃" + task["src_name"],
285 | " ┕─📃"
286 | + '{}'.format(dst_endpoint_link, task["src_name"]),
287 | )
288 | + "\n"
289 | + _text[_lang]["task_finished_time"]
290 | + finished_time
291 | + "\n"
292 | + _text[_lang]["elapsed_time"]
293 | + str(now_elapsed_time),
294 | parse_mode=ParseMode.HTML,
295 | disable_web_page_preview=True,
296 | )
297 | check_is_reset = task_list.find_one({"_id": task["_id"]})
298 | if check_is_reset['is_reset'] == 0:
299 | if task_total_prog_size != 0 and task_current_prog_size != 0:
300 | task_list.update_one(
301 | {"_id": task["_id"]},
302 | {
303 | "$set": {
304 | "status": 1,
305 | "start_time": start_time,
306 | "finished_time": finished_time,
307 | "task_current_prog_num": int(task_current_prog_num),
308 | "task_total_prog_num": int(task_total_prog_num),
309 | "task_current_prog_size": float(task_current_prog_size),
310 | "task_total_prog_size": float(task_total_prog_size),
311 | "task_current_prog_size_tail": task_current_prog_size_tail,
312 | "task_total_prog_size_tail": task_total_prog_size_tail,
313 | "dst_endpoint_link": dst_endpoint_link,
314 | "dst_endpoint_id": dst_endpoint_id["id"],
315 | }
316 | },
317 | )
318 | else:
319 | task_list.update_one(
320 | {"_id": task["_id"]},
321 | {
322 | "$set": {
323 | "status": 1,
324 | "start_time": start_time,
325 | "finished_time": finished_time,
326 | "task_current_prog_num": int(task_current_prog_num),
327 | "task_total_prog_num": int(task_total_prog_num),
328 | "task_current_prog_size": int(task_current_prog_size),
329 | "task_total_prog_size": int(task_total_prog_size),
330 | "task_current_prog_size_tail": task_current_prog_size_tail,
331 | "task_total_prog_size_tail": task_total_prog_size_tail,
332 | "dst_endpoint_link": dst_endpoint_link,
333 | "dst_endpoint_id": dst_endpoint_id["id"],
334 | }
335 | },
336 | )
337 |
338 | elif check_is_reset['is_reset'] == 1:
339 | if "task_current_prog_num" and "task_total_prog_num" in check_is_reset:
340 | task_list.update_one(
341 | {"_id": task["_id"]},
342 | {
343 | "$set": {
344 | "status": 1,
345 | "start_time": start_time,
346 | "finished_time": finished_time,
347 | "task_current_prog_num": int(task_current_prog_num) + check_is_reset['task_current_prog_num'],
348 | "task_total_prog_num": int(task_total_prog_num) + check_is_reset['task_total_prog_num'],
349 | "task_current_prog_size": 0,
350 | "task_total_prog_size": 0,
351 | "task_current_prog_size_tail": "",
352 | "task_total_prog_size_tail": "",
353 | "dst_endpoint_link": dst_endpoint_link,
354 | "dst_endpoint_id": dst_endpoint_id["id"],
355 | }
356 | },
357 | )
358 | else:
359 | task_list.update_one(
360 | {"_id": task["_id"]},
361 | {
362 | "$set": {
363 | "status": 1,
364 | "start_time": start_time,
365 | "finished_time": finished_time,
366 | "task_current_prog_num": int(task_current_prog_num),
367 | "task_total_prog_num": int(task_total_prog_num),
368 | "task_current_prog_size": 0,
369 | "task_total_prog_size": 0,
370 | "task_current_prog_size_tail": "",
371 | "task_total_prog_size_tail": "",
372 | "dst_endpoint_link": dst_endpoint_link,
373 | "dst_endpoint_id": dst_endpoint_id["id"],
374 | }
375 | },
376 | )
377 |
378 | if ns.x == 1:
379 | bot.edit_message_text(
380 | chat_id=chat_id,
381 | message_id=message_id,
382 | text=" ༺ ✪iCopy✪ ༻ \n"
383 | + _text[_lang]["killed"]
384 | + " | "
385 | + "🏳️"
386 | + _text[_lang]["current_task_id"]
387 | + str(task["_id"])
388 | + "\n\n"
389 | + message_info.replace(
390 | "\n\n" + _text[_lang]["task_start_time"] + start_time, ""
391 | ).replace(
392 | "\n"
393 | + _text[_lang]["task_status"]
394 | + "\n\n"
395 | + str(task_in_size_speed)
396 | + " | "
397 | + str(task_in_file_speed),
398 | "",
399 | )
400 | + "\n"
401 | + _text[_lang]["is_killed_by_user"],
402 | )
403 |
404 | task_list.update_one(
405 | {"_id": task["_id"]},
406 | {
407 | "$set": {
408 | "status": 1,
409 | "error": 9,
410 | "start_time": start_time,
411 | "finished_time": finished_time,
412 | }
413 | },
414 | )
415 |
416 | if interruption == 1:
417 | bot.edit_message_text(
418 | chat_id=chat_id,
419 | message_id=message_id,
420 | text=" ༺ ✪iCopy✪ ༻ \n"
421 | + _text[_lang]["interrupted"]
422 | + " | "
423 | + "🏳️"
424 | + _text[_lang]["current_task_id"]
425 | + str(task["_id"])
426 | + "\n\n"
427 | + message_info.replace(
428 | "\n\n" + _text[_lang]["task_start_time"] + start_time, ""
429 | ).replace(
430 | "\n"
431 | + _text[_lang]["task_status"]
432 | + "\n\n"
433 | + str(task_in_size_speed)
434 | + " | "
435 | + str(task_in_file_speed),
436 | "",
437 | )
438 | + "\n"
439 | + _text[_lang]["is_interrupted_error"],
440 | )
441 |
442 | task_list.update_one(
443 | {"_id": task["_id"]},
444 | {
445 | "$set": {
446 | "status": 1,
447 | "error": 1,
448 | "start_time": start_time,
449 | "finished_time": finished_time,
450 | }
451 | },
452 | )
453 |
454 | prog_bar = _bar.status(0)
455 |
456 |
457 | def task_message_box(bot, chat_id, message_id, context):
458 | global context_old
459 | context_old = "iCopy"
460 | if context_old != context:
461 | bot.edit_message_text(chat_id=chat_id, message_id=message_id, text=context)
462 | context_old = context
463 |
464 |
465 | def run(command):
466 | global icopyprocess
467 | icopyprocess = subprocess.Popen(
468 | command,
469 | stdout=subprocess.PIPE,
470 | stderr=subprocess.STDOUT,
471 | shell=False,
472 | encoding="utf-8",
473 | errors="ignore",
474 | universal_newlines=True,
475 | )
476 | while True:
477 | line = icopyprocess.stdout.readline().rstrip()
478 | if not line:
479 | break
480 | yield line
481 | icopyprocess.communicate()
482 |
483 |
--------------------------------------------------------------------------------
/iCopy/workflow/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/f0rx4/FClone-Bot/f73d648a4a3a5c727bbb79b98af5cf61fb7d2498/iCopy/workflow/__init__.py
--------------------------------------------------------------------------------
/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/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/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/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/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/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/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 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | python-telegram-bot==12.8
2 | urllib3==1.25.9
3 | requests==2.24.0
4 | google_api_python_client==1.10.0
5 | protobuf==3.12.2
6 | pymongo[srv]==3.10.1
7 | toml==0.10.1
8 | covid
--------------------------------------------------------------------------------
/runtime.txt:
--------------------------------------------------------------------------------
1 | python-3.8.5
--------------------------------------------------------------------------------