diff --git a/.gitignore b/.gitignore
index 5111785c9..1d72f627d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -163,7 +163,6 @@ plugins/l2tp
plugins/openlitespeed
plugins/tamper_proof
plugins/cryptocurrency_trade
-plugins/gdrive
plugins/mtproxy
plugins/zimg
plugins/bk_demo
diff --git a/plugins/gdrive/class/gdriveclient.py b/plugins/gdrive/class/gdriveclient.py
new file mode 100644
index 000000000..9f03da8de
--- /dev/null
+++ b/plugins/gdrive/class/gdriveclient.py
@@ -0,0 +1,413 @@
+# coding:utf-8
+
+import sys
+import io
+import os
+import time
+import re
+import json
+import io
+
+
+sys.path.append(os.getcwd() + "/class/core")
+import mw
+
+# -----------------------------
+import google.oauth2.credentials
+import google_auth_oauthlib.flow
+from googleapiclient.discovery import build
+from google_auth_oauthlib.flow import InstalledAppFlow
+from google.auth.transport.requests import Request
+from googleapiclient.http import MediaFileUpload
+from googleapiclient.http import MediaIoBaseDownload
+
+
+class gdriveclient():
+ __plugin_dir = ''
+ __server_dir = ''
+
+ __credentials = "/root/credentials.json"
+ __backup_dir_name = "backup"
+ __creds = None
+ __exclude = ""
+ __scpos = ['https://www.googleapis.com/auth/drive.file']
+ _title = 'Google Drive'
+ _name = 'Google Drive'
+ __debug = False
+
+ _DEFAULT_AUTH_PROMPT_MESSAGE = (
+ 'Please visit this URL to authorize this application: {url}')
+ """str: The message to display when prompting the user for
+ authorization."""
+ _DEFAULT_AUTH_CODE_MESSAGE = (
+ 'Enter the authorization code: ')
+ """str: The message to display when prompting the user for the
+ authorization code. Used only by the console strategy."""
+
+ _DEFAULT_WEB_SUCCESS_MESSAGE = (
+ 'The authentication flow has completed, you may close this window.')
+
+ def __init__(self, plugin_dir, server_dir):
+ self.__plugin_dir = plugin_dir
+ self.__server_dir = server_dir
+ self.set_creds()
+ # self.set_libList()
+
+ # self.get_exclode()
+
+ def setDebug(self, d=False):
+ self.__debug = d
+
+ def D(self, msg=''):
+ if self.__debug:
+ print(msg)
+
+ # 检查gdrive连接
+ def _check_connect(self):
+ try:
+ service = build('drive', 'v3', credentials=self.__creds)
+ results = service.files().list(
+ pageSize=10, fields="nextPageToken, files(id, name)").execute()
+ results.get('files', [])
+ except:
+ return False
+ return True
+
+ # 设置creds
+ def set_creds(self):
+ token_file = self.__server_dir + '/token.json'
+ if os.path.exists(token_file):
+ with open(token_file, 'rb') as token:
+ tmp_data = json.load(token)['credentials']
+ self.__creds = google.oauth2.credentials.Credentials(
+ tmp_data['token'],
+ tmp_data['refresh_token'],
+ tmp_data['id_token'],
+ tmp_data['token_uri'],
+ tmp_data['client_id'],
+ tmp_data['client_secret'],
+ tmp_data['scopes'])
+ # if not self._check_connect():
+ # return False
+ # else:
+ # return True
+
+ def get_sign_in_url(self):
+ flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
+ self.__plugin_dir + '/credentials.json',
+ scopes=self.__scpos)
+ flow.redirect_uri = 'https://localhost'
+ auth_url, state = flow.authorization_url(
+ access_type='offline',
+ prompt='consent',
+ include_granted_scopes='false')
+ return auth_url, state
+
+ def set_auth_url(self, url):
+ token_file = self.__server_dir + '/token.json'
+ if os.path.exists(token_file):
+ return mw.returnJson(True, "验证成功")
+
+ flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file(
+ self.__plugin_dir + '/credentials.json',
+ scopes=self.__scpos,
+ state=url.split('state=')[1].split('&code=')[0])
+ flow.redirect_uri = 'https://localhost'
+ flow.fetch_token(authorization_response=url)
+ credentials = flow.credentials
+
+ credentials_data = {}
+ credentials_data['credentials'] = {
+ 'token': credentials.token,
+ 'id_token': credentials.id_token,
+ 'refresh_token': credentials.refresh_token,
+ 'token_uri': credentials.token_uri,
+ 'client_id': credentials.client_id,
+ 'client_secret': credentials.client_secret,
+ 'scopes': credentials.scopes}
+ with open(token_file, 'w') as token:
+ json.dump(credentials_data, token)
+ if not self.set_creds():
+ return mw.returnJson(False, "验证失败,请根据页面1 2 3 步骤完成验证")
+ return mw.returnJson(True, "验证成功")
+
+ def set_libList(self):
+ libList = public.readFile("/www/server/panel/data/libList.conf")
+ if libList:
+ libList = json.loads(libList)
+ for i in libList:
+ if "gdrive" in i.values():
+ return
+ d = {
+ "name": "Google Drive",
+ "type": "Cron job",
+ "ps": "Back up your website or database to Google Cloud Storage.",
+ "status": False,
+ "opt": "gdrive",
+ "module": "os",
+ "script": "gdrive",
+ "help": "http://forum.aapanel.com",
+ "key": "",
+ "secret": "",
+ "bucket": "",
+ "domain": "",
+ "check": ["/www/server/panel/plugin/gdrive/gdrive_main.py",
+ "/www/server/panel/script/backup_gdrive.py"]
+ }
+ d1 = {
+ "name": "谷歌硬盘",
+ "type": "计划任务",
+ "ps": "将网站或数据库打包备份到谷歌硬盘.",
+ "status": False,
+ "opt": "gdrive",
+ "module": "os",
+ "script": "gdrive",
+ "help": "http://www.bt.cn/bbs",
+ "key": "",
+ "secret": "",
+ "bucket": "",
+ "domain": "",
+ "check": ["/www/server/panel/plugin/gdrive/gdrive_main.py",
+ "/www/server/panel/script/backup_gdrive.py"]
+ }
+ language = public.readFile("/www/server/panel/config/config.json")
+ if "English" in language:
+ data = d
+ else:
+ data = d1
+ libList.append(data)
+ public.writeFile("/www/server/panel/data/libList.conf",
+ json.dumps(libList))
+ return libList
+
+ # 获取token
+ def get_token(self, get):
+ token_file = self.__server_dir + '/token.json'
+ import requests
+ try:
+ respone = requests.get("https://www.google.com", timeout=2)
+ except:
+ return mw.returnJson(False, "连接谷歌失败")
+ if respone.status_code != 200:
+ return mw.returnJson(False, "连接谷歌失败")
+ if not self.set_creds():
+ return mw.returnJson(False, "验证失败,请根据页面1 2 3 步骤完成验证")
+ if not os.path.exists(token_file):
+ return mw.returnJson(False, "验证失败,请根据页面1 2 3 步骤完成验证")
+ return mw.returnJson(True, "验证成功")
+
+ # 获取auth_url
+ def get_auth_url(self, get):
+ self.get_sign_in_url()
+ if os.path.exists("/tmp/auth_url"):
+ return mw.readFile("/tmp/auth_url")
+
+ # 检查连接
+ def check_connect(self, get):
+ token_file = self.__server_dir + '/token.json'
+ if os.path.exists(token_file):
+ with open(token_file, 'rb') as token:
+ self.set_creds()
+ else:
+ self.D("Failed to get Google token, please verify before use")
+ return mw.returnJson(True, "Failed to get Google token, please verify before use")
+ service = build('drive', 'v3', credentials=self.__creds)
+ results = service.files().list(
+ pageSize=10, fields="nextPageToken, files(id, name)").execute()
+ try:
+ results.get('files', [])
+ return mw.returnJson(False, "验证失败,请根据页面1 2 3 步骤完成验证")
+ return mw.returnJson(True, "验证成功")
+ except:
+ return mw.returnJson(False, "验证失败,请根据页面1 2 3 步骤完成验证")
+
+ def _get_filename(self, filename):
+ l = filename.split("/")
+ return l[-1]
+
+ def _create_folder_cycle(self, filepath):
+ l = filepath.split("/")
+ fid_list = []
+ for i in l:
+ if not i:
+ continue
+ fid = self.__get_folder_id(i)
+ if fid:
+ fid_list.append(fid)
+ continue
+ if not fid_list:
+ fid_list.append("")
+ fid_list.append(self.create_folder(i, fid_list[-1]))
+ return fid_list[-1]
+
+ def build_object_name(self, data_type, file_name):
+ """根据数据类型构建对象存储名称
+
+ :param data_type:
+ :param file_name:
+ :return:
+ """
+
+ import re
+ prefix_dict = {
+ "site": "web",
+ "database": "db",
+ "path": "path",
+ }
+
+ if not prefix_dict.get(data_type):
+ print("data_type 类型错误!!!")
+ exit(1)
+
+ file_regx = prefix_dict.get(data_type) + "_(.+)_20\d+_\d+\."
+ sub_search = re.search(file_regx.lower(), file_name)
+ sub_path_name = ""
+ if sub_search:
+ sub_path_name = sub_search.groups()[0]
+ sub_path_name += '/'
+ # 构建OS存储路径
+ object_name = self.__backup_dir_name + \
+ '/{}/{}'.format(data_type, sub_path_name)
+
+ if object_name[:1] == "/":
+ object_name = object_name[1:]
+ return object_name
+
+ # 上传文件
+ def upload_file(self, filename, data_type=None):
+ """
+ get.filename 上传后的文件名
+ get.filepath 上传文件路径
+ 被面板新版计划任务调用时
+ get表示file_name
+ :param get:
+ :return:
+ """
+ # filename = filename
+ filepath = self.build_object_name(data_type, filename)
+ _filename = self._get_filename(filename)
+ self.D(filepath)
+ self.D(filename)
+
+ parents = self._create_folder_cycle(filepath)
+ self.D(parents)
+ drive_service = build('drive', 'v3', credentials=self.__creds)
+ file_metadata = {'name': _filename, 'parents': [parents]}
+ media = MediaFileUpload(filename, resumable=True)
+ file = drive_service.files().create(
+ body=file_metadata, media_body=media, fields='id').execute()
+ self.D('Upload Success ,File ID: %s' % file.get('id'))
+ return True
+
+ def _get_file_id(self, filename):
+ service = build('drive', 'v3', credentials=self.__creds)
+ results = service.files().list(pageSize=10, q="name='{}'".format(
+ filename), fields="nextPageToken, files(id, name)").execute()
+ items = results.get('files', [])
+ if not items:
+ return []
+ else:
+ for item in items:
+ return item["id"]
+
+ def delete_file(self, filename=None, data_type=None):
+ file_id = self._get_file_id(filename)
+ self.delete_file_by_id(file_id)
+ return True
+
+ def delete_file_by_id(self, file_id):
+ self.D("delete id:{}".format(file_id))
+ try:
+ drive_service = build('drive', 'v3', credentials=self.__creds)
+ drive_service.files().delete(fileId=file_id).execute()
+ return True
+ except Exception as e:
+ return False
+
+ # 创建目录
+ def create_folder(self, folder_name, parents=""):
+ self.D(self.__creds)
+ self.D("folder_name: {}".format(folder_name))
+ self.D("parents: {}".format(parents))
+ service = build('drive', 'v3', credentials=self.__creds)
+ file_metadata = {
+ 'name': folder_name,
+ 'mimeType': 'application/vnd.google-apps.folder'
+ }
+ if parents:
+ file_metadata['parents'] = [parents]
+ folder = service.files().create(body=file_metadata, fields='id').execute()
+ self.D('Create Folder ID: %s' % folder.get('id'))
+ return folder.get('id')
+
+ def get_rootdir_id(self, folder_name='backup'):
+ service = build('drive', 'v3', credentials=self.__creds)
+ results = service.files().list(pageSize=10, q="name='{}' and mimeType='application/vnd.google-apps.folder'".format(folder_name),
+ fields="nextPageToken, files(id, name,size,parents,webViewLink)").execute()
+ items = results.get('files', [])
+ if len(items) == 0:
+ self.create_folder(folder_name)
+ return self.get_rootdir_id(folder_name)
+
+ return items[0]['parents'][0]
+
+ # 获取目录id
+ def __get_folder_id(self, floder_name):
+ service = build('drive', 'v3', credentials=self.__creds)
+ results = service.files().list(pageSize=10, q="name='{}' and mimeType='application/vnd.google-apps.folder'".format(floder_name),
+ fields="nextPageToken, files(id, name)").execute()
+ items = results.get('files', [])
+ if not items:
+ return []
+ else:
+ for item in items:
+ return item["id"]
+
+ def get_res_info(self, rid):
+ service = build('drive', 'v3', credentials=self.__creds)
+ results = service.files().get(fileId='{}'.format(rid)).execute()
+ return results
+
+ def get_id_list(self, driveId=''):
+ service = build('drive', 'v3', credentials=self.__creds)
+ results = service.files().list(pageSize=10, driveId="{}".format(driveId),
+ fields="nextPageToken, files(id, name,size,parents)").execute()
+ items = results.get('files', [])
+ return items
+
+ def get_list(self, dir_id='', next_page_token=''):
+ if dir_id == '':
+ dir_id = self.get_rootdir_id(self.__backup_dir_name)
+
+ service = build('drive', 'v3', credentials=self.__creds)
+ cmd_query = "trashed=false and '{}' in parents".format(dir_id)
+ results = service.files().list(pageSize=10, q=cmd_query, orderBy='folder asc',
+ fields="nextPageToken, files(id, name,size,createdTime,parents,webViewLink)").execute()
+ items = results.get('files', [])
+ nextPageToken = results.get('nextPageToken', [])
+ # print(items)
+ # print(nextPageToken)
+ return items
+
+ def get_exclode(self, exclude=[]):
+ if not exclude:
+ tmp_exclude = os.getenv('BT_EXCLUDE')
+ if tmp_exclude:
+ exclude = tmp_exclude.split(',')
+ if not exclude:
+ return ""
+ for ex in exclude:
+ self.__exclude += " --exclude=\"" + ex + "\""
+ self.__exclude += " "
+ return self.__exclude
+
+ def download_file(self, filename):
+ file_id = self._get_file_id(filename)
+ service = build('drive', 'v3', credentials=self.__creds)
+ request = service.files().get_media(fileId=file_id)
+ with open('/tmp/{}'.format(filename), 'wb') as fh:
+ downloader = MediaIoBaseDownload(fh, request)
+ done = False
+ while done is False:
+ status, done = downloader.next_chunk()
+ print("Download %d%%." % int(status.progress() * 100))
diff --git a/plugins/gdrive/credentials.json b/plugins/gdrive/credentials.json
new file mode 100644
index 000000000..cb209bdcd
--- /dev/null
+++ b/plugins/gdrive/credentials.json
@@ -0,0 +1,11 @@
+{
+ "web": {
+ "client_id": "540181629246-1horo9i4htamdbhiqar9rcbq33bqe2ob.apps.googleusercontent.com",
+ "project_id": "plated-inn-369901",
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
+ "token_uri": "https://oauth2.googleapis.com/token",
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
+ "client_secret": "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",
+ "redirect_uris": ["https://drive.aapanel.com"]
+ }
+}
\ No newline at end of file
diff --git a/plugins/gdrive/ico.png b/plugins/gdrive/ico.png
new file mode 100644
index 000000000..3cd2b985f
Binary files /dev/null and b/plugins/gdrive/ico.png differ
diff --git a/plugins/gdrive/index.html b/plugins/gdrive/index.html
new file mode 100644
index 000000000..f0f95cbb0
--- /dev/null
+++ b/plugins/gdrive/index.html
@@ -0,0 +1,310 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/plugins/gdrive/index.py b/plugins/gdrive/index.py
new file mode 100644
index 000000000..bc864901b
--- /dev/null
+++ b/plugins/gdrive/index.py
@@ -0,0 +1,342 @@
+# coding:utf-8
+
+import sys
+import io
+import os
+import time
+import re
+import json
+
+
+# print(sys.platform)
+if sys.platform != "darwin":
+ os.chdir("/www/server/mdserver-web")
+
+
+sys.path.append(os.getcwd() + "/class/core")
+import mw
+import db
+
+_ver = sys.version_info
+is_py2 = (_ver[0] == 2)
+is_py3 = (_ver[0] == 3)
+
+DEBUG = False
+
+if is_py2:
+ reload(sys)
+ sys.setdefaultencoding('utf-8')
+
+app_debug = False
+if mw.isAppleSystem():
+ app_debug = True
+
+
+def getPluginName():
+ return 'gdrive'
+
+
+def getPluginDir():
+ return mw.getPluginDir() + '/' + getPluginName()
+
+
+def getServerDir():
+ return mw.getServerDir() + '/' + getPluginName()
+
+
+def in_array(name, arr=[]):
+ for x in arr:
+ if name == x:
+ return True
+ return False
+
+
+sys.path.append(getPluginDir() + "/class")
+from gdriveclient import gdriveclient
+
+
+gd = gdriveclient(getPluginDir(), getServerDir())
+gd.setDebug(False)
+
+
+def getArgs():
+ args = sys.argv[2:]
+ tmp = {}
+ args_len = len(args)
+ if args_len == 1:
+ t = args[0].strip('{').strip('}')
+ if t.strip() == '':
+ tmp = []
+ else:
+ t = t.split(':', 1)
+ tmp[t[0]] = t[1]
+ elif args_len > 1:
+ for i in range(len(args)):
+ t = args[i].split(':', 1)
+ tmp[t[0]] = t[1]
+ return tmp
+
+
+def checkArgs(data, ck=[]):
+ for i in range(len(ck)):
+ if not ck[i] in data:
+ return (False, mw.returnJson(False, '参数:(' + ck[i] + ')没有!'))
+ return (True, mw.returnJson(True, 'ok'))
+
+
+def status():
+ return 'start'
+
+
+def isAuthApi():
+ cfg = getServerDir() + "/token.json"
+ if os.path.exists(cfg):
+ return True
+ return False
+
+
+def getConf():
+ if not isAuthApi():
+ sign_in_url, state = gd.get_sign_in_url()
+ return mw.returnJson(False, "未授权!", {'auth_url': sign_in_url})
+ return mw.returnJson(True, "OK")
+
+
+def setAuthUrl():
+ args = getArgs()
+ data = checkArgs(args, ['url'])
+ if not data[0]:
+ return data[1]
+
+ url = args['url']
+
+ try:
+ if url.startswith("http://"):
+ url = url.replace("http://", "https://")
+ token = gd.set_auth_url(url)
+ # gd.store_token(token)
+ # gd.store_user()
+ return mw.returnJson(True, "授权成功!")
+ except Exception as e:
+ return mw.returnJson(False, "授权失败2!:" + str(e))
+ return mw.returnJson(False, "授权失败!:" + str(e))
+
+
+def clearAuth():
+ cfg = getServerDir() + "/user.conf"
+ if os.path.exists(cfg):
+ os.remove(cfg)
+
+ token = getServerDir() + "/token.pickle"
+ if os.path.exists(token):
+ os.remove(token)
+
+ return mw.returnJson(True, "清空授权成功!")
+
+
+def getList():
+ if not isAuthApi():
+ return mw.returnJson(False, "未配置,请点击`授权`", [])
+
+ args = getArgs()
+ data = checkArgs(args, ['file_id'])
+ if not data[0]:
+ return data[1]
+
+ try:
+ flist = gd.get_list(args['file_id'])
+ return mw.returnJson(True, "ok", flist)
+ except Exception as e:
+ return mw.returnJson(False, str(e), [])
+
+
+def createDir():
+ if not isAuthApi():
+ return mw.returnJson(False, "未配置,请点击`授权`", [])
+
+ args = getArgs()
+ data = checkArgs(args, ['parents', 'name'])
+ if not data[0]:
+ return data[1]
+ isok = gd.create_folder(args['name'], args['parents'])
+ if isok:
+ return mw.returnJson(True, "创建成功")
+ return mw.returnJson(False, "创建失败")
+
+
+def deleteDir():
+ args = getArgs()
+ data = checkArgs(args, ['dir_name', 'path'])
+ if not data[0]:
+ return data[1]
+
+ isok = gd.delete_file_by_id(args['dir_name'])
+ if isok:
+ return mw.returnJson(True, "删除成功")
+ return mw.returnJson(False, "文件不为空,删除失败!")
+
+
+def deleteFile():
+ args = getArgs()
+ data = checkArgs(args, ['path', 'filename'])
+ if not data[0]:
+ return data[1]
+
+ isok = gd.delete_file(args['filename'])
+ if isok:
+ return mw.returnJson(True, "删除成功")
+ return mw.returnJson(False, "删除失败")
+
+
+def findPathName(path, filename):
+ f = os.scandir(path)
+ l = []
+ for ff in f:
+ t = {}
+ if ff.name.find(filename) > -1:
+ t['filename'] = path + '/' + ff.name
+ l.append(t)
+ return l
+
+
+def backupAllFunc(stype):
+ if not isAuthApi():
+ mw.echoInfo("未授权API,无法使用!!!")
+ return ''
+
+ os.chdir(mw.getRunDir())
+ backup_dir = mw.getBackupDir()
+ run_dir = mw.getRunDir()
+
+ stype = sys.argv[1]
+ name = sys.argv[2]
+ num = sys.argv[3]
+
+ prefix_dict = {
+ "site": "web",
+ "database": "db",
+ "path": "path",
+ }
+
+ backups = []
+ sql = db.Sql()
+
+ # print("stype:", stype)
+ # 提前获取-清理多余备份
+ if stype == 'site':
+ pid = sql.table('sites').where('name=?', (name,)).getField('id')
+ backups = sql.table('backup').where(
+ 'type=? and pid=?', ('0', pid)).field('id,filename').select()
+ if stype == 'database':
+ db_path = mw.getServerDir() + '/mysql'
+ pid = mw.M('databases').dbPos(db_path, 'mysql').where(
+ 'name=?', (name,)).getField('id')
+ backups = sql.table('backup').where(
+ 'type=? and pid=?', ('1', pid)).field('id,filename').select()
+ if stype == 'path':
+ backup_path = backup_dir + '/path'
+ _name = 'path_{}'.format(os.path.basename(name))
+ backups = findPathName(backup_path, _name)
+
+ # 其他类型关系性数据库(mysql类的)
+ if stype.find('database_') > -1:
+ plugin_name = stype.replace('database_', '')
+ db_path = mw.getServerDir() + '/' + plugin_name
+ pid = mw.M('databases').dbPos(db_path, 'mysql').where(
+ 'name=?', (name,)).getField('id')
+ backups = sql.table('backup').where(
+ 'type=? and pid=?', ('1', pid)).field('id,filename').select()
+
+ args = stype + " " + name + " " + num
+ cmd = 'python3 ' + run_dir + '/scripts/backup.py ' + args
+ if stype.find('database_') > -1:
+ plugin_name = stype.replace('database_', '')
+ args = "database " + name + " " + num
+ cmd = 'python3 ' + run_dir + '/plugins/' + \
+ plugin_name + '/scripts/backup.py ' + args
+
+ if stype == 'path':
+ name = os.path.basename(name)
+
+ # print("cmd:", cmd)
+ os.system(cmd)
+
+ # 开始执行上传信息.
+ if stype.find('database_') > -1:
+ bk_name = 'database'
+ plugin_name = stype.replace('database_', '')
+ bk_prefix = plugin_name + '/db'
+ stype = 'database'
+ else:
+ bk_prefix = prefix_dict[stype]
+ bk_name = stype
+
+ find_path = backup_dir + '/' + bk_name + '/' + bk_prefix + '_' + name
+ find_new_file = "ls " + find_path + \
+ "_* | grep '.gz' | cut -d \ -f 1 | awk 'END {print}'"
+
+ # print(find_new_file)
+
+ filename = mw.execShell(find_new_file)[0].strip()
+ if filename == "":
+ mw.echoInfo("not find upload file!")
+ return False
+
+ mw.echoInfo("准备上传文件 {}".format(filename))
+ mw.echoStart('开始上传')
+ gd.upload_file(filename, stype)
+ mw.echoEnd('上传成功')
+
+ # print(backups)
+ backups = sorted(backups, key=lambda x: x['filename'], reverse=False)
+ mw.echoStart('开始删除远程备份')
+ num = int(num)
+ sep = len(backups) - num
+ if sep > -1:
+ for backup in backups:
+ fn = os.path.basename(backup['filename'])
+ gd.delete_file(fn, stype)
+ mw.echoInfo("---已清理远程过期备份文件:" + fn)
+ sep -= 1
+ if sep < 0:
+ break
+ mw.echoEnd('结束删除远程备份')
+
+ return ''
+
+
+def installPreInspection():
+ return 'ok'
+
+if __name__ == "__main__":
+ func = sys.argv[1]
+ if func == 'status':
+ print(status())
+ elif func == 'start':
+ print(start())
+ elif func == 'stop':
+ print(stop())
+ elif func == 'restart':
+ print(restart())
+ elif func == 'reload':
+ print(reload())
+ elif func == 'install_pre_inspection':
+ print(installPreInspection())
+ elif func == 'conf':
+ print(getConf())
+ elif func == 'set_auth_url':
+ print(setAuthUrl())
+ elif func == 'clear_auth':
+ print(clearAuth())
+ elif func == "get_list":
+ print(getList())
+ elif func == "create_dir":
+ print(createDir())
+ elif func == "delete_dir":
+ print(deleteDir())
+ elif func == 'delete_file':
+ print(deleteFile())
+ elif in_array(func, ['site', 'database', 'path']) or func.find('database_') > -1:
+ print(backupAllFunc(func))
+ else:
+ print('error')
diff --git a/plugins/gdrive/info.json b/plugins/gdrive/info.json
new file mode 100644
index 000000000..9e866a073
--- /dev/null
+++ b/plugins/gdrive/info.json
@@ -0,0 +1,19 @@
+{
+ "hook":["backup"],
+ "id":998,
+ "title":"谷歌云网盘",
+ "tip":"lib",
+ "name":"gdrive",
+ "type":"扩展",
+ "ps":"备份你的数据到谷歌云网盘",
+ "versions":"2.0",
+ "shell":"install.sh",
+ "checks": "server/gdrive",
+ "path":"server/gdrive",
+ "author":"google",
+ "home":"https://drive.google.com/",
+ "api_doc":"https://developers.google.com/drive/api/guides/about-sdk?hl=zh_CN",
+ "api_doc2":"https://developers.google.cn/drive/api/reference/rest/v3/comments/list?hl=zh-cn",
+ "date":"2022-06-26",
+ "pid":"4"
+}
\ No newline at end of file
diff --git a/plugins/gdrive/install.sh b/plugins/gdrive/install.sh
new file mode 100644
index 000000000..1ad2d004d
--- /dev/null
+++ b/plugins/gdrive/install.sh
@@ -0,0 +1,79 @@
+#!/bin/bash
+PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
+export PATH
+
+curPath=`pwd`
+rootPath=$(dirname "$curPath")
+rootPath=$(dirname "$rootPath")
+serverPath=$(dirname "$rootPath")
+
+install_tmp=${rootPath}/tmp/mw_install.pl
+
+PATH=$PATH:${rootPath}/bin
+export PATH
+
+VERSION=$2
+
+Install_App()
+{
+ tmp_ping=`ping -c 1 google.com 2>&1`
+ echo $tmp_ping
+ if [ $? -eq 0 ];then
+ tmp=`python -V 2>&1|awk '{print $2}'`
+ pVersion=${tmp:0:3}
+
+ which pip
+ if [ $? -eq 0 ];then
+ tmp=$(pip list|grep google-api-python-client|awk '{print $2}')
+ if [ "$tmp" != '2.39.0' ];then
+ pip install --upgrade google-api-python-client
+ # pip uninstall google-api-python-client -y
+ pip install -I google-api-python-client==2.39.0 -i https://pypi.Python.org/simple
+ fi
+ tmp=$(pip list|grep google-auth-httplib2|awk '{print $2}')
+ if [ "$tmp" != '0.1.0' ];then
+ pip uninstall google-auth-httplib2 -y
+ pip install -I google-auth-httplib2==0.1.0 -i https://pypi.Python.org/simple
+ fi
+ tmp=$(pip list|grep google-auth-oauthlib|awk '{print $2}')
+ if [ "$tmp" != '0.5.0' ];then
+ # pip uninstall google-auth-oauthlib -y
+ pip install -I google-auth-oauthlib==0.5.0 -i https://pypi.Python.org/simple
+ fi
+ tmp=$(pip list|grep -E '^httplib2'|awk '{print $2}')
+ if [ "$tmp" != '0.18.1' ];then
+ # pip uninstall httplib2 -y
+ pip install -I httplib2==0.18.1 -i https://pypi.Python.org/simple
+ fi
+ else
+ pip install -I pyOpenSSL
+ pip install -I google-api-python-client==2.39.0 google-auth-httplib2==0.1.0 google-auth-oauthlib==0.5.0 -i https://pypi.Python.org/simple
+ pip install -I httplib2==0.18.1 -i https://pypi.Python.org/simple
+ fi
+ echo '正在安装脚本文件...' > $install_tmp
+
+ mkdir -p $serverPath/gdrive
+ echo "${VERSION}" > $serverPath/gdrive/version.pl
+ echo '安装完成' > $install_tmp
+ else
+ echo '服务器连接不上谷歌云!安装失败!' > $install_tmp
+ exit 1
+ fi
+}
+
+Uninstall_App()
+{
+ rm -rf $serverPath/gdrive
+ echo '卸载完成' > $install_tmp
+}
+
+
+action=$1
+type=$2
+
+echo $action $type
+if [ "${action}" == 'install' ];then
+ Install_App
+else
+ Uninstall_App
+fi
diff --git a/plugins/gdrive/js/gdrive.js b/plugins/gdrive/js/gdrive.js
new file mode 100644
index 000000000..08aa336a1
--- /dev/null
+++ b/plugins/gdrive/js/gdrive.js
@@ -0,0 +1,260 @@
+
+function gdPost(method,args,callback){
+ var _args = null;
+ if (typeof(args) == 'string'){
+ _args = JSON.stringify(toArrayObject(args));
+ } else {
+ _args = JSON.stringify(args);
+ }
+
+ var loadT = layer.msg('正在获取...', { icon: 16, time: 0, shade: 0.3 });
+ $.post('/plugins/run', {name:'gdrive', func:method, args:_args}, function(data) {
+ layer.close(loadT);
+ if (!data.status){
+ layer.msg(data.msg,{icon:0,time:2000,shade: [0.3, '#000']});
+ return;
+ }
+
+ if(typeof(callback) == 'function'){
+ callback(data);
+ }
+ },'json');
+}
+
+
+function createDir(){
+ layer.open({
+ type: 1,
+ area: "400px",
+ title: "创建目录",
+ closeBtn: 1,
+ shift: 5,
+ shadeClose: false,
+ btn: ['确定','取消'],
+ content:'',
+ success:function(){
+ $("input[name='newPath']").focus().keyup(function(e){
+ if(e.keyCode == 13) $(".layui-layer-btn0").click();
+ });
+ },
+ yes:function(index,layero){
+ var name = $("input[name='newPath']").val();
+ if(name == ''){
+ layer.msg('目录名称不能为空!',{icon:2});
+ return;
+ }
+ var parents = $("#myPath").val();
+ var cur_file_id = $('#curPath').val();
+ if (cur_file_id!=''){
+ parents = cur_file_id;
+ }
+
+ var dirname = name;
+ var loadT = layer.msg('正在创建目录['+dirname+']...',{icon:16,time:0,shade: [0.3, '#000']});
+ gdPost('create_dir', {parents:parents,name:dirname}, function(data){
+ layer.close(loadT);
+ var rdata = $.parseJSON(data.data);
+ if(rdata.status) {
+ showMsg(rdata.msg, function(){
+ layer.close(index);
+ var file_id = $('#myPath').val();
+ if (cur_file_id!=''){
+ file_id = cur_file_id;
+ }
+ gdList(file_id);
+ } ,{icon:1}, 2000);
+ } else{
+ layer.msg(rdata.msg,{icon:2});
+ }
+ });
+ }
+ });
+}
+
+
+//设置API
+function authApi(){
+
+ gdPost('conf', {}, function(rdata){
+ var rdata = $.parseJSON(rdata.data);
+
+ // console.log(rdata);
+ // console.log(rdata.data.auth_url);
+ var apicon = '';
+ if (rdata.status){
+
+ var html = '';
+ html += '';
+
+ var loadOpen = layer.open({
+ type: 1,
+ title: '已授权',
+ area: '240px',
+ content:''+html+'
',
+ success: function(){
+ $('#clear_auth').click(function(){
+ gdPost('clear_auth', {}, function(rdata){
+ var rdata = $.parseJSON(rdata.data);
+ showMsg(rdata.msg,function(){
+ layer.close(loadOpen);
+ gdList('');
+ },{icon:rdata.status?1:2},2000);
+ });
+ });
+ }
+ });
+ return true;
+
+ } else{
+ apicon = ''+$("#check_api").html()+'
';
+ }
+
+ var layer_auth = layer.open({
+ type: 1,
+ area: "620px",
+ title: "Google Drive 授权",
+ closeBtn: 1,
+ shift: 5,
+ shadeClose: false,
+ content:apicon,
+ success:function(layero,index){
+ // console.log(layero,index);
+ if (!rdata.status){
+ $('.check_api .step_two_url').val(rdata.data['auth_url']);
+ $('.check_api .open_btlink').attr('href',rdata.data['auth_url']);
+
+ $('.check_api .ico-copy').click(function(){
+ copyPass(rdata.data['auth_url']);
+ });
+
+ $('.check_api .set_auth_btn').click(function(){
+
+ var url = $('.check_api .google_drive').val();
+ if ( url == ''){
+ layer.msg("验证URL不能为空",{icon:2});
+ return;
+ }
+ // console.log(url);
+ gdPost('set_auth_url', {url:url}, function(rdata){
+ var rdata = $.parseJSON(rdata.data);
+ var show_time = 2000;
+ if (!rdata.status){
+ show_time = 10000;
+ }
+
+ showMsg(rdata.msg,function(){
+ if (rdata.status){
+ layer.close(layer_auth);
+ gdList('');
+ }
+ },{icon:rdata.status?1:2},show_time);
+ });
+ });
+
+
+ }
+
+ }
+ });
+ });
+}
+
+//计算当前目录偏移
+function upPathLeft(){
+ var UlWidth = $(".place-input ul").width();
+ var SpanPathWidth = $(".place-input").width() - 20;
+ var Ml = UlWidth - SpanPathWidth;
+ if(UlWidth > SpanPathWidth ){
+ $(".place-input ul").css("left",-Ml)
+ }
+ else{
+ $(".place-input ul").css("left",0)
+ }
+}
+
+function getGDTime(a) {
+ return new Date(a).format("yyyy/MM/dd hh:mm:ss")
+}
+
+function gdList(file_id){
+ $('#curPath').val(file_id);
+ gdPost('get_list', {file_id:file_id}, function(rdata){
+ var rdata = $.parseJSON(rdata.data);
+ console.log(rdata);
+ if(rdata.status === false){
+ showMsg(rdata.msg,function(){
+ authApi();
+ },{icon:2},2000);
+ return;
+ }
+
+ var mlist = rdata.data;
+ var listBody = '';
+ var listFiles = '';
+ for(var i=0;i\'+mlist[i].name+' | \
+ - | \
+ - | \
+ 删除 | '
+ }else{
+ listFiles += ''+mlist[i].name+' | \
+ '+toSize(mlist[i].size)+' | \
+ '+getGDTime(mlist[i].createdTime)+' | \
+ 下载 | 删除 |
'
+ }
+ }
+ listBody += listFiles;
+ var pathLi = '根目录';
+
+ if (mlist.length>0){
+ $('#myPath').val(mlist[0]['parents'][0]);
+ }
+
+ $(".upyunCon .place-input ul").html(pathLi);
+ $(".upyunlist .list-list").html(listBody);
+
+ $('#backBtn').unbind().click(function() {
+ gdList('');
+ });
+
+ $('.upyunCon .refreshBtn').unbind().click(function(){
+ var file_id = $('#myPath').val();
+ gdList(file_id);
+ });
+ });
+}
+
+
+//删除文件
+function deleteFile(name, is_dir){
+ if (is_dir === false){
+ safeMessage('删除文件','删除后将无法恢复,真的要删除['+name+']吗?',function(){
+ var path = $("#myPath").val();
+ var filename = name;
+ gdPost('delete_file', {filename:filename,path:path}, function(rdata){
+ var rdata = $.parseJSON(rdata.data);
+ showMsg(rdata.msg,function(){
+ var file_id = $('#myPath').val();
+ gdList(file_id);
+ },{icon:rdata.status?1:2},2000);
+ });
+ });
+ } else {
+ safeMessage('删除文件夹','删除后将无法恢复,真的要删除文件资源['+name+']吗?',function(){
+ var path = $("#myPath").val();
+ gdPost('delete_dir', {dir_name:name,path:path}, function(rdata){
+ var rdata = $.parseJSON(rdata.data);
+ showMsg(rdata.msg,function(){
+ var file_id = $('#myPath').val();
+ gdList(file_id);
+ },{icon:rdata.status?1:2},2000);
+ });
+ });
+ }
+}
\ No newline at end of file
diff --git a/plugins/gdrive/t/test.py b/plugins/gdrive/t/test.py
new file mode 100644
index 000000000..360191934
--- /dev/null
+++ b/plugins/gdrive/t/test.py
@@ -0,0 +1,113 @@
+#!/usr/bin/python
+# coding: utf-8
+
+# python3 plugins/gdrive/t/test.py
+# https://console.cloud.google.com/apis/credentials/consent?project=plated-inn-369901
+
+# https://accounts.google.com/o/oauth2/auth?response_type=code&client_id=226011946234-d3e1vashgag64utjedu1ljt9u39ncrpq.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fdrive.aapanel.com&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.file&state=I2da4aZUwqgmikrgrqAwwSjyoEHVs1&access_type=offline&prompt=consent&include_granted_scopes=false
+
+
+# https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationMenuBlade/~/Overview/appId/08125e6b-6502-4ac9-9548-ad682f00848d/objectId/62b1d655-9828-47ed-be99-65eb18c3a929/isMSAApp~/false/defaultBlade/Overview/appSignInAudience/AzureADandPersonalMicrosoftAccount/servicePrincipalCreated~/true
+
+# 0WA8Q~sZkZFZKv50ryP4ux~.fpVtbHw7BuTZmbQB
+# client_id:d9878fac-8526-4ff6-8036-e1c92dd9dd80
+
+# 08125e6b-6502-4ac9-9548-ad682f00848d
+
+
+# /drive/v2internal/files?openDrive=false&reason=102&syncType=0&errorRecovery=false&q=trashed = false and '1-z6gUXseXmlxmntvkLzOe_tYFT5tdhM9' in parents&fields=kind,nextPageToken,items(kind,modifiedDate,hasVisitorPermissions,containsUnsubscribedChildren,modifiedByMeDate,lastViewedByMeDate,alternateLink,fileSize,owners(kind,permissionId,emailAddressFromAccount,id),lastModifyingUser(kind,permissionId,emailAddressFromAccount,id),customerId,ancestorHasAugmentedPermissions,hasThumbnail,thumbnailVersion,title,id,resourceKey,abuseIsAppealable,abuseNoticeReason,shared,accessRequestsCount,sharedWithMeDate,userPermission(role),explicitlyTrashed,mimeType,quotaBytesUsed,copyable,subscribed,folderColor,hasChildFolders,fileExtension,primarySyncParentId,sharingUser(kind,permissionId,emailAddressFromAccount,id),flaggedForAbuse,folderFeatures,spaces,sourceAppId,recency,recencyReason,version,actionItems,teamDriveId,hasAugmentedPermissions,createdDate,primaryDomainName,organizationDisplayName,passivelySubscribed,trashingUser(kind,permissionId,emailAddressFromAccount,id),trashedDate,parents(id),capabilities(canMoveItemIntoTeamDrive,canUntrash,canModifyContentRestriction,canMoveItemWithinTeamDrive,canMoveItemOutOfTeamDrive,canDeleteChildren,canTrashChildren,canRequestApproval,canReadCategoryMetadata,canEditCategoryMetadata,canAddMyDriveParent,canRemoveMyDriveParent,canShareChildFiles,canShareChildFolders,canRead,canMoveItemWithinDrive,canMoveChildrenWithinDrive,canAddFolderFromAnotherDrive,canChangeSecurityUpdateEnabled,canBlockOwner,canReportSpamOrAbuse,canCopy,canDownload,canEdit,canAddChildren,canDelete,canRemoveChildren,canShare,canTrash,canRename,canReadTeamDrive,canMoveTeamDriveItem),contentRestrictions(readOnly),approvalMetadata(approvalVersion,approvalSummaries,hasIncomingApproval),shortcutDetails(targetId,targetMimeType,targetLookupStatus,targetFile,canRequestAccessToTarget),spamMetadata(markedAsSpamDate,inSpamView),labels(starred,trashed,restricted,viewed)),incompleteSearch&appDataFilter=NO_APP_DATA&spaces=drive&maxResults=200&supportsTeamDrives=true&includeItemsFromAllDrives=true&corpora=default&orderBy=folder,title_natural asc&retryCount=0&key=AIzaSyD_InbmSFufIEps5UAt2NmB_3LvBH3Sz_8 HTTP/1.1
+
+# http://localhost/?code=M.C106_BAY.2.3e12c859-6107-0c5b-9ef4-14b3fb8269ba&state=JzHdzHXmA7x6zl7Be6cJ6uOlf9Bg69
+
+
+# python3 /www/mdserver-web/plugins/gdrive/index.py site zzzvps.com 3
+# python3 /www/mdserver-web/plugins/gdrive/index.py database t1 3
+# python3 /www/server/mdserver-web/plugins/msonedrive/index.py path
+# /Users/midoks/Desktop/dev/python 3
+
+
+import sys
+import io
+import os
+import time
+import re
+import json
+
+
+sys.path.append(os.getcwd() + "/class/core")
+import mw
+
+
+def getPluginName():
+ return 'gdrive'
+
+
+def getPluginDir():
+ return mw.getPluginDir() + '/' + getPluginName()
+
+
+def getServerDir():
+ return mw.getServerDir() + '/' + getPluginName()
+
+
+# print(getPluginDir())
+sys.path.append(getPluginDir() + "/class")
+from gdriveclient import gdriveclient
+
+
+gd = gdriveclient(getPluginDir(), getServerDir())
+gd.setDebug(True)
+# sign_in_url, state = gd.get_sign_in_url()
+# print(sign_in_url)
+
+# url = 'https://localhost/?state=GH2YZ1VeytzVB1BqJJpQZIBk2GGAub&code=4/0Adeu5BXnD2dQvXx8Sg0WPn1XiMpihcRBNaG1yaFKIo86gUiG7q65KU1MaNCxrj_f2bjkwQ&scope=https://www.googleapis.com/auth/drive.file'
+# t = gd.set_auth_url(url)
+# print(t)
+
+# def set_auth_url(url):
+# try:
+# if url.startswith("http://"):
+# url = url.replace("http://", "https://")
+# token = msodc.get_token_from_authorized_url(
+# authorized_url=url)
+# msodc.store_token(token)
+# msodc.store_user()
+# return mw.returnJson(True, "授权成功!")
+# except Exception as e:
+# print(e)
+# return mw.returnJson(False, "授权失败2!:" + str(e))
+# return mw.returnJson(False, "授权失败!:" + str(e))
+
+# url = 'http://localhost/?code=M.C106_BAY.2.310112f3-a158-c400-9667-d158cbd1de6c&state=jEJz0ucR9bpZYD9PGxp2GgRDotrzO6'
+# token = set_auth_url(url)
+# print(token)
+
+# token = msodc.get_token()
+# print("token:", token)
+
+# t = gd.get_list('')
+# print(t)
+
+# t = gd.get_id_list('1u7LjXGj1KoN-ltAdTRaib7IZJpsEnPdz')
+# print(t)
+
+# t = gd.create_folder('backup_demo')
+# print(t)
+
+# t = msodc.delete_object('backup')
+# print(t)
+
+
+# t = gd.upload_file('web_t1.cn_20230830_134549.tar.gz', 'site')
+# print(t)
+# print(gd.error_msg)
+
+# /Users/midoks/Desktop/mwdev/server/mdserver-web/paramiko.log
+# backup/site/paramiko.log
+# |-正在上传到 backup/site/paramiko.log...
+# True
+
+
+t = gd.upload_file(
+ 'db_zzzvps_20230830_210727.sql.gz', 'datebase')
+print(t)
diff --git a/plugins/msonedrive/class/msodclient.py b/plugins/msonedrive/class/msodclient.py
index d17efab9c..2181caf66 100644
--- a/plugins/msonedrive/class/msodclient.py
+++ b/plugins/msonedrive/class/msodclient.py
@@ -406,6 +406,11 @@ class msodclient:
"database": "db",
"path": "path",
}
+
+ if not prefix_dict.get(data_type):
+ print("data_type 类型错误!!!")
+ exit(1)
+
file_regx = prefix_dict.get(data_type) + "_(.+)_20\d+_\d+(?:\.|_)"
sub_search = re.search(file_regx, file_name)
sub_path_name = ""