简道云fastapi
This commit is contained in:
@@ -0,0 +1,298 @@
|
||||
import requests
|
||||
from urllib.parse import quote
|
||||
import pandas as pd
|
||||
import os
|
||||
import urllib.parse
|
||||
from datetime import datetime
|
||||
from app.api import API
|
||||
from typing import Optional, Dict, Any, Tuple
|
||||
from app.config import Config
|
||||
from app.module import F6Module
|
||||
import threading
|
||||
from app import back_ground_tasks
|
||||
|
||||
api_instance = API()
|
||||
|
||||
|
||||
class F6PluginModule:
|
||||
|
||||
@staticmethod
|
||||
def accept_file(data: Dict[str, Any]) -> Tuple[Optional[str], Dict[str, Any]]: # 接收文件
|
||||
"""
|
||||
接收文件。
|
||||
|
||||
此方法用于处理前端上传的文件,下载文件并保存到指定目录。主要步骤包括:
|
||||
1. 处理前端传递的数据,获取文件的URL。
|
||||
2. 解析URL以获取文件名。
|
||||
3. 根据当前时间生成新的文件名,以避免文件名冲突。
|
||||
4. 下载文件并保存到指定目录。
|
||||
5. 返回文件保存路径和处理后的数据。
|
||||
|
||||
Args:
|
||||
data (dict): 包含文件URL和其他必要信息的字典。
|
||||
|
||||
Returns:
|
||||
tuple: 包含文件保存路径和处理后的数据的元组。如果文件保存成功,返回保存路径和数据;如果失败,返回 None 和数据。
|
||||
"""
|
||||
data = api_instance.entry_data_get(data=data)
|
||||
print(data)
|
||||
try:
|
||||
# 安全地访问附件信息
|
||||
data_dict = data.get('data', {})
|
||||
attachments = data_dict.get('附件', [])
|
||||
|
||||
if not attachments or len(attachments) == 0:
|
||||
print('上传url未读取到,或无上传文件: 附件列表为空')
|
||||
save_path = ''
|
||||
return save_path, data
|
||||
|
||||
first_attachment = attachments[0]
|
||||
url = first_attachment.get('url')
|
||||
|
||||
if not url:
|
||||
print('上传url未读取到,或无上传文件: URL为空')
|
||||
save_path = ''
|
||||
return save_path, data
|
||||
|
||||
print(url)
|
||||
except (KeyError, IndexError, TypeError) as e:
|
||||
print(f'上传url未读取到,或无上传文件:{e}')
|
||||
save_path = ''
|
||||
return save_path, data
|
||||
|
||||
parsed_url = urllib.parse.urlparse(url)
|
||||
query_params = urllib.parse.parse_qs(parsed_url.query)
|
||||
attname = query_params.get('attname', [''])[0]
|
||||
filename = urllib.parse.unquote(attname)
|
||||
|
||||
# 获取当前时间并格式化为指定格式的字符串
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H-%M-%S")
|
||||
|
||||
# 分离文件名和扩展名
|
||||
name_part, ext_part = filename.rsplit('.', 1) if '.' in filename else (filename, '')
|
||||
|
||||
# 构建新文件名
|
||||
new_filename = f"{name_part}{timestamp}.{ext_part}" if ext_part else f"{name_part}{timestamp}"
|
||||
|
||||
save_path = os.path.join(Config.SAVE_DIRECTORY, new_filename)
|
||||
print(save_path)
|
||||
response = requests.get(url, stream=True)
|
||||
|
||||
if response.status_code == 200:
|
||||
with open(save_path, 'wb') as file:
|
||||
for chunk in response.iter_content(chunk_size=1024):
|
||||
if chunk:
|
||||
file.write(chunk)
|
||||
return save_path, data
|
||||
else:
|
||||
return None, data
|
||||
|
||||
|
||||
def check_file(self, data: Dict[str, Any]) -> Dict[str, str]: # 校验上传文件
|
||||
"""
|
||||
校验上传文件。
|
||||
|
||||
此方法负责接收前端上传的文件,并根据文件类型和操作指令进行相应的校验。主要步骤包括:
|
||||
1. 调用 `accept_file` 方法处理前端传递的数据,获取文件保存路径和处理后的数据。
|
||||
2. 根据数据中的 `Action` 字段判断需要执行的操作类型。
|
||||
3. 如果文件保存路径有效,继续执行以下步骤:
|
||||
- 如果操作类型为 `create_brand`,则读取文件和模板文件,校验文件格式是否正确。
|
||||
- 如果文件格式正确,返回成功消息;否则返回错误消息。
|
||||
4. 如果文件保存路径无效,返回相应的错误消息。
|
||||
5. 如果读取文件过程中发生异常,捕获异常并返回错误消息。
|
||||
|
||||
Args:
|
||||
data (dict): 前端请求发送过来的数据,包含文件信息和其他必要参数。
|
||||
|
||||
Returns:
|
||||
dict: 包含文件校验结果的消息字典。如果校验成功,则返回文件路径和校验标志;如果失败,则返回错误消息。
|
||||
"""
|
||||
save_path, data1 = self.accept_file(data)
|
||||
|
||||
# 安全地获取 Action 字段
|
||||
data_dict = data1.get('data', {})
|
||||
action = data_dict.get('Action(隐藏)')
|
||||
|
||||
if not action:
|
||||
return {'msg': '缺少Action字段,无法校验文件'}
|
||||
|
||||
if save_path:
|
||||
try:
|
||||
if action == 'create_brand':
|
||||
df1 = pd.read_excel(save_path, sheet_name=0)
|
||||
if "品牌" in df1.columns[0]: # 校验表头名字
|
||||
print('文件校验成功')
|
||||
return {'msg': f'{save_path}', 'check': '是'}
|
||||
else:
|
||||
print("'msg':'文件上传格式错误'")
|
||||
return {'msg': '文件上传格式错误'}
|
||||
elif action == 'modify_customer_info':
|
||||
df = pd.read_excel(save_path, sheet_name=0)
|
||||
if "客户手机号" in df.columns[0]: # 校验表头名字
|
||||
print('文件校验成功')
|
||||
return {'msg': f'{save_path}', 'check': '是'}
|
||||
else:
|
||||
print("'msg':'文件上传格式错误'")
|
||||
return {'msg': '文件上传格式错误'}
|
||||
elif action == 'delete_cars':
|
||||
pass
|
||||
else:
|
||||
pass
|
||||
|
||||
except Exception as e:
|
||||
return {'msg': f'读取Excel文件失败: {str(e)},文件路径:{save_path}'}
|
||||
|
||||
else:
|
||||
return {'msg': '当前节点无附件上传', 'check': '是'}
|
||||
|
||||
|
||||
@staticmethod
|
||||
def create_brand(data: Dict[str, Any]) -> Dict[str, str]: # 创建品牌
|
||||
entry_data = api_instance.entry_data_get(data=data)
|
||||
print('执行 品牌批量新建')
|
||||
username = entry_data['data']['账号']
|
||||
password = entry_data['data']['密码']
|
||||
company_name = entry_data['data']['公司名称']
|
||||
save_path = entry_data['data']['文件保存地址']
|
||||
|
||||
login_response = F6Module.login_in(username, password, company_name)
|
||||
if login_response is None:
|
||||
return {'msg': '登录失败'}
|
||||
|
||||
try:
|
||||
df = pd.read_excel(save_path, sheet_name=0, dtype='string')
|
||||
except Exception as e:
|
||||
return {'msg': f'读取Excel文件失败: {str(e)},文件路径:{save_path}'}
|
||||
|
||||
cookies = requests.utils.dict_from_cookiejar(login_response.cookies)
|
||||
|
||||
try:
|
||||
thread = threading.Thread(target=back_ground_tasks.create_brand_background,
|
||||
args=(data, cookies, df, save_path))
|
||||
thread.start()
|
||||
except Exception as e:
|
||||
print(f'创建线程失败: {str(e)}')
|
||||
|
||||
return {'msg': '正在执行', 'msg_details': '正在执行,请稍后看结果'}
|
||||
|
||||
@staticmethod
|
||||
def delete_history(data: Dict[str, Any]) -> Dict[str, str]:
|
||||
entry_data = api_instance.entry_data_get(data=data)
|
||||
username = entry_data['data']['账号']
|
||||
password = entry_data['data']['密码']
|
||||
company_name = entry_data['data']['公司名称']
|
||||
org_name = entry_data['data']['门店名称']
|
||||
|
||||
login_response = F6Module.login_in(username, password, company_name)
|
||||
if login_response is None:
|
||||
return {'msg': '未执行', 'msg_details': '登录失败'}
|
||||
|
||||
cookies = requests.utils.dict_from_cookiejar(login_response.cookies)
|
||||
|
||||
url = 'https://yunxiu.f6car.cn/hive/org/getPageOrgGroupMembers?currentPage=1&pageSize=1000&name='
|
||||
res = requests.get(url=url, cookies=cookies)
|
||||
store_data = res.json()
|
||||
org_lists = store_data['data']['list']
|
||||
|
||||
org_id = ''
|
||||
org_name1 = ''
|
||||
for org in org_lists:
|
||||
org_name1 = org['orgName']
|
||||
if org_name == org['orgName']:
|
||||
org_id = org['orgId']
|
||||
|
||||
if org_id:
|
||||
thread = threading.Thread(target=back_ground_tasks.delete_history_background,
|
||||
args=(data, cookies, org_id, org_name1))
|
||||
thread.start()
|
||||
return {'msg': '正在执行中', 'msg_details': '请稍后查看结果'}
|
||||
else:
|
||||
return {'msg': '未执行', 'msg_details': '门店信息错误'}
|
||||
|
||||
@staticmethod
|
||||
def delete_customer(data):
|
||||
entry_data = api_instance.entry_data_get(data=data)
|
||||
username = entry_data['data']['账号']
|
||||
password = entry_data['data']['密码']
|
||||
company_name = entry_data['data']['公司名称']
|
||||
|
||||
res = F6Module.login_in(username, password, company_name)
|
||||
|
||||
if res is not None:
|
||||
cookies = requests.utils.dict_from_cookiejar(res.cookies)
|
||||
url = "https://yunxiu.f6car.cn/member/customer/listForPermission?pageSize=50000&pageNo=1"
|
||||
res = requests.get(url, cookies=cookies)
|
||||
json = res.json()
|
||||
|
||||
if json:
|
||||
thread = threading.Thread(target=back_ground_tasks.delete_customer_background,
|
||||
args=(data, cookies, json['data']['data'],))
|
||||
thread.start()
|
||||
return {'msg': '正在执行中', 'msg_details': '8-20点3.5s一条数据,其余时间1.5s一条数据'}
|
||||
else:
|
||||
return {'msg': '未执行', 'msg_details': '无客户信息'}
|
||||
else:
|
||||
return {'msg': '未执行', 'msg_details': '登录失败'}
|
||||
|
||||
@staticmethod
|
||||
def delete_cars(data):
|
||||
entry_data = api_instance.entry_data_get(data=data)
|
||||
username = entry_data['data']['账号']
|
||||
password = entry_data['data']['密码']
|
||||
company_name = entry_data['data']['公司名称']
|
||||
|
||||
res = F6Module.login_in(username, password, company_name)
|
||||
|
||||
if res is not None:
|
||||
cookies = requests.utils.dict_from_cookiejar(res.cookies)
|
||||
url = "https://yunxiu.f6car.cn/member/car/carListForPermission"
|
||||
header = {
|
||||
'Referer': 'https://yunxiu.f6car.cn/erp/view/index.html',
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
'Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0'
|
||||
}
|
||||
|
||||
json_data = {"pageSize": 100, "pageNo": 1}
|
||||
res = requests.post(url=url, cookies=cookies, json=json_data, headers=header)
|
||||
res_data = res.json()
|
||||
total_items = int(res_data["data"]['total'])
|
||||
all_page = total_items // 100 + (total_items % 100 > 0)
|
||||
|
||||
if res_data:
|
||||
thread = threading.Thread(target=back_ground_tasks.delete_car_background,
|
||||
args=(data, url, cookies, header, all_page))
|
||||
thread.start()
|
||||
return {'msg': '正在执行中', 'msg_details': '8-20点3.5s一条数据,其余时间1.5s一条数据'}
|
||||
else:
|
||||
return {'msg': '未执行', 'msg_details': '无客户车辆信息'}
|
||||
|
||||
else:
|
||||
return {'msg': '未执行', 'msg_details': '登录失败'}
|
||||
|
||||
def modify_customer_info(self, data: Dict[str, str]):
|
||||
entry_data = api_instance.entry_data_get(data=data)
|
||||
username = entry_data['data']['账号']
|
||||
password = entry_data['data']['密码']
|
||||
company_name = entry_data['data']['公司名称']
|
||||
save_path = entry_data['data']['文件保存地址']
|
||||
|
||||
login_response = F6Module.login_in(username, password, company_name)
|
||||
if login_response is None:
|
||||
return {'msg': '未执行', 'msg_details': '登录失败'}
|
||||
|
||||
cookies = requests.utils.dict_from_cookiejar(login_response.cookies)
|
||||
|
||||
try:
|
||||
df = pd.read_excel(save_path, sheet_name=0, dtype='string')
|
||||
except Exception as e:
|
||||
return {'msg': f'读取Excel文件失败: {str(e)},文件路径:{save_path}'}
|
||||
|
||||
if cookies:
|
||||
thread = threading.Thread(target=back_ground_tasks.modify_customer_info_background,
|
||||
args=(data, cookies, df, save_path))
|
||||
thread.start()
|
||||
return {'msg': '正在执行中', 'msg_details': '请稍后查看结果'}
|
||||
else:
|
||||
return {'msg': '未执行', 'msg_details': 'cookies获取失败'}
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
__all__ = []
|
||||
|
||||
@@ -0,0 +1,226 @@
|
||||
import requests
|
||||
import hashlib
|
||||
from urllib.parse import quote
|
||||
from datetime import datetime
|
||||
from app.api import API
|
||||
from typing import Optional, Dict, AnyStr
|
||||
from PIL import Image, ImageEnhance
|
||||
import pytesseract
|
||||
import logging
|
||||
from datetime import datetime
|
||||
|
||||
api_instance = API()
|
||||
logger = logging.getLogger('app')
|
||||
|
||||
|
||||
class F6Module:
|
||||
@staticmethod
|
||||
def get_captcha() -> AnyStr:
|
||||
captcha_url = 'https://yunxiu.f6car.cn/kzf6/login/captcha-image'
|
||||
response = requests.get(captcha_url)
|
||||
with open('captcha.png', 'wb') as f:
|
||||
f.write(response.content)
|
||||
|
||||
image = Image.open('captcha.png')
|
||||
enhancer = ImageEnhance.Contrast(image)
|
||||
image = enhancer.enhance(2.0)
|
||||
enhancer = ImageEnhance.Brightness(image)
|
||||
image = enhancer.enhance(1.5)
|
||||
image = image.convert('L')
|
||||
image = image.point(lambda x: 0 if x < 128 else 255, '1')
|
||||
image.save('preprocessed_captcha.png')
|
||||
|
||||
captcha_text = pytesseract.image_to_string(image)
|
||||
print(f"识别的验证码为: {captcha_text}")
|
||||
|
||||
return captcha_text
|
||||
|
||||
@staticmethod
|
||||
def login_in(username: str, password: str, company_name: str = '默认门店',) -> Optional[requests.Response]:
|
||||
url = "https://yunxiu.f6car.com/kzf6/login/confirm"
|
||||
session = requests.Session()
|
||||
header = {
|
||||
'Referer': url,
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
'Chrome/130.0.0.0 Safari/537.36 Edg/129.0.0.0'
|
||||
}
|
||||
data = {
|
||||
'username': username,
|
||||
'password': hashlib.md5(password.encode('utf-8')).hexdigest(),
|
||||
}
|
||||
try:
|
||||
res = session.post(url=url, headers=header, data=data)
|
||||
res_json = res.json()
|
||||
|
||||
if res_json.get('message') == '请输入图形验证码':
|
||||
logger.warning("触发图形验证码")
|
||||
captcha_text = F6Module.get_captcha()
|
||||
data.update({'imageCode': captcha_text})
|
||||
res = session.post(url=url, headers=header, data=data)
|
||||
res_json = res.json()
|
||||
|
||||
if res_json.get("data") is None:
|
||||
return res
|
||||
else:
|
||||
group_id = ''
|
||||
for group in res_json.get('data', []):
|
||||
if group.get("groupName") == company_name:
|
||||
group_id = group.get("groupId")
|
||||
break
|
||||
|
||||
if not group_id:
|
||||
logger.error(f"未找到公司名称: {company_name}")
|
||||
return None
|
||||
|
||||
token = quote(res_json['token']) # URL 编码
|
||||
url = (f'https://yunxiu.f6car.cn/kzf6/user/loginAfterChooseGroup?'
|
||||
f'token={token}&groupId={group_id}&macAddress=')
|
||||
res1 = session.get(url, cookies=res.cookies)
|
||||
return res1
|
||||
except Exception as e:
|
||||
print(f"Error during login: {e}")
|
||||
return None
|
||||
|
||||
def accept_login_message(self, data: Dict[str, str]) -> Dict[str, str]:
|
||||
username = data['username']
|
||||
password = data['password']
|
||||
company_name = data['company_name']
|
||||
|
||||
res = self.login_in(username, password, company_name)
|
||||
|
||||
if res is not None:
|
||||
cookies = requests.utils.dict_from_cookiejar(res.cookies)
|
||||
json = res.json()
|
||||
url = 'https://yunxiu.f6car.cn/hive/company/getGroupName'
|
||||
res1 = requests.get(url=url, cookies=cookies)
|
||||
data1 = res1.json()
|
||||
|
||||
if data1['code'] == 200:
|
||||
if data1['data'] == company_name:
|
||||
if json['status'] == 'success':
|
||||
json['status'] = '登录成功'
|
||||
elif json['status'] == 'Error':
|
||||
json['status'] = '登录失败,请检查账号密码'
|
||||
else:
|
||||
json['status'] = '公司名称不正确或未选择公司名称,请重试'
|
||||
else:
|
||||
json['status'] = '请输入正确的账号密码并选择公司名称'
|
||||
return json
|
||||
else:
|
||||
return {"status": "登录失败,请检查公司名称"}
|
||||
|
||||
def get_company_information(self, data: Dict[str, str]) -> Dict[str, str]:
|
||||
username = data['username']
|
||||
password = data['password']
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H-%M-%S")
|
||||
print(username)
|
||||
|
||||
url = "https://yunxiu.f6car.com/kzf6/login/confirm"
|
||||
session = requests.Session()
|
||||
header = {
|
||||
'Referer': url,
|
||||
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
|
||||
'Chrome/130.0.0.0 Safari/537.36 Edg/130.0.0.0'
|
||||
}
|
||||
data = {
|
||||
'username': username,
|
||||
'password': hashlib.md5(password.encode('utf-8')).hexdigest(),
|
||||
}
|
||||
|
||||
try:
|
||||
res = session.post(url=url, headers=header, data=data)
|
||||
res_json = res.json()
|
||||
|
||||
if res_json.get('message') == '请输入图形验证码':
|
||||
pass
|
||||
|
||||
jiandaoyun_data = {'api_key': '6694d3c4fcb69ca9a111a6c4', 'entry_id': '6736e2112ad50045f041a827'}
|
||||
|
||||
if res_json.get("data") is None:
|
||||
print('单店')
|
||||
res = self.login_in(username, password)
|
||||
if res is not None:
|
||||
cookies = requests.utils.dict_from_cookiejar(res.cookies)
|
||||
url = 'https://yunxiu.f6car.cn/hive/company/getGroupName'
|
||||
res = requests.get(url=url, cookies=cookies)
|
||||
data = res.json()
|
||||
store_name = data['data']
|
||||
|
||||
jiandaoyun_data['data_list'] = [
|
||||
{"_widget_1731650067055": {"value": f'{username}{password}{timestamp}'},
|
||||
"_widget_1731650067056": {"value": f"{store_name}"}}]
|
||||
api_instance.entry_data_batch_create(jiandaoyun_data)
|
||||
res = {'msg': f'{username}{password}{timestamp}'}
|
||||
else:
|
||||
jiandaoyun_data_list = []
|
||||
for group in res_json.get('data', []):
|
||||
append_data = {"_widget_1731650067055": {"value": f'{username}{password}{timestamp}'},
|
||||
"_widget_1731650067056": {"value": f"{group['groupName']}"}}
|
||||
jiandaoyun_data_list.append(append_data)
|
||||
|
||||
jiandaoyun_data['data_list'] = jiandaoyun_data_list
|
||||
|
||||
res = api_instance.entry_data_batch_create(jiandaoyun_data)
|
||||
|
||||
print(res)
|
||||
|
||||
res = {'msg': f'{username}{password}{timestamp}'}
|
||||
return res
|
||||
|
||||
except Exception as e:
|
||||
print(f"获取公司名称失败: {e}")
|
||||
res = {'msg': '获取公司名称失败,请重新获取'}
|
||||
return res
|
||||
|
||||
def get_store_information(self, data: Dict[str, str]) -> Dict[str, dict[str, str]]:
|
||||
username = data['username']
|
||||
password = data['password']
|
||||
company_name = data['company_name']
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H-%M-%S")
|
||||
|
||||
login_response = self.login_in(username, password, company_name)
|
||||
if login_response is None:
|
||||
return {"msg": {'msg': '未执行', 'msg_details': '登录失败'}}
|
||||
|
||||
cookies = requests.utils.dict_from_cookiejar(login_response.cookies)
|
||||
|
||||
url = 'https://yunxiu.f6car.cn/hive/org/getPageOrgGroupMembers?currentPage=1&pageSize=100&name='
|
||||
res = requests.get(url=url, cookies=cookies)
|
||||
data = res.json()
|
||||
org_lists = data['data']['list']
|
||||
|
||||
url = 'https://yunxiu.f6car.cn/member/car/carListForPermission'
|
||||
json = {"pageSize": 10, "pageNo": 1}
|
||||
car_res = requests.post(url=url, json=json, cookies=cookies)
|
||||
total_cars_data = car_res.json()
|
||||
total_cars = total_cars_data['data']['total']
|
||||
|
||||
url = 'https://yunxiu.f6car.cn/member/customer/listForPermission?pageSize=10&pageNo=1'
|
||||
customer_res = requests.get(url=url, cookies=cookies)
|
||||
total_customer_data = customer_res.json()
|
||||
total_customer = total_customer_data['data']['total']
|
||||
|
||||
jiandaoyun_data = {'api_key': '6694d3c4fcb69ca9a111a6c4',
|
||||
'entry_id': '673c38ccca57a5cf266eb18c'}
|
||||
|
||||
jiandaoyun_data_list = []
|
||||
for org in org_lists:
|
||||
append_data = {"_widget_1731999948708": {"value": f'{username}{password}{company_name}{timestamp}'},
|
||||
"_widget_1731999948709": {"value": f"{org['orgName']}"}}
|
||||
jiandaoyun_data_list.append(append_data)
|
||||
|
||||
jiandaoyun_data['data_list'] = jiandaoyun_data_list
|
||||
|
||||
api_instance.entry_data_batch_create(jiandaoyun_data)
|
||||
|
||||
res = {'msg': {"msg": f'{username}{password}{company_name}{timestamp}',
|
||||
"total_cars": f"{total_cars}条客户车辆",
|
||||
"total_customer": f"{total_customer}条客户"}}
|
||||
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def get_keep_heart(data: Dict[str, str]) -> Dict[str, str]:
|
||||
return data
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import requests
|
||||
from urllib.parse import quote
|
||||
import pandas as pd
|
||||
import os
|
||||
import urllib.parse
|
||||
from datetime import datetime
|
||||
from app.api import API
|
||||
from typing import Optional, Dict, Any, Tuple
|
||||
from app.config import Config
|
||||
from app.module import F6Module
|
||||
import threading
|
||||
from app import back_ground_tasks
|
||||
|
||||
api_instance = API()
|
||||
|
||||
|
||||
class OtherPluginModule:
|
||||
|
||||
def sms_signature_status(self):
|
||||
pass
|
||||
|
||||
|
||||
Reference in New Issue
Block a user