linkkf 로직수정중

This commit is contained in:
2025-12-25 19:42:32 +09:00
parent 695d26767e
commit af9a38a973
128 changed files with 8711 additions and 1484 deletions

BIN
lib/.DS_Store vendored Normal file

Binary file not shown.

BIN
lib/framework/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -1,25 +1,70 @@
try:
import yaml
except:
import os
try:
os.system("pip install pyyaml")
except:
pass
from .init_main import Framework
from .version import VERSION
frame = Framework.get_instance()
F = frame
logger = frame.logger
app = frame.app
celery = frame.celery
db = frame.db
scheduler = frame.scheduler
socketio = frame.socketio
path_app_root = frame.path_app_root
path_data = frame.path_data
get_logger = frame.get_logger
# 2024.06.13
# 잘못된 설계로 인해 import 만으로 초기화 되버려 lib을 사용할 수 없다.
# 분리.
F = None
frame = None
logger = None
app = None
celery = None
db = None
scheduler = None
socketio = None
rd = None
path_app_root = None
path_data = None
get_logger = None
SystemModelSetting = None
get_cache = None
def initiaize():
global F
global frame
global logger
global app
global celery
global db
global scheduler
global socketio
global path_app_root
global path_data
global get_logger
global SystemModelSetting
global get_cache
F = Framework.get_instance()
frame = F
logger = frame.logger
app = frame.app
celery = frame.celery
db = frame.db
scheduler = frame.scheduler
socketio = frame.socketio
rd = frame.rd
path_app_root = frame.path_app_root
path_data = frame.path_data
get_logger = frame.get_logger
frame.initialize_system()
from system.setup import SystemModelSetting as SS
SystemModelSetting = SS
frame.initialize_plugin()
return frame
from flask_login import login_required
from support import d
from .init_declare import User, check_api
from .scheduler import Job
frame.initialize_system()
from system.setup import SystemModelSetting
frame.initialize_plugin()

View File

@@ -0,0 +1,73 @@
import redis
class _RedisManager:
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super().__new__(cls)
return cls._instance
def __init__(self, host='localhost', port=6379):
if hasattr(self, 'redis_client'):
return
try:
self.redis_client = redis.Redis(host=host, port=port, db=1, decode_responses=True)
self.redis_client.ping()
self.is_redis = True
except redis.exceptions.ConnectionError:
self.is_redis = False
self.cache_backend = {} # Redis 실패 시 메모리 캐시 사용
def set(self, key, value, ex=None):
if self.is_redis:
self.redis_client.set(key, value, ex=ex)
else:
self.cache_backend[key] = value
def get(self, key):
if self.is_redis:
return self.redis_client.get(key)
else:
return self.cache_backend.get(key)
def delete(self, key):
if self.is_redis:
self.redis_client.delete(key)
else:
if key in self.cache_backend:
del self.cache_backend[key]
#_redis_manager_instance = _RedisManager()
class NamespacedCache:
def __init__(self, namespace):
self._manager = _RedisManager._instance
self.namespace = namespace
def _make_key(self, key):
# 'plugin_name:key' 형식으로 실제 키를 생성
return f"{self.namespace}:{key}"
def set(self, key, value, ex=None):
full_key = self._make_key(key)
self._manager.set(full_key, value, ex=ex)
def get(self, key):
full_key = self._make_key(key)
return self._manager.get(full_key)
def delete(self, key):
full_key = self._make_key(key)
self._manager.delete(full_key)
def get_cache(plugin_name: str) -> NamespacedCache:
"""
플러그인 이름을 기반으로 네임스페이스가 적용된 캐시 객체를 반환합니다.
"""
if not plugin_name:
raise ValueError("플러그인 이름은 필수입니다.")
return NamespacedCache(plugin_name)

View File

@@ -13,13 +13,13 @@ def check_api(original_function):
#logger.warning(request.url)
#logger.warning(request.form)
try:
if F.SystemModelSetting.get_bool('auth_use_apikey'):
if request.method == 'POST':
apikey = request.form['apikey']
else:
apikey = request.args.get('apikey')
#apikey = request.args.get('apikey')
if apikey is None or apikey != F.SystemModelSetting.get('auth_apikey'):
if F.SystemModelSetting.get_bool('use_apikey'):
try:
d = request.get_json()
except Exception:
d = request.form.to_dict() if request.method == 'POST' else request.args.to_dict()
apikey = d.get('apikey')
if apikey is None or apikey != F.SystemModelSetting.get('apikey'):
F.logger.warning('CHECK API : ABORT no match ({})'.format(apikey))
F.logger.warning(request.environ.get('HTTP_X_REAL_IP', request.remote_addr))
abort(403)
@@ -31,7 +31,7 @@ def check_api(original_function):
return original_function(*args, **kwargs) #2
return wrapper_function
# Suuport를 logger 생성전에 쓰지 않기 위해 중복 선언
# Support를 logger 생성전에 쓰지 않기 위해 중복 선언
import logging
@@ -47,7 +47,7 @@ class CustomFormatter(logging.Formatter):
# pathname filename
#format = "[%(asctime)s|%(name)s|%(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
__format = '[{yellow}%(asctime)s{reset}|{color}%(levelname)s{reset}|{green}%(name)s{reset} %(pathname)s:%(lineno)s] {color}%(message)s{reset}' if os.environ.get('LOGGER_PATHNAME', "False") == "True" else '[{yellow}%(asctime)s{reset}|{color}%(levelname)s{reset}|{green}%(name)s{reset} %(filename)s:%(lineno)s] {color}%(message)s{reset}'
__format = '[{yellow}%(asctime)s{reset}|{color}%(levelname)s{reset}|{green}%(name)s{reset}|%(pathname)s:%(lineno)s] {color}%(message)s{reset}' if os.environ.get('LOGGER_PATHNAME', "False") == "True" else '[{yellow}%(asctime)s{reset}|{color}%(levelname)s{reset}|{green}%(name)s{reset}|%(filename)s:%(lineno)s] {color}%(message)s{reset}'
FORMATS = {
logging.DEBUG: __format.format(color=grey, reset=reset, yellow=yellow, green=green),

View File

@@ -8,14 +8,15 @@ import time
import traceback
from datetime import datetime
import redis
import yaml
from flask import Flask
from flask_cors import CORS
from flask_login import LoginManager, login_required
from flask_socketio import SocketIO
from flask_sqlalchemy import SQLAlchemy
from flaskext.markdown import Markdown
from pytz import timezone, utc
from werkzeug.middleware.proxy_fix import ProxyFix
from .init_declare import CustomFormatter, check_api
@@ -37,12 +38,15 @@ class Framework:
self.db = None
self.scheduler = None
self.socketio = None
self.rd = None
self.path_app_root = None
self.path_data = None
self.users = {}
self.get_cache = None
self.__level_unset_logger_list = []
self.__logger_list = []
self.all_log_filehandler = None
self.__exit_code = -1
self.login_manager = None
#self.plugin_instance_list = {}
@@ -59,14 +63,17 @@ class Framework:
def __initialize(self):
os.environ["PYTHONUNBUFFERED"] = "1"
os.environ['FF'] = "true"
os.environ['FF_PYTHON'] = sys.executable
self.__config_initialize("first")
self.__make_default_dir()
self.logger = self.get_logger(__package__)
self.get_logger('support')
import support
self.__prepare_starting()
self.app = Flask(__name__)
self.app.wsgi_app = ProxyFix(self.app.wsgi_app, x_proto=1)
self.__config_initialize('flask')
self.__init_db()
@@ -82,7 +89,6 @@ class Framework:
self.socketio = SocketIO(self.app, cors_allowed_origins="*", async_mode='threading')
CORS(self.app)
Markdown(self.app)
self.login_manager = LoginManager()
self.login_manager.init_app(self.app)
@@ -94,10 +100,11 @@ class Framework:
self.app.config.update(
DROPZONE_MAX_FILE_SIZE = 102400,
DROPZONE_TIMEOUT = 5*60*1000,
#DROPZONE_ALLOWED_FILE_CUSTOM = True,
#DROPZONE_ALLOWED_FILE_TYPE = 'default, image, audio, video, text, app, *.*',
DROPZONE_ALLOWED_FILE_CUSTOM = True,
DROPZONE_ALLOWED_FILE_TYPE = "image/*, audio/*, video/*, text/*, application/*, *.*",
)
self.dropzone = Dropzone(self.app)
def __init_db(self):
@@ -131,19 +138,20 @@ class Framework:
def __init_celery(self):
redis_port = 6379
try:
from celery import Celery
#if frame.config['use_celery'] == False or platform.system() == 'Windows':
if self.config['use_celery'] == False:
raise Exception('no celery')
raise Exception('use_celery=False')
from celery import Celery
redis_port = os.environ.get('REDIS_PORT', None)
if redis_port == None:
redis_port = self.config.get('redis_port', None)
if redis_port == None:
redis_port = '6379'
self.config['redis_port'] = redis_port
self.rd = redis.StrictRedis(host='localhost', port=redis_port, db=0)
if self.config['use_celery'] == False:
raise Exception('no celery')
self.app.config['CELERY_BROKER_URL'] = 'redis://localhost:%s/0' % redis_port
self.app.config['CELERY_RESULT_BACKEND'] = 'redis://localhost:%s/0' % redis_port
@@ -166,6 +174,7 @@ class Framework:
F.logger.info(f"celery running_type: {running_type}")
#F.logger.info(f"celery running_type: {options}")
celery.steps['worker'].add(CustomArgs)
except Exception as e:
if self.config['use_celery']:
self.logger.error('CELERY!!!')
@@ -187,6 +196,14 @@ class Framework:
if len(args) > 0 and type(args[0]) == type(dummy_func):
return args[0]
self.f(*args, **kwargs)
try:
from .init_cache_manager import _RedisManager, get_cache
_RedisManager(host='localhost', port=redis_port)
self.get_cache = get_cache
except Exception as e:
self.logger.error(f"get_cache import error: {str(e)}")
self.get_cache = None
return celery
@@ -201,11 +218,13 @@ class Framework:
self.logger.error(f'Exception:{str(e)}')
self.logger.error(traceback.format_exc())
self.SystemModelSetting = SystemInstance.ModelSetting
SystemInstance.plugin_load()
if self.config['run_flask']:
SystemInstance.plugin_load()
self.app.register_blueprint(SystemInstance.blueprint)
self.config['flag_system_loading'] = True
self.__config_initialize('member')
self.__config_initialize('system_loading_after')
self.set_level(self.SystemModelSetting.get_int('log_level'))
def initialize_plugin(self):
@@ -232,6 +251,7 @@ class Framework:
self.__make_default_logger()
self.__config_initialize("last")
self.config['loading_completed'] = True
self.logger.info('### LAST')
self.logger.info(f"### PORT: {self.config.get('port')}")
self.logger.info('### Now you can access App by webbrowser!!')
@@ -248,6 +268,7 @@ class Framework:
def __config_initialize(self, mode):
if mode == "first":
self.config = {}
self.config['loading_completed'] = False
self.config['os'] = platform.system()
self.config['flag_system_loading'] = False
#self.config['run_flask'] = True if sys.argv[0].endswith('main.py') else False
@@ -263,6 +284,8 @@ class Framework:
self.config['export_filepath'] = os.path.join(self.config['path_app'], 'export.sh')
self.config['exist_export'] = os.path.exists(self.config['export_filepath'])
self.config['recent_version'] = '--'
from .version import VERSION
self.config['version'] = VERSION
self.__process_args()
self.__load_config()
self.__init_define()
@@ -270,7 +293,7 @@ class Framework:
self.config['notify_yaml_filepath'] = os.path.join(self.config['path_data'], 'db', 'notify.yaml')
if 'running_type' not in self.config:
self.config['running_type'] = 'native'
self.pip_install()
elif mode == "flask":
self.app.secret_key = os.urandom(24)
self.app.config['TEMPLATES_AUTO_RELOAD'] = True
@@ -295,8 +318,8 @@ class Framework:
self.config['DEFINE'] = {}
# 이건 필요 없음
self.config['DEFINE']['GIT_VERSION_URL'] = 'https://raw.githubusercontent.com/flaskfarm/flaskfarm/main/lib/framework/version.py'
self.config['DEFINE']['CHANGELOG'] = 'https://flaskfarm.github.io/posts/changelog'
self.config['DEFINE']['CHANGELOG'] = 'https://github.com/flaskfarm/flaskfarm'
#self.config['DEFINE']['WEB_DIRECT_URL'] = "http://52.78.103.230:49734"
def __process_args(self):
@@ -363,6 +386,9 @@ class Framework:
self.config['debug'] = False
if self.config.get('plugin_update') == None:
self.config['plugin_update'] = True
# 2022-11-20
if self.config['debug']:
self.config['plugin_update'] = False
if self.config.get('plugin_loading_only_devpath') == None:
self.config['plugin_loading_only_devpath'] = False
if self.config.get('plugin_loading_list') == None:
@@ -402,8 +428,8 @@ class Framework:
try:
if self.config['flag_system_loading']:
try:
from system import SystemModelSetting
level = SystemModelSetting.get_int('log_level')
#from system import SystemModelSetting
level = self.SystemModelSetting.get_int('log_level')
except:
level = logging.DEBUG
if self.__level_unset_logger_list is not None:
@@ -426,7 +452,7 @@ class Framework:
return converted.timetuple()
if from_command == False:
file_formatter = logging.Formatter(u'[%(asctime)s|%(levelname)s|%(filename)s:%(lineno)s] %(message)s')
file_formatter = logging.Formatter(u'[%(asctime)s|%(levelname)s|%(name)s|%(filename)s:%(lineno)s] %(message)s')
else:
file_formatter = logging.Formatter(u'[%(asctime)s] %(message)s')
@@ -435,10 +461,18 @@ class Framework:
fileHandler = logging.handlers.RotatingFileHandler(filename=os.path.join(self.path_data, 'log', f'{name}.log'), maxBytes=file_max_bytes, backupCount=5, encoding='utf8', delay=True)
fileHandler.setFormatter(file_formatter)
logger.addHandler(fileHandler)
if name == 'framework' and self.all_log_filehandler == None:
self.all_log_filehandler = logging.handlers.RotatingFileHandler(filename=os.path.join(self.path_data, 'log', f'all.log'), maxBytes=5*1024*1024, backupCount=5, encoding='utf8', delay=True)
self.all_log_filehandler.setFormatter(file_formatter)
if from_command == False:
streamHandler = logging.StreamHandler()
streamHandler.setFormatter(CustomFormatter())
logger.addHandler(streamHandler)
if self.all_log_filehandler != None:
logger.addHandler(self.all_log_filehandler)
return logger
@@ -459,7 +493,7 @@ class Framework:
def set_level(self, level):
try:
for l in self.__logger_list:
l.setLevel(level)
l.setLevel(int(level))
self.__make_default_logger()
except:
pass
@@ -468,7 +502,7 @@ class Framework:
def start(self):
host = '0.0.0.0'
for i in range(5):
for i in range(5):
try:
#self.logger.debug(d(self.config))
# allow_unsafe_werkzeug=True termux nohup 실행시 필요함
@@ -517,8 +551,8 @@ class Framework:
PluginManager.plugin_unload()
with self.app.test_request_context():
self.socketio.stop()
except Exception as exception:
self.logger.error('Exception:%s', exception)
except Exception as e:
self.logger.error(f"Exception:{str(e)}")
self.logger.error(traceback.format_exc())
def get_recent_version(self):
@@ -532,3 +566,11 @@ class Framework:
self.logger.error(traceback.format_exc())
self.config['recent_version'] = '확인 실패'
return False
# dev 도커용. package는 setup에 포함.
def pip_install(self):
try:
import json_fix
except:
os.system('pip install json_fix')

View File

@@ -1,93 +1,144 @@
import os
import shutil
import traceback
from framework import F, logger
from support import SupportYaml, d
from framework import F
class MenuManager:
menu_map = None
@classmethod
def __load_menu_yaml(cls):
menu_yaml_filepath = os.path.join(F.config['path_data'], 'db', 'menu.yaml')
if os.path.exists(menu_yaml_filepath) == False:
shutil.copy(
os.path.join(F.config['path_app'], 'files', 'menu.yaml.template'),
menu_yaml_filepath
)
cls.menu_map = SupportYaml.read_yaml(menu_yaml_filepath)
try:
menu_yaml_filepath = os.path.join(F.config['path_data'], 'db', 'menu.yaml')
if os.path.exists(menu_yaml_filepath) == False:
shutil.copy(
os.path.join(F.config['path_app'], 'files', 'menu.yaml.template'),
menu_yaml_filepath
)
cls.menu_map = SupportYaml.read_yaml(menu_yaml_filepath)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
cls.menu_map = SupportYaml.read_yaml(os.path.join(F.config['path_app'], 'files', 'menu.yaml.template'))
@classmethod
def init_menu(cls):
cls.__load_menu_yaml()
from .init_plugin import PluginManager
plugin_menus = PluginManager.plugin_menus
copy_map = []
if cls.__init_menu() == False:
cls.menu_map = SupportYaml.read_yaml(os.path.join(F.config['path_app'], 'files', 'menu.yaml.template'))
cls.__init_menu()
for category in cls.menu_map:
if 'uri' in category:
copy_map.append(category)
continue
cate_count = 0
@classmethod
def __init_menu(cls):
try:
from .init_plugin import PluginManager
plugin_menus = PluginManager.plugin_menus
copy_map = []
for category in cls.menu_map:
if 'uri' in category:
if category['uri'] in plugin_menus:
plugin_menus[category['uri']]['match'] = True
copy_map.append(plugin_menus[category['uri']]['menu'])
else:
copy_map.append(category)
continue
cate_count = 0
tmp_cate_list = []
for item in category['list']:
if item['uri'] in plugin_menus:
plugin_menus[item['uri']]['match'] = True
tmp_cate_list.append(plugin_menus[item['uri']]['menu'])
cate_count += 1
elif item['uri'].startswith('http'):
tmp_cate_list.append({
'uri': item['uri'],
'name': item['name'],
'target': item.get('target', '_blank')
})
cate_count += 1
elif (len(item['uri'].split('/')) > 1 and item['uri'].split('/')[0] in plugin_menus) or item['uri'].startswith('javascript') or item['uri'] in ['-']:
tmp_cate_list.append({
'uri': item['uri'],
'name': item.get('name', ''),
})
cate_count += 1
elif item['uri'] == 'setting':
if len(PluginManager.setting_menus) > 0:
tmp_cate_list = []
for item in category['list']:
if item['uri'] in plugin_menus:
plugin_menus[item['uri']]['match'] = True
tmp_cate_list.append(plugin_menus[item['uri']]['menu'])
cate_count += 1
elif item['uri'].startswith('http'):
tmp_cate_list.append({
'uri': item['uri'],
'name': item['name'],
'target': item.get('target', '_blank')
})
cate_count += 1
elif (len(item['uri'].split('/')) > 1 and item['uri'].split('/')[0] in plugin_menus) or item['uri'].startswith('javascript') or item['uri'] in ['-']:
tmp_cate_list.append({
'uri': item['uri'],
'name': item.get('name', ''),
'list': PluginManager.setting_menus
})
if cate_count > 0:
copy_map.append({
'name': category['name'],
'list': tmp_cate_list,
'count': cate_count
})
cls.menu_map = copy_map
make_dummy_cate = False
for name, plugin_menu in plugin_menus.items():
#F.logger.info(d(plugin_menu))
#if 'uri' not in plugin_menu['menu']:
# continue
if plugin_menu['match'] == False:
if make_dummy_cate == False:
make_dummy_cate = True
cls.menu_map.insert(len(cls.menu_map)-1, {
'name':'미분류', 'count':0, 'list':[]
cate_count += 1
elif item['uri'] == 'setting':
# 2024.06.04
# 확장설정도 메뉴 구성
if len(PluginManager.setting_menus) > 0:
set_tmp = item.get('list')
if set_tmp:
cp = PluginManager.setting_menus.copy()
include = []
for set_ch in set_tmp:
if set_ch.get('uri') and (set_ch.get('uri') == '-' or set_ch.get('uri').startswith('http')):
include.append(set_ch)
continue
for i, ps in enumerate(cp):
if set_ch.get('plugin') != None and set_ch.get('plugin') == ps.get('plugin'):
include.append(ps)
del cp[i]
break
tmp_cate_list.append({
'uri': item['uri'],
'name': item.get('name', ''),
'list': include + cp
})
else:
tmp_cate_list.append({
'uri': item['uri'],
'name': item.get('name', ''),
'list': PluginManager.setting_menus
})
if cate_count > 0:
copy_map.append({
'name': category['name'],
'list': tmp_cate_list,
'count': cate_count
})
cls.menu_map = copy_map
make_dummy_cate = False
for name, plugin_menu in plugin_menus.items():
#F.logger.info(d(plugin_menu))
#if 'uri' not in plugin_menu['menu']:
# continue
if plugin_menu['match'] == False:
if make_dummy_cate == False:
make_dummy_cate = True
cls.menu_map.insert(len(cls.menu_map)-1, {
'name':'미분류', 'count':0, 'list':[]
})
c = cls.menu_map[-2]
c['count'] += 1
c['list'].append(plugin_menu['menu'])
c = cls.menu_map[-2]
c['count'] += 1
c['list'].append(plugin_menu['menu'])
return True
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False
#F.logger.warning(d(cls.menu_map))
@classmethod
def get_menu_map(cls):
#F.logger.warning(d(cls.menu_map))
return cls.menu_map
@classmethod
def get_setting_menu(cls, plugin):
from .init_plugin import PluginManager
for tmp in PluginManager.setting_menus:
if tmp['plugin'] == plugin:
return tmp

View File

@@ -7,9 +7,8 @@ import traceback
import zipfile
import requests
from support import SupportFile, SupportSubprocess, SupportYaml
from framework import F
from support import SupportFile, SupportSubprocess, SupportYaml
class PluginManager:
@@ -30,13 +29,13 @@ class PluginManager:
tmps = os.listdir(plugin_path)
add_plugin_list = []
for t in tmps:
if not t.startswith('_') and os.path.isdir(os.path.join(plugin_path, t)):
if t.startswith('_') == False and t.startswith('.') == False and os.path.isdir(os.path.join(plugin_path, t)) and t != 'false' and t != 'tmp':
add_plugin_list.append(t)
cls.all_package_list[t] = {'pos':'normal', 'path':os.path.join(plugin_path, t), 'loading':(F.config.get('plugin_loading_only_devpath', None) != True)}
plugins = plugins + add_plugin_list
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
if F.config.get('plugin_loading_only_devpath', None) == True:
@@ -59,12 +58,12 @@ class PluginManager:
tmps = os.listdir(__)
add_plugin_list = []
for t in tmps:
if not t.startswith('_') and os.path.isdir(os.path.join(__, t)):
if t.startswith('_') == False and t.startswith('.') == False and os.path.isdir(os.path.join(__, t)) and t != 'false' and t != 'tmp':
add_plugin_list.append(t)
cls.all_package_list[t] = {'pos':'dev', 'path':os.path.join(__, t), 'loading':True}
plugins = plugins + add_plugin_list
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
# plugin_loading_list
@@ -79,8 +78,8 @@ class PluginManager:
cls.all_package_list[_]['loading'] = False
cls.all_package_list[_]['status'] = 'not_include_loading_list'
plugins = new_plugins
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
# plugin_except_list
@@ -95,8 +94,8 @@ class PluginManager:
cls.all_package_list[_]['loading'] = False
cls.all_package_list[_]['status'] = 'include_except_list'
plugins = new_plugins
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
return plugins
@@ -113,43 +112,26 @@ class PluginManager:
for plugin_name in plugins:
F.logger.debug(f'[+] PLUGIN LOADING Start.. [{plugin_name}]')
entity = cls.all_package_list[plugin_name]
entity['version'] = '3'
try:
mod = __import__('%s' % (plugin_name), fromlist=[])
mod_plugin_info = None
try:
mod_plugin_info = getattr(mod, 'plugin_info')
entity['module'] = mod
except Exception as exception:
F.logger.info(f'[!] PLUGIN_INFO not exist : [{plugin_name}] - is FF')
if mod_plugin_info == None:
try:
mod = __import__(f'{plugin_name}.setup', fromlist=['setup'])
entity['version'] = '4'
except Exception as e:
F.logger.error(f'Exception:{str(e)}')
F.logger.error(traceback.format_exc())
F.logger.warning(f'[!] NOT normal plugin : [{plugin_name}]')
mod = __import__(f'{plugin_name}.setup', fromlist=['setup'])
except Exception as e:
F.logger.error(f'Exception:{str(e)}')
F.logger.error(traceback.format_exc())
F.logger.warning(f'[!] NOT normal plugin : [{plugin_name}]')
continue
try:
if entity['version'] != '4':
mod_blue_print = getattr(mod, 'blueprint')
else:
entity['setup_mod'] = mod
entity['P'] = getattr(mod, 'P')
mod_blue_print = getattr(entity['P'], 'blueprint')
entity['setup_mod'] = mod
entity['P'] = getattr(mod, 'P')
mod_blue_print = getattr(entity['P'], 'blueprint')
if mod_blue_print:
F.app.register_blueprint(mod_blue_print)
except Exception as exception:
#logger.error('Exception:%s', exception)
#logger.error(traceback.format_exc())
F.logger.warning(f'[!] BLUEPRINT not exist : [{plugin_name}]')
cls.plugin_list[plugin_name] = entity
#system.LogicPlugin.current_loading_plugin_list[plugin_name]['status'] = 'success'
#system.LogicPlugin.current_loading_plugin_list[plugin_name]['info'] = mod_plugin_info
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
F.logger.debug('no blueprint')
cls.all_package_list[plugin_name]['loading'] = False
@@ -157,36 +139,50 @@ class PluginManager:
cls.all_package_list[plugin_name]['log'] = traceback.format_exc()
if not F.config['run_celery']:
try:
with F.app.app_context():
F.db.create_all()
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
F.logger.debug('db.create_all error')
if F.config['run_celery']:
for key, entity in cls.plugin_list.items():
try:
mod_plugin_load = getattr(entity['P'], 'plugin_load_celery')
if mod_plugin_load:
def func(mod_plugin_load, key):
try:
#F.logger.debug(f'[!] plugin_load_celery threading start : [{key}]')
mod_plugin_load()
#F.logger.debug(f'[!] plugin_load_celery threading end : [{key}]')
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
t = threading.Thread(target=func, args=(mod_plugin_load, key))
t.setDaemon(True)
t.start()
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
if not F.config['run_flask']:
return
for key, entity in cls.plugin_list.items():
try:
mod_plugin_load = None
if entity['version'] == '3':
mod_plugin_load = getattr(entity['module'], 'plugin_load')
elif entity['version'] == '4':
mod_plugin_load = getattr(entity['P'], 'plugin_load')
mod_plugin_load = getattr(entity['P'], 'plugin_load')
if mod_plugin_load:
def func(mod_plugin_load, key):
try:
F.logger.debug(f'[!] plugin_load threading start : [{key}]')
#mod.plugin_load()
F.logger.info(f'[!] plugin_load threading start : [{key}]')
mod_plugin_load()
F.logger.debug(f'[!] plugin_load threading end : [{key}]')
except Exception as exception:
except Exception as e:
F.logger.error('### plugin_load exception : %s', key)
F.logger.error('Exception:%s', exception)
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
cls.all_package_list[key]['loading'] = False
cls.all_package_list[key]['status'] = 'plugin_load error'
@@ -199,42 +195,29 @@ class PluginManager:
MenuManager.init_menu()
F.logger.info(f"플러그인 로딩 실패로 메뉴 삭제2 : {key}")
t = threading.Thread(target=func, args=(mod_plugin_load, key))
t.setDaemon(True)
t.start()
# mod는 위에서 로딩
if key != 'mod':
t = threading.Thread(target=func, args=(mod_plugin_load, key))
t.setDaemon(True)
t.start()
#if key == 'mod':
# t.join()
except Exception as exception:
except Exception as e:
F.logger.debug(f'[!] PLUGIN_LOAD function not exist : [{key}]')
#logger.error('Exception:%s', exception)
#logger.error(traceback.format_exc())
#logger.debug('no init_scheduler')
try:
mod_menu = None
if entity['version'] == '3':
mod_menu = getattr(entity['module'], 'menu')
elif entity['version'] == '4':
mod_menu = getattr(entity['P'], 'menu')
mod_menu = getattr(entity['P'], 'menu')
if mod_menu and cls.all_package_list[key]['loading'] != False:
cls.plugin_menus[key]= {'menu':mod_menu, 'match':False}
if entity['version'] == '4':
setting_menu = getattr(entity['P'], 'setting_menu')
if setting_menu != None and cls.all_package_list[key]['loading'] != False:
F.logger.info(f"메뉴 포함 : {key}")
cls.setting_menus.append(setting_menu)
setting_menu = getattr(entity['P'], 'setting_menu')
setting_menu['plugin'] = entity['P'].package_name
if setting_menu != None and cls.all_package_list[key]['loading'] != False:
F.logger.info(f"확장 설정 : {key}")
cls.setting_menus.append(setting_menu)
except Exception as exception:
F.logger.debug('no menu')
F.logger.debug('### plugin_load threading all start.. : %s ', len(cls.plugin_list))
# 모든 모듈을 로드한 이후에 app 등록, table 생성, start
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
@@ -243,17 +226,9 @@ class PluginManager:
def plugin_unload(cls):
for key, entity in cls.plugin_list.items():
try:
if entity['version'] == '3':
mod_plugin_unload = getattr(entity['module'], 'plugin_unload')
elif entity['version'] == '4':
mod_plugin_unload = getattr(entity['P'], 'plugin_unload')
#if plugin_name == 'rss':
# continue
#mod_plugin_unload = getattr(mod, 'plugin_unload')
mod_plugin_unload = getattr(entity['P'], 'plugin_unload')
if mod_plugin_unload:
mod_plugin_unload()
#mod.plugin_unload()
except Exception as e:
F.logger.error('module:%s', key)
F.logger.error(f'Exception:{str(e)}')
@@ -267,6 +242,7 @@ class PluginManager:
@classmethod
def plugin_install(cls, plugin_git, zip_url=None, zip_filename=None):
plugin_git = plugin_git.strip()
is_git = True if plugin_git != None and plugin_git != '' else False
ret = {}
try:
@@ -381,7 +357,7 @@ class PluginManager:
tmps = os.listdir(plugins_path)
for t in tmps:
plugin_path = os.path.join(plugins_path, t)
if t.startswith('_'):
if t.startswith('_') or t.startswith('.'):
continue
if os.path.exists(os.path.join(plugin_path, '.git')):
command = ['git', '-C', plugin_path, 'reset', '--hard', 'HEAD']
@@ -392,14 +368,15 @@ class PluginManager:
F.logger.debug(ret)
else:
F.logger.debug(f"{plugin_path} not git repo")
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
@classmethod
def get_plugin_instance(cls, package_name):
try:
return cls.all_package_list[package_name]['P']
if cls.all_package_list[package_name]['loading']:
return cls.all_package_list[package_name]['P']
except:
pass

View File

@@ -4,7 +4,6 @@ import traceback
from flask import (jsonify, redirect, render_template, request,
send_from_directory)
from flask_login import login_required
from framework import F
@@ -86,27 +85,31 @@ def open_file(path):
@F.app.route("/file/<path:path>")
@F.check_api
def file2(path):
# 윈도우 drive 필요 없음
import platform
if platform.system() == 'Windows':
path = os.path.splitdrive(path)[1][1:]
return send_from_directory('/', path, as_attachment=True)
@F.app.route("/upload", methods=['GET', 'POST'])
@login_required
def upload():
try:
if request.method == 'POST':
f = request.files['file']
from werkzeug import secure_filename
from werkzeug.utils import secure_filename
upload_path = F.SystemModelSetting.get('path_upload')
os.makedirs(upload_path, exist_ok=True)
f.save(os.path.join(upload_path, secure_filename(f.filename)))
return jsonify('success')
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
return jsonify('fail')
@F.app.route("/videojs", methods=['GET', 'POST'])
@login_required
def videojs():
data = {}
data['play_title'] = request.form['play_title']
@@ -116,9 +119,33 @@ def videojs():
data['play_subtitle_src'] = request.form['play_subtitle_src']
return render_template('videojs.html', data=data)
@F.app.route("/videojs_drm", methods=['GET', 'POST'])
@login_required
def videojs_drm():
data = {}
data['play_title'] = request.form['play_title']
data['play_source_src'] = request.form['play_source_src']
data['play_source_type'] = request.form['play_source_type']
if 'play_subtitle_src' in request.form:
data['play_subtitle_src'] = request.form['play_subtitle_src']
return render_template('videojs_drm.html', data=data)
@F.app.route("/videojs_discord", methods=['GET', 'POST'])
@login_required
def videojs_og():
data = {}
"""
data['play_title'] = request.form['play_title']
data['play_source_src'] = request.form['play_source_src']
data['play_source_type'] = request.form['play_source_type']
if 'play_subtitle_src' in request.form:
data['play_subtitle_src'] = request.form['play_subtitle_src']
"""
return render_template('videojs_discord.html', data=data)
@F.app.route("/headers", methods=['GET', 'POST'])
@login_required
def headers():
from support import d
F.logger.info(d(request.headers))
@@ -127,6 +154,7 @@ def headers():
# 3.10에서 이거 필수
@F.socketio.on('connect', namespace=f'/framework')
@login_required
def connect():
pass

View File

@@ -4,6 +4,10 @@ from framework import F
def get_menu(full_query):
match = re.compile(r'\/(?P<package_name>.*?)\/(?P<module_name>.*?)\/manual\/(?P<sub2>.*?)($|\?)').match(full_query)
if match:
return match.group('package_name'), match.group('module_name'), f"manual/{match.group('sub2')}"
match = re.compile(r'\/(?P<menu>.*?)\/manual\/(?P<sub2>.*?)($|\?)').match(full_query)
if match:
return match.group('menu'), 'manual', match.group('sub2')
@@ -48,12 +52,14 @@ def jinja_initialize(app):
app.jinja_env.globals.update(get_menu=get_menu)
app.jinja_env.globals.update(get_theme=get_theme)
app.jinja_env.globals.update(get_menu_map=MenuManager.get_menu_map)
app.jinja_env.globals.update(get_setting_menu=MenuManager.get_setting_menu)
app.jinja_env.globals.update(get_web_title=get_web_title)
app.jinja_env.globals.update(dropzone=F.dropzone)
app.jinja_env.filters['get_menu'] = get_menu
app.jinja_env.filters['get_theme'] = get_theme
app.jinja_env.filters['get_menu_map'] = MenuManager.get_menu_map
app.jinja_env.filters['get_setting_menu'] = MenuManager.get_setting_menu
app.jinja_env.filters['get_web_title'] = get_web_title
app.jinja_env.auto_reload = True

View File

@@ -4,17 +4,18 @@ import time
import traceback
from flask import request
from support import SingletonClass
from framework import F
from support import SingletonClass
namespace = 'log'
@F.socketio.on('connect', namespace='/%s' % namespace)
@F.login_required
def socket_connect():
F.logger.debug('log connect')
@F.socketio.on('start', namespace='/%s' % namespace)
@F.login_required
def socket_file(data):
try:
package = filename = None
@@ -24,8 +25,8 @@ def socket_file(data):
filename = data['filename']
LogViewer.instance().start(package, filename, request.sid)
F.logger.debug('start package:%s filename:%s sid:%s', package, filename, request.sid)
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
@F.socketio.on('disconnect', namespace='/%s' % namespace)
@@ -33,8 +34,8 @@ def disconnect():
try:
LogViewer.instance().disconnect(request.sid)
F.logger.debug('disconnect sid:%s', request.sid)
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
@@ -62,18 +63,17 @@ class WatchThread(threading.Thread):
key = 'filename'
value = self.filename
if os.path.exists(logfile):
with open(logfile, 'r') as f:
with open(logfile, 'r', encoding='utf8') as f:
f.seek(0, os.SEEK_END)
while not self.stop_flag:
line = f.readline()
if not line:
time.sleep(0.1) # Sleep briefly
continue
F.socketio.emit("add", {key : value, 'data': line}, namespace='/log', broadcast=True)
F.socketio.emit("add", {key : value, 'data': line}, namespace='/log')
F.logger.debug('WatchThread.. End %s', value)
else:
F.socketio.emit("add", {key : value, 'data': 'not exist logfile'}, namespace='/log', broadcast=True)
F.socketio.emit("add", {key : value, 'data': 'not exist logfile'}, namespace='/log')
class LogViewer(SingletonClass):

View File

@@ -49,8 +49,8 @@ class Scheduler(object):
if flag_exit:
self.remove_job("scheduler_check")
#time.sleep(30)
except Exception as exception:
self.logger.error('Exception:%s', exception)
except Exception as e:
self.logger.error(f"Exception:{str(e)}")
self.logger.error(traceback.format_exc())
def shutdown(self):
@@ -233,21 +233,21 @@ class Job(object):
if self.args is None:
self.thread = threading.Thread(target=self.target_function, args=())
else:
self.thread = threading.Thread(target=self.target_function, args=(self.args,))
self.thread = threading.Thread(target=self.target_function, args=self.args)
self.thread.daemon = True
self.thread.start()
F.socketio.emit('notify', {'type':'success', 'msg':f"{self.description}<br>작업을 시작합니다." }, namespace='/framework', broadcast=True)
F.socketio.emit('notify', {'type':'success', 'msg':f"{self.description}<br>작업을 시작합니다." }, namespace='/framework')
self.thread.join()
F.socketio.emit('notify', {'type':'success', 'msg':f"{self.description}<br>작업이 종료되었습니다." }, namespace='/framework', broadcast=True)
F.socketio.emit('notify', {'type':'success', 'msg':f"{self.description}<br>작업이 종료되었습니다." }, namespace='/framework')
self.end_time = datetime.now(timezone('Asia/Seoul'))
self.running_timedelta = self.end_time - self.start_time
self.status = 'success'
if not F.scheduler.is_include(self.job_id):
F.scheduler.remove_job_instance(self.job_id)
self.count += 1
except Exception as exception:
except Exception as e:
self.status = 'exception'
F.logger.error('Exception:%s', exception)
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
finally:
self.is_running = False

BIN
lib/framework/static/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -106,3 +106,5 @@ background-color: #ffff0080 !important;
.dropdown-menu {
margin:-2px;
}
.modal { overflow: scroll !important; }

View File

@@ -0,0 +1,160 @@
h3 {
border-bottom: 1px solid #ddd;
}
.top-bar {
height: 45px;
min-height: 45px;
position: absolute;
top: 0;
right: 0;
left: 0;
}
.bars-lnk {
color: #fff;
}
.bars-lnk i {
display: inline-block;
margin-left: 10px;
margin-top: 7px;
}
.bars-lnk img {
display: inline-block;
margin-left: 10px;
margin-top: -15px;
margin-right: 15px;
height: 35px;
}
.lateral-menu {
background-color: #333;
color: rgb(144, 144, 144);
width: 300px;
}
.lateral-menu label {
color: rgb(144, 144, 144);
}
.lateral-menu-content {
padding-left: 10px;
height: 100%;
font-size: 12px;
font-style: normal;
font-variant: normal;
font-weight: bold;
line-height: 16px;
}
.lateral-menu-content .title{
padding-top: 15px;
font-size: 2em;
height: 45px;
}
.lateral-menu-content-inner {
overflow-y: auto;
height: 100%;
padding-top: 10px;
padding-bottom: 50px;
padding-right: 10px;
font-size: 0.9em;
}
#preview {
height: 97%;
max-height: 97%;
border: 1px solid #eee;
overflow-y: scroll;
width: 55%;
padding: 10px;
}
pre {
white-space: pre-wrap; /* css-3 */
white-space: -moz-pre-wrap; /* Mozilla, since 1999 */
white-space: -pre-wrap; /* Opera 4-6 */
white-space: -o-pre-wrap; /* Opera 7 */
word-wrap: break-word; /* Internet Explorer 5.5+ */
background-color: #f8f8f8;
border: 1px solid #dfdfdf;
margin-top: 1.5em;
margin-bottom: 1.5em;
padding: 0.125rem 0.3125rem 0.0625rem;
}
pre code {
background-color: transparent;
border: 0;
padding: 0;
}
.modal-wrapper {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 999;
background-color: rgba(51,51,51,0.5);
}
.modal-inner {
margin-top: 200px;
margin-left: auto;
margin-right: auto;
width: 600px;
height: 225px;
background-color: #fff;
opacity: 1;
z-index: 1000;
}
.modal-close-btn {
float: right;
display: inline-block;
margin-right: 5px;
color: #ff4336;
}
.modal-close-btn:hover {
float: right;
display: inline-block;
margin-right: 5px;
color: #8d0002;
}
.modal-topbar {
clear: both;
height: 25px;
}
.modal-inner .link-area {
margin: 10px;
height: 170px;
}
.modal-inner textarea {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.version {
color: white;
font-size: 0.8em !important;
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="margin: auto; background: none; display: block; shape-rendering: auto;" width="100px" height="100px" viewBox="0 0 100 100" preserveAspectRatio="xMidYMid">
<circle cx="50" cy="50" r="31" stroke-width="4" stroke="#e15b64" stroke-dasharray="48.69468613064179 48.69468613064179" fill="none" stroke-linecap="round">
<animateTransform attributeName="transform" type="rotate" dur="2.6315789473684212s" repeatCount="indefinite" keyTimes="0;1" values="0 50 50;360 50 50"></animateTransform>
</circle>
<circle cx="50" cy="50" r="26" stroke-width="4" stroke="#f8b26a" stroke-dasharray="40.840704496667314 40.840704496667314" stroke-dashoffset="40.840704496667314" fill="none" stroke-linecap="round">
<animateTransform attributeName="transform" type="rotate" dur="2.6315789473684212s" repeatCount="indefinite" keyTimes="0;1" values="0 50 50;-360 50 50"></animateTransform>
</circle>
<!-- [ldio] generated by https://loading.io/ --></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@ if (tmp.length == 2) {
var PACKAGE_NAME = tmp[1];
var MODULE_NAME = tmp[2];
var PAGE_NAME = "";
} else if (tmp.length == 4){
} else if (tmp.length > 3){
var PACKAGE_NAME = tmp[1];
var MODULE_NAME = tmp[2];
var PAGE_NAME = tmp[3];
@@ -23,8 +23,6 @@ $(window).on("load resize", function (event) {
});
$('#command_modal').on('show.bs.modal', function (event) {
console.log('111111111')
console.log(event);
})
///////////////////////////////////////
@@ -113,7 +111,6 @@ function showModal(data='EMPTY', title='JSON', json=true) {
data = JSON.stringify(data, null, 2);
}
document.getElementById("modal_body").innerHTML = '<pre style="white-space: pre-wrap;">' +data + '</pre>';
$("#large_modal").modal();
}
@@ -168,7 +165,22 @@ function use_collapse(div, reverse=false) {
}
}
// jquery extend function
// post로 요청하면서 리다이렉트
$.extend(
{
redirectPost: function(location, args)
{
var form = '';
$.each( args, function( key, value ) {
console.log(key);
console.log(value);
value = value.split('"').join('\"')
form += '<input type="hidden" name="'+key+'" value="'+value+'">';
});
$('<form action="' + location + '" method="POST">' + form + '</form>').appendTo($(document.body)).submit();
}
});
@@ -282,20 +294,3 @@ function pad(n, width) {
return n.length >= width ? n : new Array(width - n.length + 1).join('0') + n;
}
// jquery extend function
// post로 요청하면서 리다이렉트
// 푹 자동에서 푹 기본 검색할때 사용
$.extend(
{
redirectPost: function(location, args)
{
var form = '';
$.each( args, function( key, value ) {
console.log(key);
console.log(value);
value = value.split('"').join('\"')
form += '<input type="hidden" name="'+key+'" value="'+value+'">';
});
$('<form action="' + location + '" method="POST">' + form + '</form>').appendTo($(document.body)).submit();
}
});

View File

@@ -15,11 +15,10 @@ $(document).ready(function(){
var protocol = window.location.protocol;
var frameSocket = io.connect(protocol + "//" + document.domain + ":" + location.port + "/framework");
console.log(frameSocket);
frameSocket.on('notify', function(data){
$.notify({
message : data['msg'],
message : '<strong>' + data['msg'] + '</strong>',
url: data['url'],
target: '_self'
},{
@@ -29,7 +28,7 @@ frameSocket.on('notify', function(data){
});
frameSocket.on('modal', function(data){
m_modal(data.data, data.title, false);
showModal(data.data, data.title, false);
});
frameSocket.on('loading_hide', function(data){
@@ -37,14 +36,12 @@ frameSocket.on('loading_hide', function(data){
});
frameSocket.on('refresh', function(data){
console.log('data')
window.location.reload();
});
$('#command_modal').on('hide.bs.modal', function (e) {
//e.preventDefault(); 있으면 동작 안함.
console.log("ff global command_modal hide.bs.modal CATCH")
$.ajax({
url: `/global/ajax/command_modal_hide`,
type: 'POST',
@@ -74,13 +71,27 @@ $("body").on('click', '#globalLinkBtn', function(e) {
window.location.href = url;
});
$("body").on('click', '#globalReloadBtn', function(e) {
e.preventDefault();
location.reload();
});
// global_link_btn 모두 찾아 변경
$("body").on('click', '#globalSettingSaveBtn', function(e){
e.preventDefault();
globalSettingSave();
if (globalSettingSaveBefore()) {
globalSettingSave();
}
});
function globalSettingSaveBefore() {
return true;
}
function globalSettingSaveAfter() {
return true;
}
function globalSettingSave() {
var formData = getFormdata('#setting');
$.ajax({
@@ -94,6 +105,7 @@ function globalSettingSave() {
$.notify('<strong>설정을 저장하였습니다.</strong>', {
type: 'success'
});
globalSettingSaveAfter();
} else {
$.notify('<strong>설정 저장에 실패하였습니다.</strong>', {
type: 'warning'
@@ -106,7 +118,10 @@ function globalSettingSave() {
$("body").on('click', '#globalEditBtn', function(e) {
e.preventDefault();
file = $(this).data('file');
console.log(file);
if (file == null) {
var tag = $(this).data('tag');
file = $('#' + tag).val();
}
$.ajax({
url: '/global/ajax/is_available_edit',
type: "POST",
@@ -236,107 +251,188 @@ $("body").on('click', '#globalImmediatelyExecutePageBtn', function(e){
});
});
$("body").on('click', '#globalDbDeleteDayBtn', function(e){
e.preventDefault();
var tag_id = $(this).data('tag_id');
var day = $('#' + tag_id).val();
globalConfirmModal('DB 삭제', "최근 " + day + "일 이내 데이터를 제외하고 삭제 하시겠습니까?", function() {
globalDbDelete(day);
});
});
$("body").on('click', '#globalDbDeleteBtn', function(e){
e.preventDefault();
document.getElementById("confirm_title").innerHTML = "DB 삭제";
document.getElementById("confirm_body").innerHTML = "전체 목록을 삭제 하시겠습니까?";
$('#confirm_button').attr('onclick', "globalDbDelete();");
$("#confirm_modal").modal();
return;
globalConfirmModal('DB 삭제', "전체 목록을 삭제 하시겠습니까?", function() {
globalDbDelete(0);
});
});
function globalDbDelete() {
function globalDbDelete(day) {
$.ajax({
url: '/'+PACKAGE_NAME+'/ajax/' + MODULE_NAME + '/reset_db',
url: '/'+PACKAGE_NAME+'/ajax/' + MODULE_NAME + '/db_delete',
type: "POST",
cache: false,
data: {},
data: {day:day},
dataType: "json",
success: function (data) {
if (data) {
$.notify('<strong>삭제하였습니다.</strong>', {
type: 'success'
});
} else {
if (data == -1) {
$.notify('<strong>삭제에 실패하였습니다.</strong>',{
type: 'warning'
});
} else {
$.notify('<strong>'+data+'개를 삭제하였습니다.</strong>', {
type: 'success'
});
globalRequestSearch('1');
}
}
});
}
///////////////////////////////////////////////////
$("body").on('click', '#globalDbDeleteDayPageBtn', function(e){
e.preventDefault();
var tag_id = $(this).data('tag_id');
var day = $('#' + tag_id).val();
globalConfirmModal('DB 삭제', day + "일 제외 목록을 삭제 하시겠습니까?", function() {
globalDbDeletePage(day);
});
});
$("body").on('click', '#globalDbDeletePageBtn', function(e){
e.preventDefault();
document.getElementById("confirm_title").innerHTML = "DB 삭제";
document.getElementById("confirm_body").innerHTML = "전체 목록을 삭제 하시겠습니까?";
$('#confirm_button').attr('onclick', "globalDbDeletePage();");
$("#confirm_modal").modal();
return;
globalConfirmModal('DB 삭제', "최근 " + day + "일 이내 데이터를 제외하고 삭제 하시겠습니까?", function() {
globalDbDeletePage(0);
});
});
function globalDbDeletePage() {
function globalDbDeletePage(day) {
$.ajax({
url: '/'+PACKAGE_NAME+'/ajax/' + MODULE_NAME + '/' + PAGE_NAME + '/reset_db',
type: "POST",
cache: false,
data: {sub:sub},
data: {day:day},
dataType: "json",
success: function (data) {
if (data) {
$.notify('<strong>삭제하였습니다.</strong>', {
type: 'success'
});
} else {
if (data == -1) {
$.notify('<strong>삭제에 실패하였습니다.</strong>',{
type: 'warning'
});
} else {
$.notify('<strong>'+data+'개를 삭제하였습니다.</strong>', {
type: 'success'
});
globalRequestSearch('1');
}
}
});
}
$("body").on('click', '#globalDbDeleteItemBtn', function(e){
e.preventDefault();
var db_id = $(this).data('id');
$.ajax({
url: '/'+PACKAGE_NAME+'/ajax/' + MODULE_NAME + '/db_delete_item',
type: "POST",
cache: false,
data: {db_id:db_id},
dataType: "json",
success: function (ret) {
if (ret) {
notify('삭제하였습니다.', 'success');
globalRequestSearch(current_page);
} else {
notify('삭제에 실패하였습니다.', 'warning');
}
}
});
});
$("body").on('click', '#globalDbDeleteItemPageBtn', function(e){
e.preventDefault();
var db_id = $(this).data('id');
$.ajax({
url: '/'+PACKAGE_NAME+'/ajax/' + MODULE_NAME + '/' + PAGE_NAME + '/db_delete_item',
type: "POST",
cache: false,
data: {db_id:db_id},
dataType: "json",
success: function (ret) {
if (ret) {
notify('삭제하였습니다.', 'success');
globalRequestSearch(current_page);
} else {
notify('삭제에 실패하였습니다.', 'warning');
}
}
});
});
$("body").on('click', '#globalJsonBtn', function(e){
e.preventDefault();
showModal(current_data.list[$(this).data('idx')]);
});
///////////////////////////////////////
// Global - 함수
///////////////////////////////////////
function globalSendCommand(command, arg1, arg2, arg3, modal_title, callback) {
console.log("globalSendCommand [" + command + '] [' + arg1 + '] [' + arg2 + '] [' + arg3 + '] [' + modal_title + '] [' + callback + ']');
console.log('/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/command');
function globalSendCommand(command, arg1, arg2, arg3, callback) {
var url = '/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/command';
return globalSendCommandByUrl(url, command, arg1, arg2, arg3, callback);
}
function globalSendCommandByUrl(url, command, arg1, arg2, arg3, callback) {
$.ajax({
url: '/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/command',
url: url,
type: "POST",
cache: false,
data:{command:command, arg1:arg1, arg2:arg2, arg3},
dataType: "json",
success: function (ret) {
console.log(ret)
if (ret.msg != null) notify(ret.msg, ret.ret);
if (ret.modal != null) showModal(ret.modal, modal_title, false);
if (ret.json != null) showModal(ret.json, modal_title, true);
if (ret.modal != null) showModal(ret.modal, ret.title, false);
if (ret.json != null) showModal(ret.json, ret.title, true);
if (callback != null) callback(ret);
if (ret.reload) location.reload();
}
});
}
function globalSendCommandPage(command, arg1, arg2, arg3, modal_title, callback) {
console.log("globalSendCommandPage [" + command + '] [' + arg1 + '] [' + arg2 + '] [' + arg3 + '] [' + modal_title + '] [' + callback + ']');
console.log('/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/command');
function globalSendCommandPage(command, arg1, arg2, arg3, callback) {
var url = '/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/' + PAGE_NAME + '/command';
return globalSendCommandPageByUrl(url, command, arg1, arg2, arg3, callback);
}
function globalSendCommandPageByUrl(url, command, arg1, arg2, arg3, callback) {
$.ajax({
url: '/' + PACKAGE_NAME + '/ajax/' + MODULE_NAME + '/' + PAGE_NAME + '/command',
url: url,
type: "POST",
cache: false,
data:{command:command, arg1:arg1, arg2:arg2, arg3},
dataType: "json",
success: function (ret) {
if (ret.msg != null) notify(ret.msg, ret.ret);
if (ret.modal != null) m_modal(ret.modal, modal_title, false);
if (ret.json != null) m_modal(ret.json, modal_title, true);
if (ret.modal != null) showModal(ret.modal, ret.title, false);
if (ret.json != null) showModal(ret.json, ret.title, true);
if (callback != null) callback(ret);
if (ret.reload) location.reload();
}
});
}
@@ -400,6 +496,10 @@ function make_page_html(data) {
str += '<button id="gloablSearchPageBtn" data-page="' + (data.last_page+1) + '" type="button" class="btn btn-secondary">&raquo;</button>'
}
if (data.last_page != data.total_page) {
str += '<button id="gloablSearchPageBtn" data-page="' + (data.total_page) + '" type="button" class="btn btn-secondary">'+data.total_page+'</button>'
}
str += '</div> \
</div> \
</div> \
@@ -431,6 +531,22 @@ $("body").on('click', '#globalSearchResetBtn', function(e){
});
$("body").on('change', '#option1', function(e){
e.preventDefault();
globalRequestSearch(1);
});
$("body").on('change', '#option2', function(e){
e.preventDefault();
globalRequestSearch(1);
});
$("body").on('change', '#order', function(e){
e.preventDefault();
globalRequestSearch(1);
});
///////////////////////////////////////
// 파일 선택 모달
@@ -483,7 +599,6 @@ let listdir = (path = '/', only_dir = true) => {
},
dataType: 'json'
}).done((datas) => {
console.log(datas)
if (datas.length == 0) {
return false;
}
@@ -510,8 +625,6 @@ let listdir = (path = '/', only_dir = true) => {
} else {
//new_path = (path !== path_spliter) ? path + path_spliter + $(evt.currentTarget).text() : path + $(evt.currentTarget).text();
new_path = $(evt.currentTarget).data('value');
console.log(new_path)
console.log(evt)
}
*/
@@ -587,3 +700,23 @@ function ResizeTextArea() {
///////////////////////////////////////
///////////////////////////////////////
// Confirm MODAL
///////////////////////////////////////
function globalConfirmModal(title, body, func) {
$("#confirm_title").html(title);
$("#confirm_body").html(body);
//$('#confirm_button').attr('onclick', func);
$("body").on('click', '#confirm_button', function(e){
e.stopImmediatePropagation();
e.preventDefault();
func();
});
$("#confirm_modal").modal();
}

View File

@@ -0,0 +1,14 @@
///////////////////////////////////////
// 자주 사용하는 플러그인에 전용 명령
function pluginRcloneLs(remote_path) {
var url = '/rclone/ajax/config/command';
globalSendCommandByUrl(url, "ls", remote_path);
}
function pluginRcloneSize(remote_path) {
var url = '/rclone/ajax/config/command';
globalSendCommandByUrl(url, "size", remote_path);
}

View File

@@ -10,12 +10,13 @@ function j_button_group(h) {
}
// primary, secondary, success, danger, warning, info, light, dark, white
function j_button(id, text, data={}, color='primary', outline=true, small=false) {
function j_button(id, text, data={}, color='primary', outline=true, small=false, _class='') {
var str = '<button id="'+id+'" name="'+id+'" class="btn btn-sm btn';
if (outline) {
str += '-outline';
}
str += '-' + color+'';
str += ' ' + _class;
if (small) {
str += ' py-0" style="font-size: 0.8em;"';
} else {
@@ -35,9 +36,14 @@ function j_button_small(id, text, data={}, color='primary', outline=true) {
function j_row_start(padding='10', align='center') {
var str = '<div class="row" style="padding-top: '+padding+'px; padding-bottom:'+padding+'px; align-items:'+align+';">';
var str = '<div class="row chover" style="padding-top: '+padding+'px; padding-bottom:'+padding+'px; align-items:'+align+';">';
return str;
}
function j_row_start_hover(padding='10', align='center') {
var str = '<div class="row my_hover" style="padding-top: '+padding+'px; padding-bottom:'+padding+'px; align-items:'+align+';">';
return str;
}
function j_col(w, h, align='left') {
var str = '<div class="col-sm-' + w + ' " style="text-align: '+align+'; word-break:break-all;">';
str += h;
@@ -45,6 +51,13 @@ function j_col(w, h, align='left') {
return str;
}
function j_col_with_class(w, h, align='left', _class='context_menu') {
var str = '<div class="col-sm-' + w + ' '+_class+'" style="text-align: '+align+'; word-break:break-all;">';
str += h;
str += '</div>';
return str;
}
function j_col_wide(w, h, align='left') {
var str = '<div class="col-sm-' + w + ' " style="padding:0px; margin:0px; text-align: '+align+'; word-break:break-all;">';
str += h;
@@ -87,57 +100,101 @@ function j_row_info(left, right, l=2, r=8) {
function j_progress(id, width, label) {
var str = '';
str += '<div class="progress" style="height: 25px;">'
str += '<div id="'+id+'" class="progress-bar" style="background-color:yellow;width:'+width+'%"></div>';
str += '<div id="'+id+'_label" class="justify-content-center d-flex w-100 " style="margin-top:2px">'+label+'</div>';
str += '<div id="'+id+'" class="progress-bar bg-success" style="width:'+width+'%"></div>';
str += '<div id="'+id+'_label" class="justify-content-center d-flex w-100 position-absolute" style="margin-top:2px">'+label+'</div>';
str += '</div>'
return str;
}
function j_td(text, width='10', align='center', colspan='1') {
str = '<td scope="col" colspan="'+colspan+'" style="width:'+width+'%; text-align:'+align+';">'+ text + '</td>';
return str;
}
function j_th(text, width='10', align='center', colspan='1') {
str = '<th scope="col" colspan="'+colspan+'" style="width:'+width+'%; text-align:'+align+';">'+ text + '</td>';
return str;
}
function make_log(key, value, left=2, right=10) {
row = m_col(left, key, aligh='right');
row += m_col(right, value, aligh='left');
function j_info_text(key, value, left=2, right=10) {
row = j_row_start(0);
row += j_col(left, '<strong>' + key + '</strong>', aligh='right');
row += j_col(right, value, aligh='left');
row += j_row_end();
return row;
}
function j_info_text_left(key, value, left=3, right=9) {
row = j_row_start(0);
row += j_col(left, '<strong>' + key + '</strong>', aligh='left');
row += j_col(right, value, aligh='left');
row += j_row_end();
return row;
}
function j_tab_make(data) {
str = '<nav><div class="nav nav-tabs" id="nav-tab" role="tablist">';
for (i in data) {
if (data[i][2]) {
str += '<a class="nav-item nav-link active" id="tab_head_'+data[i][0]+'" data-toggle="tab" href="#tab_content_'+data[i][0]+'" role="tab">'+data[i][1]+'</a>';
} else {
str += '<a class="nav-item nav-link" id="tab_head_'+data[i][0]+'" data-toggle="tab" href="#tab_content_'+data[i][0]+'" role="tab">'+data[i][1]+'</a>';
}
}
str += '</div></nav>';
str += '<div class="tab-content" id="nav-tabContent">';
for (i in data) {
if (data[i][2]) {
str += '<div class="tab-pane fade show active" id="tab_content_'+data[i][0]+'" role="tabpanel" ></div>';
} else {
str += '<div class="tab-pane fade show" id="tab_content_'+data[i][0]+'" role="tabpanel" ></div>';
}
}
str += '</div>';
return str;
}
// javascript에서 화면 생성
function text_color(text, color='red') {
return '<span style="color:'+color+'; font-weight:bold">' + text + '</span>';
}
function j_pre(text) {
return '<pre style="word-wrap: break-word;white-space: pre-wrap;white-space: -moz-pre-wrap;white-space: -pre-wrap;white-space: -o-pre-wrap;word-break:break-all;">'+text+'</pre>';
}
@@ -277,10 +334,7 @@ document.addEventListener("DOMContentLoaded", function(){
function m_row_start_hover(padding='10', align='center') {
var str = '<div class="row my_hover" style="padding-top: '+padding+'px; padding-bottom:'+padding+'px; align-items:'+align+';">';
return str;
}
function m_row_start_top(padding='10') {
return m_row_start(padding, 'top');
}
@@ -309,46 +363,5 @@ function m_row_start_color2(padding='10', align='center') {
function m_tab_head(name, active) {
if (active) {
var str = '<a class="nav-item nav-link active" id="id_'+name+'" data-toggle="tab" href="#'+name+'" role="tab">'+name+'</a>';
} else {
var str = '<a class="nav-item nav-link" id="id_'+name+'" data-toggle="tab" href="#'+name+'" role="tab">'+name+'</a>';
}
return str;
}
function m_tab_content(name, content, active) {
if (active) {
var str = '<div class="tab-pane fade show active" id="'+name+'" role="tabpanel" >';
} else {
var str = '<div class="tab-pane fade show" id="'+name+'" role="tabpanel" >';
}
str += content;
str += '</div>'
return str;
}
function m_progress2(id, width, label) {
var str = '';
str += '<div class="progress" style="height: 25px;">'
str += '<div id="'+id+'" class="progress-bar" style="background-color:yellow;width:'+width+'%"></div>';
str += '<div id="'+id+'_label" class="justify-content-center d-flex w-100 position-absolute" style="margin:0px; margin-top:2px">'+label+'</div>';
str += '</div>'
return str;
}

View File

@@ -0,0 +1,35 @@
//
// Google Prettify
// A showdown extension to add Google Prettify (http://code.google.com/p/google-code-prettify/)
// hints to showdown's HTML output.
//
(function () {
var prettify = function () {
return [
{
type: 'output',
filter: function (source) {
return source.replace(/(<pre[^>]*>)?[\n\s]?<code([^>]*)>/gi, function (match, pre, codeClass) {
if (pre) {
return '<pre class="prettyprint linenums"><code' + codeClass + '>';
} else {
return ' <code class="prettyprint">';
}
});
}
}
];
};
// Client-side export
if (typeof window !== 'undefined' && window.showdown && window.showdown.extensions) {
window.showdown.extensions.prettify = prettify;
}
// Server-side export
if (typeof module !== 'undefined') {
module.exports = prettify;
}
}());

File diff suppressed because one or more lines are too long

View File

@@ -35,6 +35,12 @@
<script src="https://cdn.jsdelivr.net/gh/gitbrent/bootstrap4-toggle@3.4.0/js/bootstrap4-toggle.min.js"></script>
<!-- end 토글 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.8.0/jquery.contextMenu.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.8.0/jquery.contextMenu.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-contextmenu/2.8.0/jquery.ui.position.js"></script>
</head>
<body class="body ">
@@ -50,7 +56,8 @@
</div>
</main>
<div class="loading" id="loading">
<img src="/static/img/loading.gif" />
<!-- <img src="/static/img/loading.gif" /> -->
<img src="/static/img/loader.svg" />
</div>
{{ modals() }}
</body>
@@ -59,3 +66,5 @@
<!-- 글로벌 버튼이 모두 나오고 처리-->
<script src="{{ url_for('static', filename='js/sjva_global1.js') }}"></script>
<script src="{{ url_for('static', filename='js/ff_global1.js') }}"></script>
<script src="{{ url_for('static', filename='js/ff_global_plugin.js') }}"></script>

View File

@@ -8,13 +8,13 @@
{{ macros.m_tab_head_end() }}
</nav>
<div class="tab-content" id="nav-tabContent">
{{ macros.m_tab_content_start('이전', true) }}
{{ macros.m_tab_content_start('old', true) }}
<div>
<textarea id="log" class="col-md-12" rows="30" charswidth="23" disabled style="background-color:#ffffff;visibility:hidden"></textarea>
</div>
{{ macros.m_tab_content_end() }}
{{ macros.m_tab_content_start('실시간', false) }}
{{ macros.m_tab_content_start('new', false) }}
<div>
<textarea id="add" class="col-md-12" rows="30" charswidth="23" disabled style="background-color:#ffffff;visibility:visible"></textarea>
</div>
@@ -52,6 +52,7 @@ $(window).resize(function() {
var protocol = window.location.protocol;
var socket = io.connect(protocol + "//" + document.domain + ":" + location.port + "/log");
socket.emit("start", {'package':'{{package}}'} );
socket.on('on_start', function(data){
document.getElementById("log").innerHTML += data.data;

View File

@@ -25,11 +25,11 @@
<!---->
{% macro m_tab_head_start() %}
<div class="nav nav-tabs" id="nav-tab" role="tablist">
<div class="nav nav-tabs" id="nav-tab" role="tablist">
{% endmacro %}
{% macro m_tab_head_end() %}
</div>
</div>
{% endmacro %}
{% macro m_tab_head(name, title, active) %}
@@ -39,12 +39,25 @@
<a class="nav-item nav-link" id="tab_{{name}}" data-toggle="tab" href="#{{name}}" role="tab">{{title}}</a>
{% endif %}
{% endmacro %}
<!----------------------------------------------------------------->
<!-- SETTING -->
<!-- SETTING -->
<!-- SETTING -->
<!------------------------------------------------------------------>
<!-- 설정 -->
<!-- SETTING 기본 틀-->
{% macro setting_top(left='', padding='10') %}
@@ -57,6 +70,16 @@
<div class='col-sm-9'>
{% endmacro %}
{% macro setting_top_big(left='', padding='10') %}
<div class='row' style="padding-top: {{padding}}px; padding-bottom:{{padding}}px; align-items: center;">
<div class='col-sm-3 set-left'>
{% if left != '' %}
<strong><h4>{{ left }}</h4></strong>
{% endif %}
</div>
<div class='col-sm-9'>
{% endmacro %}
{% macro setting_bottom(desc=None, padding_top='5') %}
{% if desc is not none %}
<div style="padding-left:20px; padding-top:{{padding_top}}px;">
@@ -247,18 +270,39 @@
<!-- 스케쥴링 작동 버튼-->
{% macro setting_global_scheduler_button(is_include, is_running, id='scheduler', left='스케쥴링 작동', desc=['On : 스케쥴링 시작','Off : 스케쥴링 중지']) %}
{% macro global_setting_scheduler_button(is_include, is_running, id='scheduler', left='스케쥴링 작동', desc=['On : 스케쥴링 시작','Off : 스케쥴링 중지']) %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
{% if is_include == True %}
{% if is_include == True or is_include == "True" %}
<input id="globalSchedulerSwitchBtn" name="globalSchedulerSwitchBtn" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
{% else %}
<input id="globalSchedulerSwitchBtn" name="globalSchedulerSwitchBtn" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
{% endif %}
{% if is_running == True %}
{% if is_running == True or is_running == "True" %}
<span style="padding-left:10px; padding-top: 8px; font-weight: bold;">실행중</span>
{% else %}
{% if is_include == True %}
{% if is_include == True or is_include == "True" %}
<span style="padding-left:10px; padding-top: 8px; ">대기중</span>
{% endif %}
{% endif %}
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
<!-- 스케쥴링 작동 버튼 페이지 -->
{% macro global_setting_scheduler_button_page(is_include, is_running, id='scheduler', left='스케쥴링 작동', desc=['On : 스케쥴링 시작','Off : 스케쥴링 중지']) %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
{% if is_include == True or is_include == "True" %}
<input id="globalSchedulerSwitchPageBtn" name="globalSchedulerSwitchPageBtn" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
{% else %}
<input id="globalSchedulerSwitchPageBtn" name="globalSchedulerSwitchPageBtn" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
{% endif %}
{% if is_running == True or is_running == "True" %}
<span style="padding-left:10px; padding-top: 8px; font-weight: bold;">실행중</span>
{% else %}
{% if is_include == True or is_include == "True" %}
<span style="padding-left:10px; padding-top: 8px; ">대기중</span>
{% endif %}
{% endif %}
@@ -268,13 +312,116 @@
setting_gole
<!-- NOT SETTING -->
<!-- NOT SETTING -->
<!-- NOT SETTING -->
<!-- SELECT Dummy
option을 script로 넣을 때 사용
예: 시스템 - 전체로그
-->
{% macro setting_select_empty(id, title, col='9', desc=None, value=None) %}
{{ setting_top(title) }}
<div class="input-group col-sm-{{col}}">
<div id="{{id}}_div" name="{{id}}_div"></div>
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
{% macro setting_input_int(id, left, value='', min='', max='', placeholder='', desc=None) %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
<input id="{{ id }}" name="{{ id }}" type="number" class="form-control form-control-sm"
{% if min != '' %}
min="{{ min }}"
{% endif %}
{% if max != '' %}
max="{{ max }}"
{% endif %}
{% if placeholder != '' %}
placeholder="{{ placeholder }}"
{% endif %}
value="{{ value }}">
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
<!-- 토글버튼형식 -->
{% macro setting_checkbox(id, left, value, desc='') %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
{% if value == True or value == 'True' or value == 'true' or value == 'On' %}
<input id="{{ id }}" name="{{ id }}" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
{% else %}
<input id="{{ id }}" name="{{ id }}" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
{% endif %}
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
<!------------------------------------------------------------------>
<!-- 설정 외 -->
<!-- 리스트 div로 꾸밀때 헤드 -->
{% macro m_hr_head_top() %}
<div class="d-inline-block"></div>
<hr style="width: 100%; margin:0px; background-color:#808080;">
{% endmacro %}
{% macro m_hr_head_bottom() %}
<hr style="width: 100%; margin:0px; margin-bottom:10px; margin-top:2px; background-color:#808080; height:2px" />
{% endmacro %}
<!-- 버튼 그룹 -->
{% macro m_button_group(buttons) %}
<div class="btn-group btn-group-sm flex-wrap mr-2" role="group">
@@ -304,6 +451,14 @@
{{ setting_bottom(desc, padding_top='-5') }}
{% endmacro %}
{% macro info_text_big(id, left, value='', desc=None) %}
{{ setting_top_big(left) }}
<div style="padding-left:20px; padding-top:-5px;">
<span id={{id}}><h4>{{value}}</h4></span>
</div>
{{ setting_bottom(desc, padding_top='-5') }}
{% endmacro %}
{% macro info_text_go(id, left, value='', desc=None, padding=10) %}
{{ setting_top(left, padding) }}
<div style="padding-left:20px; padding-top:-5px;">
@@ -354,219 +509,208 @@
<!-- SELECT Dummy
option을 script로 넣을 때 사용
예: 시스템 - 전체로그
-->
{% macro setting_select_empty(id, title, col='9', desc=None, value=None) %}
{{ setting_top(title) }}
<div class="input-group col-sm-{{col}}">
<div id="{{id}}_div" name="{{id}}_div"></div>
</div>
{{ setting_bottom(desc) }}
{% macro m_modal_start(id, title, size) %}
<!-- Modal -->
<div class="modal fade" id="{{id}}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog {{size}}">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="{{id}}_title">{{title}}</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body" id="{{id}}_modal_body" style="word-break:break-all;">
{% endmacro %}
<!-- 삭제해야함 --------------------------------------------------------->
<!--
{% macro setting_radio(id, title, radios, value=None, desc=None, disabled=False) %}
{{ setting_top(title) }}
<div class="input-group col-sm-9">
{% for r in radios %}
<div class="custom-control custom-radio custom-control-inline">
{% if value|int == loop.index0 %}
<input id="{{id}}{{loop.index0}}" type="radio" class="custom-control-input" name="{{id}}" value="{{loop.index0}}" checked {% if disabled %} disabled {% endif %}>
{% else %}
<input id="{{id}}{{loop.index0}}" type="radio" class="custom-control-input" name="{{id}}" value="{{loop.index0}}" {% if disabled %} disabled {% endif %}>
{% endif %}
<label class="custom-control-label" for="{{id}}{{loop.index0}}">{{r}}</label>
{% macro m_modal_end() %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-warning" data-dismiss="modal">닫기 (취소)</button>
</div>
<div class="loading" id="modal_loading">
<img src="/static/img/loading.gif" />
</div>
{% endfor %}
</div>
{{ setting_bottom(desc) }}
</div>
</div>
<!-- Modal end -->
{% endmacro %}
-->
<!-- 그룹화 하지 않음.. 삭제-->
<!--
{% macro setting_button(buttons, left='', desc='') %}
{{ setting_top(left) }}
<div class="input-group col-sm-9">
{% for b in buttons %}
{% if not loop.first %}
<span class='text-left' style="padding-left:5px; padding-top:0px">
{% endif %}
<button id="{{b[0]}}" class="btn btn-sm btn-outline-primary">{{b[1]}}</button>
</span>
{% endfor %}
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
-->
<!----------------------------------------------------------------->
{% macro setting_input_int(id, left, value='', min='', max='', placeholder='', desc=None) %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
<input id="{{ id }}" name="{{ id }}" type="number" class="form-control form-control-sm"
{% if min != '' %}
min="{{ min }}"
{% endif %}
{% if max != '' %}
max="{{ max }}"
{% endif %}
{% if placeholder != '' %}
placeholder="{{ placeholder }}"
{% endif %}
value="{{ value }}">
{% macro m_modal_end_with_button(buttons) %}
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
<!-- 토글버튼형식 -->
{% macro setting_checkbox(id, left, value, desc='') %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
{% if value == 'True' or value == 'true' or value == 'On' %}
<input id="{{ id }}" name="{{ id }}" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
{% else %}
<input id="{{ id }}" name="{{ id }}" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
{% endif %}
<div class="modal-footer">
<div class="btn-group btn-group-sm flex-wrap mr-2" role="group">
{% for b in buttons %}
<button id="{{b[0]}}" class="btn btn-sm btn-outline-primary"
{% if b|length > 2 %}
{% for d in b[2] %}
data-{{d[0]}}="{{d[1]}}""
{% endfor %}
{% endif %}
>{{b[1]}}</button>
{% endfor %}
<button type="button" class="btn btn-sm btn-warning" data-dismiss="modal">닫기 (취소)</button>
</div>
</div>
{{ setting_bottom(desc) }}
<div class="loading" id="modal_loading">
<img src="/static/img/loading.gif" />
</div>
</div>
</div>
</div>
<!-- Modal end -->
{% endmacro %}
{% macro print_md(id, text) %}
<div id="{{id}}_div" data-text="{{text}}"></div>
<script type="text/javascript">
ret = converter.makeHtml($('#{{id}}_div').data('text'));
$('#{{id}}_div').html(ret);
</script>
{% endmacro %}
{% macro print_md1(id, text) %}
<script type="text/javascript">
ret = converter.makeHtml($('#{{id}}_div').data('text'));
</script>
{% endmacro %}
<!----------------------------------------------------------->
<!----------------------------------------------------------->
<!----------------------------------------------------------->
<!----------------------------------------------------------->
<!----------------------------------------------------------->
<!--이하 정리 필요------------------------>
<!-- 일반적인 체크박스 -->
{% macro setting_default_checkbox(id, left, label, value, desc='') %}
@@ -584,31 +728,6 @@ option을 script로 넣을 때 사용
{{ setting_bottom(desc) }}
{% endmacro %}
<!-- 스케쥴러 스위치 체크박스 전용-->
{% macro setting_scheduler_switch(left='스케쥴링 작동', desc=['On : 스케쥴링 시작','Off : 스케쥴링 중지'], is_include='False', is_running='False') %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
{% if is_include == 'True' %}
<input id="scheduler_swtich_btn" name="scheduler_swtich_btn" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
{% else %}
<input id="scheduler_swtich_btn" name="scheduler_swtich_btn" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
{% endif %}
{% if is_running == 'True' %}
<span style="padding-left:10px; padding-top: 8px;">동작중</span>
{% else %}
{% if is_include == 'True' %}
<span style="padding-left:10px; padding-top: 8px;">대기중</span>
{% endif %}
{% endif %}
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
<!--
@@ -637,6 +756,26 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
{% macro select(id, options, col='3', value=None) %}
<div class="input-group col-sm-{{col}}" style="padding-left:0px; padding-top:0px">
<select id="{{id}}" name="{{id}}" class="form-control form-control-sm">
{% for item in options %}
{% if value is not none and value == item[0] %}
<option value="{{ item[0] }}" selected>{{item[1]}}</option>
{% else %}
<option value="{{ item[0] }}">{{item[1]}}</option>
{% endif %}
{% endfor %}
</select>
</div>
{% endmacro %}
<!-- select -->
{% macro setting_select(id, title, options, col='9', desc=None, value=None) %}
{{ setting_top(title) }}
@@ -655,21 +794,6 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
{{ setting_bottom(desc) }}
{% endmacro %}
{% macro select(id, options, col='3', value=None) %}
<div class="input-group col-sm-{{col}}" style="padding-left:0px; padding-top:0px">
<select id="{{id}}" name="{{id}}" class="form-control form-control-sm">
{% for item in options %}
{% if value is not none and value == item[0] %}
<option value="{{ item[0] }}" selected>{{item[1]}}</option>
{% else %}
<option value="{{ item[0] }}">{{item[1]}}</option>
{% endif %}
{% endfor %}
</select>
</div>
{% endmacro %}
<!-- select + 버튼 -->
@@ -703,15 +827,6 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
<!--progress-bar-striped progress-bar-animated-->
{% macro setting_progress(id, left='', desc='') %}
{{ setting_top(left) }}
@@ -725,66 +840,6 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
{% endmacro %}
<!-- 스케쥴링 작동 버튼-->
{% macro setting_scheduler_button(is_include, is_running, id='scheduler', left='스케쥴링 작동', desc=['On : 스케쥴링 시작','Off : 스케쥴링 중지']) %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
{% if is_include == 'True' %}
<input id="scheduler" name="scheduler" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
{% else %}
<input id="scheduler" name="scheduler" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
{% endif %}
{% if is_running == 'True' %}
<span style="padding-left:10px; padding-top: 8px;">동작중</span>
{% else %}
{% if is_include == 'True' %}
<span style="padding-left:10px; padding-top: 8px;">대기중</span>
{% endif %}
{% endif %}
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
{% macro setting_global_scheduler_sub_button(is_include, is_running, id='scheduler', left='스케쥴링 작동', desc=['On : 스케쥴링 시작','Off : 스케쥴링 중지']) %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
{% if is_include == 'True' %}
<input id="global_scheduler_sub" name="global_scheduler_sub" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
{% else %}
<input id="global_scheduler_sub" name="global_scheduler_sub" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
{% endif %}
{% if is_running == 'True' %}
<span style="padding-left:10px; padding-top: 8px;">동작중</span>
{% else %}
{% if is_include == 'True' %}
<span style="padding-left:10px; padding-top: 8px;">대기중</span>
{% endif %}
{% endif %}
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
{% macro setting_global_scheduler_sublogic_button(is_include, is_running, id='scheduler', left='스케쥴링 작동', desc=['On : 스케쥴링 시작','Off : 스케쥴링 중지']) %}
{{ setting_top(left) }}
<div class="input-group col-sm-3">
{% if is_include == 'True' %}
<input id="global_scheduler_sublogic" name="global_scheduler_sublogic" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
{% else %}
<input id="global_scheduler_sublogic" name="global_scheduler_sublogic" class="form-control form-control-sm" type="checkbox" data-toggle="toggle">
{% endif %}
{% if is_running == 'True' %}
<span style="padding-left:10px; padding-top: 8px;">동작중</span>
{% else %}
{% if is_include == 'True' %}
<span style="padding-left:10px; padding-top: 8px;">대기중</span>
{% endif %}
{% endif %}
</div>
{{ setting_bottom(desc) }}
{% endmacro %}
@@ -803,14 +858,7 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
{% endmacro %}
{% macro m_hr_head_top() %}
<div class="d-inline-block"></div>
<hr style="width: 100%; margin:0px; background-color:#808080;">
{% endmacro %}
{% macro m_hr_head_bottom() %}
<hr style="width: 100%; margin:0px; margin-bottom:10px; margin-top:2px; background-color:#808080; height:2px" />
{% endmacro %}
{% macro m_button(id, text) %}
<button id="{{id}}" name="{{id}}" class="btn btn-sm btn-outline-primary">{{text}}</button>
@@ -836,59 +884,6 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
{% macro m_modal_start(id, title, size) %}
<!-- Modal -->
<div class="modal fade" id="{{id}}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog {{size}}">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="{{id}}_title">{{title}}</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
</div>
<div class="modal-body" id="modal_body" style="word-break:break-all;">
{% endmacro %}
{% macro m_modal_end() %}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">닫기</button>
</div>
<div class="loading" id="modal_loading">
<img src="/static/img/loading.gif" />
</div>
</div>
</div>
</div>
<!-- Modal end -->
{% endmacro %}
{% macro m_modal_start2(id, title, size) %}
<!-- Modal -->
<div class="modal fade" id="{{id}}" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog {{size}}">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title" id="{{id}}_title">{{title}}</h4>
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
</div>
<div class="loading" id="modal_loading">
<img src="/static/img/loading.gif" />
</div>
{% endmacro %}
{% macro m_modal_end2() %}
</div>
</div>
</div>
<!-- Modal end -->
{% endmacro %}
{% macro row_start(padding='10') %}
<div class='row' style="padding-top: {{padding}}px; padding-bottom:{{padding}}px; align-items: center;">
@@ -1002,16 +997,3 @@ macros.setting_button_with_info([['toggle_btn', 'Toggle', [{'key':'category', 'v
<!-- 다른이름으로 정의함. 나중에 삭제 -->
{% macro buttons(buttons, left='', desc='') %}
{{ setting_top(left) }}
<div class="input-group col-sm-9">
<div class="btn-group btn-group-sm flex-wrap mr-2" role="group">
{% for b in buttons %}
<button id="{{b[0]}}" class="btn btn-sm btn-outline-primary">{{b[1]}}</button>
{% endfor %}
</div>
</div>
{{ setting_bottom(desc) }}
{% endmacro %}

View File

@@ -14,7 +14,7 @@
<div class="modal-body" id="modal_body" style="word-break:break-all;">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">닫기</button>
<button type="button" class="btn btn-warning" data-dismiss="modal">닫기</button>
<!--<button type="button" class="btn btn-primary">Save changes</button>-->
</div>
</div>
@@ -70,7 +70,7 @@
<div class="modal-footer">
<button type="button" id='select_local_file_modal_confirm_btn' class="btn btn-success" data-dismiss="modal">선택
</button>
<button type="button" id='select_local_file_modal_cancel_btn' class="btn btn-default" data-dismiss="modal">닫기
<button type="button" id='select_local_file_modal_cancel_btn' class="btn btn-warning" data-dismiss="modal">닫기
</button>
</div>
</div>

View File

@@ -19,7 +19,7 @@
{% if 'uri' in category and category['uri'].startswith('http') %}
<li class="nav-item"> <a class="nav-link" href="{{ category['uri']}}" target="_blank">{{category['name']}}</a></li>
{% elif 'uri' in category and category['uri'].startswith('http') == False %}
<li class="nav-item"> <a class="nav-link" href="{{ category['uri']}}">{{category['name']}}</a></li>
<li class="nav-item"> <a class="nav-link" href="/{{ category['uri']}}">{{category['name']}}</a></li>
{% else %}
<!--{{ category }}-->
<li class="nav-item dropdown">
@@ -134,10 +134,11 @@
{% if current_menu[0] == plugin['uri'] and 'list' in plugin %}
{% for module in plugin['list'] %}
{% if module['uri'] == current_menu[1] and 'list' in module%}
<!--{{ module }}-->
<!-- {{ module }} -->
<ul class="nav nav-pills bg-light shadow text-dark">
{% for page in module['list'] %}
{% if current_menu[2] == page['uri'] %}
<!--{{ current_menu }}-->
{% if current_menu[2]!= None and page['uri'].startswith(current_menu[2]) %}
<li class="nav-item"><a class="nav-link active" href="/{{ current_menu[0] }}/{{ current_menu[1] }}/{{ page['uri'] }}">{{page['name']}}</a></li>
{% else %}
<li class="nav-item"><a class="nav-link" href="/{{ current_menu[0] }}/{{ current_menu[1] }}/{{ page['uri'] }}">{{page['name']}}</a></li>

View File

@@ -1,30 +1,32 @@
{% extends "base.html" %}
{% block content %}
{% filter markdown %}
{{ data }}
{% endfilter %}
<script src="https://cdn.jsdelivr.net/gh/google/code-prettify@master/loader/run_prettify.js?autorun=true&amp;lang=css&lang=python&skin=sunburst"></script>
<style type="text/css">
img{
<script src="{{ url_for('static', filename='js/showdown_2.1.0.js') }}"></script>
<script src="{{ url_for('static', filename='js/showdown-prettify.js') }}"></script>
<link href="{{ url_for('static', filename='css/showdown.css') }}" rel="stylesheet">
display: block;
max-width: 100%;
margin-right: auto;
}
</style>
<div id="md_div" data-url="{{ arg }}"></div>
<div id="content_div" data-url="{{ arg }}"></div>
<meta id="text" data-text="{{data}}">
<div id="text_div"></div>
<script type="text/javascript">
$(document).ready(function(){
//$('#main_container').attr('class', 'container-fluid');
});
$(document).ready(function(){
var converter = new showdown.Converter({extensions: ['prettify']});
converter.setOption('tables', true);
converter.setOption('strikethrough', true);
converter.setOption('ghCodeBlocks',true);
text = $('#text').data('text');
if (window.location.href.endsWith('.yaml')) {
text = "```" + text + "```";
}
html = converter.makeHtml(text);
$('#text_div').html(html);
});
</script>
{% endblock %}

View File

@@ -0,0 +1,30 @@
{% extends "base.html" %}
{% block content %}
{% filter markdown %}
{{ data }}
{% endfilter %}
<style type="text/css">
img{
display: block;
max-width: 100%;
margin-right: auto;
}
</style>
<div id="md_div" data-url="{{ arg }}"></div>
<div id="content_div" data-url="{{ arg }}"></div>
<script type="text/javascript">
$(document).ready(function(){
//$('#main_container').attr('class', 'container-fluid');
});
</script>
{% endblock %}

View File

@@ -1,3 +1,5 @@
<html>
<title>{{data['play_title']}}</title>
<script src="https://vjs.zencdn.net/7.11.4/video.min.js"></script>
<link href="https://vjs.zencdn.net/7.11.4/video-js.css" rel="stylesheet" />
@@ -63,3 +65,4 @@ player.ready(function(){
player.play();
</script>
</html>

View File

@@ -0,0 +1,90 @@
<title>aaaa</title>
<meta charset="UTF-8">
<meta content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" name="viewport">
<meta content="ie=edge" http-equiv="X-UA-Compatible">
<meta content="width=device-width, initial-scale=1" name="viewport">
<link href="/media/avatar.png" rel="icon" type="image/jpeg">
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.1/dist/css/bootstrap.min.css" integrity="sha384-F3w7mX95PdgyTmZZMECAngseQB83DfGTowi0iMjiWaeVhAn4FJkqJByhZMI3AhiU" rel="stylesheet" />
<link href="https://unpkg.com/aos@2.3.1/dist/aos.css" rel="stylesheet" />
<link href="https://unpkg.com/swiper@7/swiper-bundle.min.css" rel="stylesheet" />
<link href='https://unpkg.com/boxicons@2.1.2/css/boxicons.min.css' rel='stylesheet'>
<link href="/css/style.css" rel="stylesheet" />
<link href="/css/style_dark.css" rel="stylesheet" />
<link href="/media/favicon.png" rel="icon" type="image/jpeg">
<meta property="og:site_name" content="aaaaaaaaaaaaaaaaa" />
<meta property="og:url" content="https://ff.soju6jan.synology.me/gds_tool/api/route/streaming.mp4?apikey=ooo5298ooo&type=file&id=1gtpG7CAUKTWu6wxWtCKx-XN01PMz70v8" />
<meta property="og:type" content="video.other" />
<meta property="og:title" content="Mini rengar xD" />
<meta property="og:image" content="https://outplays.eu/Q5THkfY3/thumbnail.png" />
<meta property="og:video" content="https://ff.soju6jan.synology.me/gds_tool/api/route/streaming.mp4?apikey=ooo5298ooo&type=file&id=1gtpG7CAUKTWu6wxWtCKx-XN01PMz70v8" />
<meta property="og:video:type" content="video/mp4" />
<meta property="og:video:secure_url" content="https://ff.soju6jan.synology.me/gds_tool/api/route/streaming.mp4?apikey=ooo5298ooo&type=file&id=1gtpG7CAUKTWu6wxWtCKx-XN01PMz70v8" />
<meta property="og:video:height" content="1080" />
<meta property="og:video:width" content="1920" />
<meta property="og:image:height" content="1080" />
<meta property="og:image:width" content="1920" />
<script src="https://vjs.zencdn.net/7.11.4/video.min.js"></script>
<link href="https://vjs.zencdn.net/7.11.4/video-js.css" rel="stylesheet" />
<body bgcolor='black'>
<video id=player width=960 height=540 class="video-js vjs-default-skin vjs-16-9" autoplay controls>
<source
src="https://ff.soju6jan.synology.me/gds_tool/api/route/streaming.mp4?apikey=ooo5298ooo&type=file&id=1gtpG7CAUKTWu6wxWtCKx-XN01PMz70v8"
type="application/x-mpegURL" />
</video>
</body>
<script>
var subtitle_src = "aaaa";
let options = {
html5: {
nativeTextTracks: false
},
playbackRates: [.5, .75, 1, 1.5, 2],
controls: true,
preload: "auto",
controlBar: {
playToggle: false,
pictureInPictureToggle: false,
remainingTimeDisplay: true,
qualitySelector: true,
}
};
let player = videojs('player', options);
player.ready(function(){
// set subtitle track
if (subtitle_src != "") {
var suburl = subtitle_src.replace(/&amp;/g, '&');
let captionOption = {
kind: 'captions',
srclang: 'ko',
label: 'Korean',
src: suburl,
mode: 'showing'
};
player.addRemoteTextTrack(captionOption);
var settings = this.textTrackSettings;
settings.setValues({
"backgroundColor": "#000",
"backgroundOpacity": "0",
"edgeStyle": "uniform",
});
settings.updateDisplay();
}
else {
var tracks = player.textTracks();
for (var i = 0; i < tracks.length; i++) {
var track = tracks[i];
}
}
});
player.play();
</script>

View File

@@ -34,8 +34,8 @@ class Util(object):
paging['count'] = count
F.logger.debug('paging : c:%s %s %s %s %s %s', count, paging['total_page'], paging['prev_page'], paging['next_page'] , paging['start_page'], paging['last_page'])
return paging
except Exception as exception:
F.logger.debug('Exception:%s', exception)
except Exception as e:
F.logger.debug(f"Exception:{str(e)}")
F.logger.debug(traceback.format_exc())
@@ -60,8 +60,8 @@ class Util(object):
ret['dirname'] = max_filename.replace('/%s' % ret['filename'], '')
ret['max_size'] = max_size
return ret
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
@@ -81,8 +81,8 @@ class Util(object):
import shutil
shutil.rmtree(zip_path)
return True
except Exception as exception:
F.logger.error('Exception:%s', exception)
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
return False
@@ -92,12 +92,12 @@ class Util(object):
def make_apikey(url):
from framework import SystemModelSetting
url = url.format(ddns=SystemModelSetting.get('ddns'))
if SystemModelSetting.get_bool('auth_use_apikey'):
if SystemModelSetting.get_bool('use_apikey'):
if url.find('?') == -1:
url += '?'
else:
url += '&'
url += 'apikey=%s' % SystemModelSetting.get('auth_apikey')
url += 'apikey=%s' % SystemModelSetting.get('apikey')
return url

View File

@@ -1 +1 @@
VERSION="4.0.47"
VERSION="4.1.40"

View File

@@ -1,5 +1,6 @@
# 순서 바꾸지 말 것
import os, sys, traceback, re, threading, time, queue
import os, sys, traceback, re, threading, time, queue, json, shutil, yaml
import requests
from datetime import datetime, timedelta
from flask import Blueprint, render_template, jsonify, redirect, request
from sqlalchemy import desc, or_

View File

@@ -94,8 +94,8 @@ class FfmpegQueue(object):
self.download_thread = threading.Thread(target=self.download_thread_function, args=())
self.download_thread.daemon = True
self.download_thread.start()
except Exception as exception:
self.P.logger.error('Exception:%s', exception)
except Exception as e:
self.P.logger.error(f"Exception:{str(e)}")
self.P.logger.error(traceback.format_exc())
@@ -107,8 +107,8 @@ class FfmpegQueue(object):
if self.current_ffmpeg_count < self.max_ffmpeg_count:
break
time.sleep(5)
except Exception as exception:
self.P.logger.error('Exception:%s', exception)
except Exception as e:
self.P.logger.error(f"Exception:{str(e)}")
self.P.logger.error(traceback.format_exc())
self.P.logger.error('current_ffmpeg_count : %s', self.current_ffmpeg_count)
self.P.logger.error('max_ffmpeg_count : %s', self.max_ffmpeg_count)
@@ -153,8 +153,8 @@ class FfmpegQueue(object):
f.start()
self.current_ffmpeg_count += 1
self.download_queue.task_done()
except Exception as exception:
self.P.logger.error('Exception:%s', exception)
except Exception as e:
self.P.logger.error(f"Exception:{str(e)}")
self.P.logger.error(traceback.format_exc())
def ffmpeg_listener(self, **arg):
@@ -203,8 +203,8 @@ class FfmpegQueue(object):
self.entity_list.append(entity)
self.download_queue.put(entity)
return True
except Exception as exception:
self.P.logger.error('Exception:%s', exception)
except Exception as e:
self.P.logger.error(f"Exception:{str(e)}")
self.P.logger.error(traceback.format_exc())
return False
@@ -270,8 +270,8 @@ class FfmpegQueue(object):
self.entity_list = new_list
ret['ret'] = 'refresh'
return ret
except Exception as exception:
self.P.logger.error('Exception:%s', exception)
except Exception as e:
self.P.logger.error(f"Exception:{str(e)}")
self.P.logger.error(traceback.format_exc())

View File

@@ -8,6 +8,8 @@ from support import SupportYaml
from . import (Logic, default_route, default_route_single_module,
get_model_setting)
from loguru import logger as logger1
class PluginBase(object):
package_name = None
@@ -52,6 +54,7 @@ class PluginBase(object):
self.home_module = setting.get('home_module')
self.status = "init_success"
self.config = {}
self.recent_menu_plugin_except_list = setting.get('recent_menu_plugin_except_list', [])
except Exception as e:
self.logger.error(f'Exception:{str(e)}')
self.logger.error(traceback.format_exc())
@@ -59,10 +62,16 @@ class PluginBase(object):
def set_module_list(self, mod_list):
try:
# self.module_list = []
for mod in mod_list:
logger1.debug(mod)
mod_ins = mod(self)
# self.logger.debug(mod_ins)
logger1.debug(mod_ins)
self.module_list.append(mod_ins)
if self.home_module == None:
self.home_module = self.module_list[0].name
except Exception as e:
F.logger.error(f'[{self.package_name}] Exception:{str(e)}')
F.logger.error(traceback.format_exc())
@@ -77,16 +86,31 @@ class PluginBase(object):
def plugin_load(self):
self.logic.plugin_load()
if self.logic:
self.logic.plugin_load()
def plugin_load_celery(self):
if self.logic:
self.logic.plugin_load_celery()
def plugin_unload(self):
self.logic.plugin_unload()
if self.logic:
self.logic.plugin_unload()
def get_first_manual_path(self):
for __ in self.menu['list']:
if __['uri'] == 'manual' and len(__['list']) > 0:
return __['list'][0]['uri']
def get_module(self, sub):
try:
for module in self.module_list:
if module.name == sub:
return module
except Exception as e:
self.logger.error(f'Exception:{str(e)}')
#self.P.logger.error(traceback.format_exc())
def create_plugin_instance(config):
ins = PluginBase(config)

View File

@@ -32,14 +32,39 @@ class Logic(object):
if self.P.ModelSetting is not None:
for module in self.P.module_list:
key = f'{module.name}_auto_start'
if self.P.ModelSetting.has_key(key) and self.P.ModelSetting.get_bool(key):
key2 = f'{module.name}_interval'
if self.P.ModelSetting.has_key(key) and self.P.ModelSetting.get_bool(key) and self.P.ModelSetting.has_key(key2):
self.scheduler_start(module.name)
if module.page_list is not None:
for page_instance in module.page_list:
key = f'{module.name}_{page_instance.name}_auto_start'
if self.P.ModelSetting.has_key(key) and self.P.ModelSetting.get_bool(key):
self.scheduler_start_sub(module.name, page_instance.name)
key1 = f'{module.name}_db_auto_delete'
key2 = f'{module.name}_db_delete_day'
if self.P.ModelSetting.has_key(key1) and self.P.ModelSetting.has_key(key2) and self.P.ModelSetting.get_bool(key1):
try: module.db_delete(self.P.ModelSetting.get_int(key2))
except: pass
if module.page_list == None:
continue
for page_instance in module.page_list:
key1 = f'{module.name}_{page_instance.name}_db_auto_delete'
key2 = f'{module.name}_{page_instance.name}_db_delete_day'
if self.P.ModelSetting.has_key(key1) and self.P.ModelSetting.has_key(key2) and self.P.ModelSetting.get_bool(key1):
try: page_instance.db_delete(self.P.ModelSetting.get_int(key2))
except: pass
def plugin_load_celery(self):
self.P.logger.debug('%s plugin_load_celery', self.P.package_name)
for module in self.P.module_list:
module.plugin_load_celery()
if module.page_list is not None:
for page_instance in module.page_list:
page_instance.plugin_load_celery()
def db_init(self):
try:
@@ -88,7 +113,7 @@ class Logic(object):
try:
job_id = '%s_%s' % (self.P.package_name, module_name)
module = self.get_module(module_name)
job = Job(self.P.package_name, job_id, module.get_scheduler_interval(), self.scheduler_function, module.get_scheduler_desc(), args=module_name)
job = Job(self.P.package_name, job_id, module.get_scheduler_interval(), self.scheduler_function, module.get_scheduler_desc(), args=(module_name,))
F.scheduler.add_job_instance(job)
except Exception as e:
self.P.logger.error(f'Exception:{str(e)}')
@@ -112,15 +137,25 @@ class Logic(object):
self.P.logger.error(f'Exception:{str(e)}')
self.P.logger.error(traceback.format_exc())
def reset_db(self, module_name):
def db_delete(self, module_name, page_name, day):
try:
module = self.get_module(module_name)
return module.reset_db()
if module == None:
return False
if page_name != None:
page = module.get_page(page_name)
if page != None:
return page.db_delete(day)
else:
return module.db_delete(day)
except Exception as e:
self.P.logger.error(f'Exception:{str(e)}')
self.P.logger.error(traceback.format_exc())
def one_execute(self, module_name):
self.P.logger.debug('one_execute :%s', module_name)
try:
@@ -166,6 +201,7 @@ class Logic(object):
self.P.logger.error(f'Exception:{str(e)}')
self.P.logger.error(traceback.format_exc())
"""
def process_telegram_data(self, data, target=None):
try:
for module in self.P.module_list:
@@ -174,7 +210,7 @@ class Logic(object):
except Exception as e:
self.P.logger.error(f'Exception:{str(e)}')
self.P.logger.error(traceback.format_exc())
"""
@@ -303,12 +339,23 @@ class Logic(object):
def arg_to_dict(self, arg):
"""
import urllib.parse
tmp = urllib.parse.unquote(arg)
tmps = tmp.split('&')
ret = {}
for tmp in tmps:
_ = tmp.split('=')
_ = tmp.split('=', 1)
ret[_[0]] = _[1]
return ret
"""
import html
import urllib.parse
char = '||!||'
arg = arg.replace('&amp;', char)
tmp = html.unescape(arg)
tmp = urllib.parse.unquote(tmp)
tmp = dict(urllib.parse.parse_qs(tmp, keep_blank_values=True))
ret = {k: v[0].replace(char, '&') for k, v in tmp.items()}
return ret

View File

@@ -39,6 +39,8 @@ class PluginModuleBase(object):
def get_page(self, page_name):
try:
if self.page_list == None:
return
for page in self.page_list:
if page_name == page.name:
return page
@@ -48,10 +50,22 @@ class PluginModuleBase(object):
def process_menu(self, page, req):
if self.page_list is not None:
page_ins = self.get_page(page)
if page_ins != None:
return page_ins.process_menu(req)
from framework import F
try:
if self.page_list is not None:
page_ins = self.get_page(page)
if page_ins != None:
return page_ins.process_menu(req)
arg = self.P.ModelSetting.to_dict() if self.P.ModelSetting != None else {}
arg['path_data'] = F.config['path_data']
arg['is_include'] = F.scheduler.is_include(self.get_scheduler_name())
arg['is_running'] = F.scheduler.is_running(self.get_scheduler_name())
return render_template(f'{self.P.package_name}_{self.name}_{page}.html', arg=arg)
except Exception as e:
self.P.logger.error(f'Exception:{str(e)}')
self.P.logger.error(traceback.format_exc())
return render_template('sample.html', title=f"PluginModuleBase-process_menu{self.P.package_name}/{self.name}/{page}")
def process_ajax(self, sub, req):
@@ -69,12 +83,18 @@ class PluginModuleBase(object):
def scheduler_function(self):
pass
def reset_db(self):
pass
def db_delete(self, day):
if self.web_list_model != None:
return self.web_list_model.delete_all(day)
def plugin_load(self):
pass
def plugin_load_celery(self):
pass
def plugin_unload(self):
pass
@@ -115,12 +135,37 @@ class PluginModuleBase(object):
pass
def arg_to_dict(self, arg):
return self.P.logic.arg_to_dict(arg)
def get_scheduler_name(self):
return f'{self.P.package_name}_{self.name}'
def process_discord_data(self, data):
pass
def start_celery(self, func, on_message=None, *args, page=None):
from framework import F
if F.config['use_celery']:
result = func.apply_async(args)
try:
if on_message != None:
ret = result.get(on_message=on_message, propagate=True)
else:
ret = result.get()
except:
ret = result.get()
else:
if on_message == None:
ret = func(*args)
else:
if page == None:
ret = func(self, *args)
else:
ret = func(page, *args)
return ret
@@ -153,7 +198,8 @@ class PluginPageBase(object):
arg = self.P.ModelSetting.to_dict()
return render_template(f'{self.P.package_name}_{self.parent.name}_{self.name}.html', arg=arg)
except Exception as e:
pass
self.P.logger.error(f'Exception:{str(e)}')
self.P.logger.error(traceback.format_exc())
return render_template('sample.html', title=f"PluginPageBase-process_menu --- {self.P.package_name}/{self.parent.name}/{self.name}")
@@ -176,6 +222,9 @@ class PluginPageBase(object):
def plugin_load(self):
pass
def plugin_load_celery(self):
pass
# logic
def plugin_unload(self):
pass
@@ -207,3 +256,41 @@ class PluginPageBase(object):
pass
def arg_to_dict(self, arg):
return self.P.logic.arg_to_dict(arg)
def get_page(self, page_name):
return self.parent.get_page(page_name)
def get_module(self, module_name):
return self.parent.get_module(module_name)
def process_discord_data(self, data):
pass
def db_delete(self, day):
if self.web_list_model != None:
return self.web_list_model.delete_all(day)
def start_celery(self, func, on_message, *args):
return self.parent.start_celery(func, on_message, *args, page=self)
"""
def start_celery(self, func, on_message=None, *args):
from framework import F
if F.config['use_celery']:
result = func.apply_async(args)
try:
if on_message != None:
ret = result.get(on_message=on_message, propagate=True)
else:
ret = result.get()
except:
ret = result.get()
else:
ret = func(*args)
return ret
"""

View File

@@ -1,3 +1,4 @@
import sqlite3
import traceback
from datetime import datetime, timedelta
@@ -45,7 +46,7 @@ class ModelBase(F.db.Model):
paging['next_page'] = False
paging['current_page'] = current_page
paging['count'] = count
F.logger.debug('paging : c:%s %s %s %s %s %s', count, paging['total_page'], paging['prev_page'], paging['next_page'] , paging['start_page'], paging['last_page'])
#F.logger.debug('paging : c:%s %s %s %s %s %s', count, paging['total_page'], paging['prev_page'], paging['next_page'] , paging['start_page'], paging['last_page'])
return paging
except Exception as e:
cls.P.logger.error(f'Exception:{str(e)}')
@@ -89,24 +90,30 @@ class ModelBase(F.db.Model):
return False
@classmethod
def delete_all(cls, days=None):
def delete_all(cls, day=None):
count = -1
try:
with F.app.app_context():
if days == None:
F.db.session.query(cls).delete()
F.db.session.commit()
if day == None or day in [0, '0']:
count = F.db.session.query(cls).delete()
else:
now = datetime.now()
ago = now - timedelta(days=int(days))
ago = now - timedelta(days=int(day))
#ago.hour = 0
#ago.minute = 0
count = F.db.session.query(cls).filter(cls.created_time > ago).delete()
cls.P.logger.info(f"delete_all {days=} {count=}")
return True
count = F.db.session.query(cls).filter(cls.created_time < ago).delete()
cls.P.logger.info(f"delete_all {day=} {count=}")
F.db.session.commit()
db_file = F.app.config['SQLALCHEMY_BINDS'][cls.P.package_name].replace('sqlite:///', '').split('?')[0]
connection = sqlite3.connect(db_file)
cursor = connection.cursor()
cursor.execute('VACUUM;')
connection.close()
except Exception as e:
cls.P.logger.error(f'Exception:{str(e)}')
cls.P.logger.error(traceback.format_exc())
return False
return count
@classmethod
@@ -135,12 +142,12 @@ class ModelBase(F.db.Model):
if cls.P.ModelSetting is not None and cls.__tablename__ is not None:
cls.P.ModelSetting.set(f'{cls.__tablename__}_last_list_option', f'{order}|{page}|{search}|{option1}|{option2}')
except Exception as e:
F.logger.error('Exception:%s', e)
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
F.logger.error(f'{cls.__tablename__}_last_list_option ERROR!' )
return ret
except Exception as e:
cls.P.logger.error('Exception:%s', e)
cls.P.logger.error(f"Exception:{str(e)}")
cls.P.logger.error(traceback.format_exc())
@@ -149,6 +156,10 @@ class ModelBase(F.db.Model):
def make_query(cls, req, order='desc', search='', option1='all', option2='all'):
with F.app.app_context():
query = F.db.session.query(cls)
if order == 'desc':
query = query.order_by(desc(cls.id))
else:
query = query.order_by(cls.id)
return query
@@ -171,3 +182,16 @@ class ModelBase(F.db.Model):
query = query.filter(field.like('%'+search+'%'))
#query = query1.union(query2)
return query
@classmethod
def get_list_by_status(cls, status):
try:
with F.app.app_context():
query = F.db.session.query(cls).filter(
cls.status == status,
)
query = query.order_by(cls.id)
return query.all()
except:
pass

View File

@@ -107,6 +107,8 @@ def get_model_setting(package_name, logger, table_name=None):
if ModelSetting.get(key) != value:
change_list.append(key)
entity = F.db.session.query(ModelSetting).filter_by(key=key).with_for_update().first()
if entity == None:
logger.warning(f"NOT exist setting key: {key}")
entity.value = value
F.db.session.commit()
return True, change_list
@@ -117,7 +119,8 @@ def get_model_setting(package_name, logger, table_name=None):
return False, []
@staticmethod
def get_list(key, delimeter='\n', comment=' #'):
def get_list(key, delimeter='\n', comment='#'):
value = None
try:
value = ModelSetting.get(key).replace('\n', delimeter)
if comment is None:

View File

@@ -27,7 +27,12 @@ def default_route(P):
def first_menu(sub):
try:
if P.ModelSetting is not None and (P.package_name == 'system' and sub != 'home'):
P.ModelSetting.set('recent_menu_plugin', '{}'.format(sub))
current_menu = sub
for except_menu in P.recent_menu_plugin_except_list:
if current_menu.startswith(except_menu) or current_menu == except_menu:
break
else:
P.ModelSetting.set('recent_menu_plugin', current_menu)
for module in P.module_list:
if sub == module.name:
first_menu = module.get_first_menu()
@@ -45,8 +50,8 @@ def default_route(P):
return redirect(f"/{P.package_name}/manual/{P.get_first_manual_path()}")
return render_template('sample.html', title='%s - %s' % (P.package_name, sub))
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/manual/<path:path>', methods=['GET', 'POST'])
@@ -57,16 +62,43 @@ def default_route(P):
filepath = os.path.join(plugin_root, *path.split('/'))
from support import SupportFile
data = SupportFile.read_file(filepath)
if filepath.endswith('.mdf'):
try:
from support import SupportSC
data = SupportSC.decode(data)
except:
pass
return render_template('manual.html', data=data)
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/<module_name>/manual/<path:path>', methods=['GET', 'POST'])
@login_required
def module_manual(module_name, path):
try:
plugin_root = os.path.dirname(P.blueprint.template_folder)
filepath = os.path.join(plugin_root, *path.split('/'))
from support import SupportFile
data = SupportFile.read_file(filepath)
return render_template('manual.html', data=data)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/<sub>/<sub2>', methods=['GET', 'POST'])
@login_required
def second_menu(sub, sub2):
if sub2 == 'null':
return
if P.ModelSetting is not None:
P.ModelSetting.set('recent_menu_plugin', '{}|{}'.format(sub, sub2))
current_menu = f"{sub}|{sub2}"
for except_menu in P.recent_menu_plugin_except_list:
if current_menu.startswith(except_menu) or current_menu == except_menu:
break
else:
P.ModelSetting.set('recent_menu_plugin', current_menu)
try:
for module in P.module_list:
if sub == module.name:
@@ -74,8 +106,8 @@ def default_route(P):
if sub == 'log':
return render_template('log.html', package=P.package_name)
return render_template('sample.html', title='%s - %s' % (P.package_name, sub))
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
#########################################################
@@ -84,7 +116,7 @@ def default_route(P):
@P.blueprint.route('/ajax/<sub>', methods=['GET', 'POST'])
@login_required
def ajax(sub):
P.logger.debug('AJAX %s %s', P.package_name, sub)
#P.logger.debug('AJAX %s %s', P.package_name, sub)
try:
# global
if sub == 'setting_save':
@@ -93,8 +125,8 @@ def default_route(P):
module.setting_save_after(change_list)
return jsonify(ret)
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/ajax/<module_name>/<cmd>', methods=['GET', 'POST'])
@@ -109,8 +141,9 @@ def default_route(P):
else:
P.logic.scheduler_stop(module_name)
return jsonify(go)
elif cmd == 'reset_db':
ret = P.logic.reset_db(module_name)
elif cmd == 'db_delete':
day = request.form['day']
ret = P.logic.db_delete(module_name, None, day)
return jsonify(ret)
elif cmd == 'one_execute':
ret = P.logic.one_execute(module_name)
@@ -118,18 +151,25 @@ def default_route(P):
elif cmd == 'immediately_execute':
ret = P.logic.immediately_execute(module_name)
return jsonify(ret)
elif cmd == 'web_list':
model = P.logic.get_module(module_name).web_list_model
if model != None:
return jsonify(model.web_list(request))
if module_name == module.name:
if cmd == 'command':
return module.process_command(request.form['command'], request.form.get('arg1'), request.form.get('arg2'), request.form.get('arg3'), request)
elif cmd == 'web_list':
model = P.logic.get_module(module_name).web_list_model
if model != None:
return jsonify(model.web_list(request))
elif cmd == 'db_delete_item':
db_id = request.form['db_id']
ret = False
if module.web_list_model != None:
ret = module.web_list_model.delete_by_id(db_id)
return jsonify(ret)
else:
return module.process_ajax(cmd, request)
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/ajax/<module_name>/<page_name>/<command>', methods=['GET', 'POST'])
@@ -148,10 +188,10 @@ def default_route(P):
else:
P.logic.scheduler_stop_sub(module_name, page_name)
return jsonify(go)
#elif command == 'reset_db':
# sub = request.form['sub']
# ret = P.logic.reset_db(sub)
# return jsonify(ret)
elif command == 'db_delete':
day = request.form['day']
ret = P.logic.db_delete(module_name, page_name, day)
return jsonify(ret)
elif command == 'one_execute':
ret = P.logic.one_execute_sub(module_name, page_name)
return jsonify(ret)
@@ -160,25 +200,30 @@ def default_route(P):
return jsonify(ret)
elif command == 'command':
return ins_page.process_command(request.form['command'], request.form.get('arg1'), request.form.get('arg2'), request.form.get('arg3'), request)
elif command == 'db_delete_item':
db_id = request.form['db_id']
ret = False
if ins_page.web_list_model != None:
ret = ins_page.web_list_model.delete_by_id(db_id)
return jsonify(ret)
else:
return ins_page.process_ajax(command, request)
P.logger.error(f"not process ajax : {P.package_name} {module_name} {page_name} {command}")
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
#########################################################
# API - 외부
#########################################################
# 단일 모듈인 경우 모듈이름을 붙이기 불편하여 추가.
@P.blueprint.route('/api/<sub2>', methods=['GET', 'POST'])
@P.blueprint.route('/api/<sub>', methods=['GET', 'POST'])
@F.check_api
def api_first(sub2):
def api_first(sub):
try:
for module in P.module_list:
return module.process_api(sub2, request)
except Exception as exception:
P.logger.error('Exception:%s', exception)
return P.module_list[0].process_api(sub, request)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/api/<sub>/<sub2>', methods=['GET', 'POST'])
@@ -188,8 +233,16 @@ def default_route(P):
for module in P.module_list:
if sub == module.name:
return module.process_api(sub2, request)
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/normal/<sub>', methods=['GET', 'POST'])
def normal_first(sub):
try:
return P.module_list[0].process_normal(sub, request)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/normal/<sub>/<sub2>', methods=['GET', 'POST'])
@@ -198,13 +251,13 @@ def default_route(P):
for module in P.module_list:
if sub == module.name:
return module.process_normal(sub2, request)
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
# default_route 끝
@@ -266,18 +319,14 @@ def default_route_single_module(P):
else:
P.logic.scheduler_stop(sub)
return jsonify(go)
elif sub == 'reset_db':
sub = request.form['sub']
ret = P.logic.reset_db(sub)
return jsonify(ret)
elif sub == 'one_execute':
sub = request.form['sub']
ret = P.logic.one_execute(sub)
return jsonify(ret)
else:
return P.module_list[0].process_ajax(sub, request)
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/api/<sub>', methods=['GET', 'POST'])
@@ -285,16 +334,16 @@ def default_route_single_module(P):
def api(sub):
try:
return P.module_list[0].process_api(sub, request)
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@P.blueprint.route('/normal/<sub>', methods=['GET', 'POST'])
def normal(sub):
try:
return P.module_list[0].process_normal(sub, request)
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@@ -330,14 +379,15 @@ def default_route_socketio_module(module, attach=''):
module.socketio_list = []
@F.socketio.on('connect', namespace=f'/{P.package_name}/{module.name}{attach}')
@F.login_required
def connect():
try:
P.logger.debug(f'socket_connect : {P.package_name} - {module.name}{attach}')
module.socketio_list.append(request.sid)
socketio_callback('start', '')
module.socketio_connect()
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@@ -347,8 +397,8 @@ def default_route_socketio_module(module, attach=''):
P.logger.debug(f'socket_disconnect : {P.package_name} - {module.name}{attach}')
module.socketio_list.remove(request.sid)
module.socketio_disconnect()
except Exception as exception:
P.logger.error('Exception:%s', exception)
except Exception as e:
P.logger.error(f"Exception:{str(e)}")
P.logger.error(traceback.format_exc())
@@ -357,7 +407,7 @@ def default_route_socketio_module(module, attach=''):
if encoding:
data = json.dumps(data, cls=AlchemyEncoder)
data = json.loads(data)
F.socketio.emit(cmd, data, namespace=f'/{P.package_name}/{module.name}{attach}', broadcast=True)
F.socketio.emit(cmd, data, namespace=f'/{P.package_name}/{module.name}{attach}')
module.socketio_callback = socketio_callback
@@ -392,9 +442,10 @@ def default_route_socketio_page(page):
page.socketio_list = []
@F.socketio.on('connect', namespace=f'/{P.package_name}/{module.name}/{page.name}')
@F.login_required
def page_socketio_connect():
try:
P.logger.debug(f'socket_connect : {P.package_name}/{module.name}/{page.name}')
#P.logger.debug(f'socket_connect : {P.package_name}/{module.name}/{page.name}')
page.socketio_list.append(request.sid)
page_socketio_socketio_callback('start', '')
except Exception as e:
@@ -405,7 +456,7 @@ def default_route_socketio_page(page):
@F.socketio.on('disconnect', namespace=f'/{P.package_name}/{module.name}/{page.name}')
def page_socketio_disconnect():
try:
P.logger.debug(f'socket_disconnect : {P.package_name}/{module.name}/{page.name}')
#P.logger.debug(f'socket_disconnect : {P.package_name}/{module.name}/{page.name}')
page.socketio_list.remove(request.sid)
except Exception as e:
P.logger.error(f'Exception:{str(e)}')
@@ -417,6 +468,6 @@ def default_route_socketio_page(page):
if encoding:
data = json.dumps(data, cls=AlchemyEncoder)
data = json.loads(data)
F.socketio.emit(cmd, data, namespace=f'/{P.package_name}/{module.name}/{page.name}', broadcast=True)
F.socketio.emit(cmd, data, namespace=f'/{P.package_name}/{module.name}/{page.name}')
page.socketio_callback = page_socketio_socketio_callback

BIN
lib/support/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -10,15 +10,19 @@ def d(data):
from .logger import get_logger
logger = get_logger()
#logger = get_logger()
import logging
logger = logging.getLogger('support')
from .base.aes import SupportAES
from .base.discord import SupportDiscord
from .base.file import SupportFile
from .base.os_command import SupportOSCommand
from .base.string import SupportString
from .base.sub_process import SupportSubprocess
from .base.support_sc import SupportSC
from .base.telegram import SupportTelegram
from .base.slack import SupportSlack
from .base.util import (AlchemyEncoder, SingletonClass, SupportUtil,
default_headers, pt)
from .base.yaml import SupportYaml

View File

@@ -18,8 +18,8 @@ class SupportAES(object):
def encrypt(cls, raw, mykey=None):
try:
Random.atfork()
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
raw = pad(raw)
@@ -31,8 +31,8 @@ class SupportAES(object):
cipher = AES.new(key if mykey is None else mykey, AES.MODE_CBC, iv )
try:
tmp = cipher.encrypt( raw )
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
tmp = cipher.encrypt( raw.encode() )
ret = base64.b64encode( iv + tmp )
@@ -64,8 +64,8 @@ class SupportAES(object):
def encrypt_(cls, raw, mykey=None, iv=None):
try:
Random.atfork()
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
raw = pad(raw)
if type(raw) == type(''):
@@ -79,8 +79,8 @@ class SupportAES(object):
cipher = AES.new(key if mykey is None else mykey, AES.MODE_CBC, iv )
try:
tmp = cipher.encrypt( raw )
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
tmp = cipher.encrypt( raw.encode() )
ret = base64.b64encode( tmp )

View File

@@ -1,4 +1,3 @@
import io
import os
import random
import time
@@ -11,31 +10,36 @@ try:
except:
os.system('pip3 install discord-webhook')
# 2023-10-13 by flaskfarm
# 웹훅 URL이 git에 노출되면 중단.
# base64로 인코딩.
import base64
from discord_webhook import DiscordEmbed, DiscordWebhook
from . import logger
webhook_list = [
#'https://discord.com/api/webhooks/933908493612744705/DGPWBQN8LiMnt2cnCSNVy6rCc5Gi_vj98QpJ3ZEeihohzsfOsCWvcixJU1A2fQuepGFq', # 1
#'https://discord.com/api/webhooks/932754078839234731/R2iFzQ7P8IKV-MGWp820ToWX07s5q8X-st-QsUJs7j3JInUj6ZlI4uDYKeR_cwIi98mf', # 2
#'https://discord.com/api/webhooks/932754171835351131/50RLrYa_B69ybk4BWoLruNqU7YlZ3pl3gpPr9bwuankWyTIGtRGbgf0CJ9ExJWJmvXwo', # 3
'https://discord.com/api/webhooks/794661043863027752/A9O-vZSHIgfQ3KX7wO5_e2xisqpLw5TJxg2Qs1stBHxyd5PK-Zx0IJbAQXmyDN1ixZ-n', # 4
'https://discord.com/api/webhooks/810373348776476683/h_uJLBBlHzD0w_CG0nUajFO-XEh3fvy-vQofQt1_8TMD7zHiR7a28t3jF-xBCP6EVlow', # 5
'https://discord.com/api/webhooks/810373405508501534/wovhf-1pqcxW5h9xy7iwkYaf8KMDjHU49cMWuLKtBWjAnj-tzS1_j8RJ7tsMyViDbZCE', # 6
'https://discord.com/api/webhooks/796558388326039552/k2VV356S1gKQa9ht-JuAs5Dqw5eVkxgZsLUzFoxmFG5lW6jqKl7zCBbbKVhs3pcLOetm', # 7
'https://discord.com/api/webhooks/810373566452858920/Qf2V8BoLOy2kQzlZGHy5HZ1nTj7lK72ol_UFrR3_eHKEOK5fyR_fQ8Yw8YzVh9EQG54o', # 8
'https://discord.com/api/webhooks/810373654411739157/SGgdO49OCkTNIlc_BSMSy7IXQwwXVonG3DsVfvBVE6luTCwvgCqEBpEk30WBeMMieCyI', # 9
'https://discord.com/api/webhooks/810373722341900288/FwcRJ4YxYjpyHpnRwF5f2an0ltEm8JPqcWeZqQi3Qz4QnhEY-kR2sjF9fo_n6stMGnf_', # 10
'https://discord.com/api/webhooks/931779811691626536/vvwCm1YQvE5tW4QJ4SNKRmXhQQrmOQxbjsgRjbTMMXOSiclB66qipiZaax5giAqqu2IB', # 11
'https://discord.com/api/webhooks/931779905631420416/VKlDwfxWQPJfIaj94-ww_hM1MNEayRKoMq0adMffCC4WQS60yoAub_nqPbpnfFRR3VU5', # 12
'https://discord.com/api/webhooks/931779947914231840/22amQuHSOI7wPijSt3U01mXwd5hTo_WHfVkeaowDQMawCo5tXVfeEMd6wAWf1n7CseiG', # 13
'https://discord.com/api/webhooks/810374294416654346/T3-TEdKIg7rwMZeDzNr46KPDvO7ZF8pRdJ3lfl39lJw2XEZamAG8uACIXagbNMX_B0YN', # 14
'https://discord.com/api/webhooks/810374337403289641/_esFkQXwlPlhxJWtlqDAdLg2Nujo-LjGPEG3mUmjiRZto69NQpkBJ0F2xtSNrCH4VAgb', # 15
'https://discord.com/api/webhooks/810374384736534568/mH5-OkBVpi7XqJioaQ8Ma-NiL-bOx7B5nYJpL1gZ03JaJaUaIW4bCHeCt5O_VGLJwAtj', # 16
'https://discord.com/api/webhooks/810374428604104724/Z1Tdxz3mb0ytWq5LHWi4rG5CeJnr9KWXy5aO_waeD0NcImQnhRXe7h7ra7UrIDRQ2jOg', # 17
'https://discord.com/api/webhooks/810374475773509643/QCPPN4djNzhuOmbS3DlrGBunK0SVR5Py9vMyCiPL-0T2VPgitFZS4YM6GCLfM2fkrn4-', # 18
'https://discord.com/api/webhooks/810374527652855819/5ypaKI_r-hYzwmdDlVmgAU6xNgU833L9tFlPnf3nw4ZDaPMSppjt77aYOiFks4KLGQk8', # 19
'https://discord.com/api/webhooks/810374587917402162/lHrG7CEysGUM_41DMnrxL2Q8eh1-xPjJXstYE68WWfLQbuUAV3rOfsNB9adncJzinYKi', # 20
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MTkwMTk2NzE1NTMwNS9uY01aaWZVVDY3ZTRISXdPeG8xM0dLdFBTNFBnVjZZSDBZaU1SQ2FMQkNfMU0yMHo3WmNFRjExM2xnY0NpRTFFdnhEZQ==').decode('utf-8'), # 1
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MjY1NjQwOTA3MTY0Ni9zUjlHZFJMbERrQV9Cc243UkdvQXQ3TmVSMU9SVFRxczVqUEc5UU9PYTJCbjllTTI1YnctV0FXZ2pYT1pYa183U0V4Wg==').decode('utf-8'), # 2
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MjkyNDExOTA0NDE4OC9wX3ZMN211eElKUmFWOXRDVG56S3c4LVJjY0R5V1JaSjdER2dYc1YwaXlLVGFjZEM4MVBiYmctWHFzY0NDTk5jdXpWeQ==').decode('utf-8'), # 3
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MzE2MjE1ODI0MzkwMS9KdDMwZjlTTTR6dWNfVmYwSVlmbzdZNTluOFI5T2RQazNXdTFtNG93MHZxZFJERXlneVZvb25Rdm1QbVRya1lOVTRyag==').decode('utf-8'), # 4
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MzMyNzY1MzE1ODk4Mi82Nk0zZVFyRGpSZG1UTzExaXZlMGhlTFFpNGwtckZUN1lRYTJ3elpmMjNBOGZPYm1CYjJSRmhxR2dNSHNlNUdHSFNLTA==').decode('utf-8'), # 5
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MzU1ODAzNzg5MzIxNC84aWFNLTJIdXJFOW1XM0RqY293dm9tUVhUeUxLOElrbWR5SnhsY1BFRzJ4MjBqOTNIN0FWNnY0dVJIak5XeGprcjg4Tw==').decode('utf-8'), # 6
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1MzczMTQzNDYxMDc0OS9xRktGX0hSWDRYVHFYMVFPRzM5YWNJVkp6dmdRZXBzZjM2TEFEUlpNOWtiZ0pNUHVfd091OXZ4bXdZVVBRMUpkUjhhRg==').decode('utf-8'), # 7
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1Mzg1NTI0NjI3NDYwMS9vWGVxYVdhWENNZktkM19iZktEVjB0Ti1XQzUyLUxpVjU0VjQxWE1jNWd3XzJmQnpnekp4MzJNYS1wOWlvQkFpd1I3Mw==').decode('utf-8'), # 8
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1Mzk5MDgyNzE1MTQ2MS85a0xXTXZCY1FaNzZRcnRpZmVJck9DOXo5SXl1WGl4YnRmbldocHVjSlFRVUJqcGxSd0tIdzdDc0h3THJhQkRQM1h5ag==').decode('utf-8'), # 9
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDExMjQ1NzYzNzg5OC9ZVC1qblZTeWFxcjAxMjFtWUtVZFU1SjJaVFZHS0NOM2djUDI2RXEwWm5hR3RWeFllM3NZa0kyUG81RWhPd211WDd6aw==').decode('utf-8'), # 10
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDI1Mzg1MTk1NTMxMS9RVUt1cU5uWFFiaWkwU01FMWxkU0lEakxhZXh5RDRUZEZuLWdXejFuSXRlYy1mSFVCU3dxUDd3WHNBbDB1dXd2VVJTRw==').decode('utf-8'), # 11
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDM3NDMyNDgxMzkyNS9VR1Jsc3liY2dPQ3hoMVQ1Z0J0QVc2RGQyZ0dPaGVOXzcydy15QTBvZzU5aU1BcnB3WWxVRzhka0ZXTUxSVUZpaHFScw==').decode('utf-8'), # 12
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDUxNjE5NzI3Nzc2Ny9iOEFIN1FtY2JPSl9XcUVHZmtMOVNPbXBJMWluVThvcDF4amQwWGFjRXFFZW82ZURzbS0yYkpZYllmQ1RYclMxbHhUdQ==').decode('utf-8'), # 13
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDY0MDIzMTIzOTcwMS90bkFSTzFvYWo1SWRmb0U4UEVJejRZUVMxNFhKXzdpc0I5Q1otdzVyaXdDN0U0cVVzQ1B6V2pLRnM3WE9OazBvVEo5Qg==').decode('utf-8'), # 14
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDc1NTcxNzIwMTk4MS9WLWQwc0hvNl9QakJTdFpLVmtuSTdDS0RuQks1QzRhS2dPZUZ4azEwam41VE5oZk1PdFNOSFNHN3BpaGNWLVh6Y0kxZg==').decode('utf-8'), # 15
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NDg4NDc4NDMyNDYxOS9XVEpHWWVjcjVKOHhtN0hTaUpCbmdnU01Uc3JkMUxiaDVwQzB2Vm5tYVptZWlvd2RRZWZQRHRuZHowRmViWE9xYkNoeA==').decode('utf-8'), # 16
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NTAxMTIxMzA5OTEyOS9neHVVenpsMTBpMUV4NWZtdU5jZGlOQ2FocHBEM3liQlpxaTR3Y3phdlpGeG1OUGx2VFRadU9CalZCMTBOZzJ2QWpLcA==').decode('utf-8'), # 17
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NTEzMjg4OTczMTE1My9YcTU4cXdCTGlOOEF4S1djQTl0MFJERkhIT0NDNjg4MlQ1aXBKbkJxY3VSOFVxMGowSzF4Rko3dUZWaGhRR0RFTjc3bw==').decode('utf-8'), # 18
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NTI5NzYzNzc5MzgxMy9pV3hoZkxRN190dHhkNENIVnNPWjA2ZHFOUjlkVTZUdlNfdHA2OHVnNlI2WmRIa2dESzJKb28xUVNSa3NrRDhLUXRyTg==').decode('utf-8'), # 19
base64.b64decode(b'aHR0cHM6Ly9kaXNjb3JkLmNvbS9hcGkvd2ViaG9va3MvMTM5NDk1NTQ0NDk0MjAxMjQ4OC9zandtaFNDYjI0ZElYbjBVMWhwMmdJRzZDV2REcC1Kb3M0OW1Oc05jQllGenNDNm1KYVZJOVpoQm11dGt4cXd1bDc1ZA==').decode('utf-8'), # 20
]
@@ -44,20 +48,60 @@ class SupportDiscord(object):
@classmethod
def send_discord_message(cls, text, image_url=None, webhook_url=None):
try:
"""
webhook = DiscordWebhook(url=webhook_url, content=text)
if image_url is not None:
embed = DiscordEmbed()
embed.set_timestamp()
embed.set_image(url=image_url)
webhook.add_embed(embed)
response = webhook.execute()
return True
except Exception as exception:
logger.error('Exception:%s', exception)
"""
try:
if image_url is not None:
webhook = DiscordWebhook(url=webhook_url)
embed = DiscordEmbed()
embed.set_timestamp()
embed.set_image(url=image_url)
tmp = text.split('\n', 1)
embed.set_title(tmp[0])
embed.set_description(tmp[1])
webhook.add_embed(embed)
else:
if 'http://' in text or 'https://' in text:
webhook = DiscordWebhook(url=webhook_url, content= text)
else:
webhook = DiscordWebhook(url=webhook_url, content='```' + text + '```')
webhook.execute()
return True
except:
webhook = DiscordWebhook(url=webhook_url, content=text)
if image_url is not None:
embed = DiscordEmbed()
embed.set_timestamp()
embed.set_image(url=image_url)
webhook.add_embed(embed)
webhook.execute()
return True
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False
@classmethod
def send_discord_bot_message(cls, text, webhook_url, encryped=True):
try:
from support import SupportAES
if encryped:
text = '^' + SupportAES.encrypt(text)
return cls.send_discord_message(text, webhook_url=webhook_url)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False
@classmethod
def discord_proxy_image(cls, image_url, webhook_url=None, retry=True):
@@ -95,21 +139,21 @@ class SupportDiscord(object):
return image_url
else:
raise Exception(str(data))
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
if retry:
time.sleep(1)
return cls.discord_proxy_image(image_url, webhook_url=None, retry=False)
return cls.discord_proxy_image(image_url, webhook_url=webhook_url, retry=False)
else:
return image_url
@classmethod
def discord_proxy_image_localfile(cls, filepath, retry=True):
def discord_proxy_image_localfile(cls, filepath, webhook_url=None, retry=True):
data = None
webhook_url = webhook_list[random.randint(0,len(webhook_list)-1)]
if webhook_url is None or webhook_url == '':
webhook_url = webhook_list[random.randint(0,len(webhook_list)-1)]
try:
webhook = DiscordWebhook(url=webhook_url, content='')
import io
@@ -133,8 +177,8 @@ class SupportDiscord(object):
if retry:
time.sleep(1)
return cls.discord_proxy_image_localfile(filepath, retry=False)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
if retry:
@@ -143,15 +187,15 @@ class SupportDiscord(object):
@classmethod
def discord_proxy_image_bytes(cls, bytes, retry=True):
def discord_proxy_image_bytes(cls, bytes, retry=True, format='jpg', webhook_url=None):
data = None
idx = random.randint(0,len(webhook_list)-1)
webhook_url = webhook_list[idx]
if webhook_url is None or webhook_url == '':
webhook_url = webhook_list[random.randint(0,len(webhook_list)-1)]
try:
webhook = DiscordWebhook(url=webhook_url, content='')
webhook.add_file(file=bytes, filename='image.jpg')
webhook.add_file(file=bytes, filename=f'image.{format}')
embed = DiscordEmbed()
embed.set_image(url="attachment://image.jpg")
embed.set_image(url=f"attachment://image.{format}")
response = webhook.execute()
data = None
if type(response) == type([]):
@@ -168,8 +212,8 @@ class SupportDiscord(object):
if retry:
time.sleep(1)
return cls.discord_proxy_image_bytes(bytes, retry=False)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
if retry:
@@ -181,7 +225,7 @@ class SupportDiscord(object):
# RSS에서 자막 올린거
@classmethod
def discord_cdn(cls, byteio=None, filepath=None, filename=None, webhook_url=None, content='', retry=True):
def discord_cdn(cls, byteio=None, filepath=None, filename=None, webhook_url="https://discord.com/api/webhooks/1050549730964410470/ttge1ggOfIxrCSeTmYbIIsUWyMGAQj-nN6QBgwZTqLcHtUKcqjZ8wFWSWAhHmZne57t7", content='', retry=True):
data = None
if webhook_url is None:
webhook_url = webhook_list[random.randint(0,9)]
@@ -210,8 +254,8 @@ class SupportDiscord(object):
if retry:
time.sleep(1)
return cls.discord_proxy_image_localfile(filepath, retry=False)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
if retry:
time.sleep(1)

View File

@@ -3,6 +3,7 @@ import json
import os
import re
import traceback
import zipfile
from . import logger
@@ -16,8 +17,8 @@ class SupportFile(object):
data = ifp.read()
ifp.close()
return data
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@classmethod
@@ -27,8 +28,8 @@ class SupportFile(object):
ofp = codecs.open(filename, mode, encoding='utf8')
ofp.write(data)
ofp.close()
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@classmethod
@@ -37,8 +38,8 @@ class SupportFile(object):
with open(filepath, "r", encoding='utf8') as json_file:
data = json.load(json_file)
return data
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@classmethod
@@ -48,8 +49,8 @@ class SupportFile(object):
os.makedirs(os.path.dirname(filepath), exist_ok=True)
with open(filepath, "w", encoding='utf8') as json_file:
json.dump(data, json_file, indent=4, ensure_ascii=False)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@@ -71,8 +72,8 @@ class SupportFile(object):
response = requests.get(url, headers=headers) # get request
file_is.write(response.content) # write to file
return True
except Exception as exception:
logger.debug('Exception:%s', exception)
except Exception as e:
logger.debug(f"Exception:{str(e)}")
logger.debug(traceback.format_exc())
return False
@@ -82,118 +83,18 @@ class SupportFile(object):
#text = text.replace('/', '')
# 2021-07-31 X:X
#text = text.replace(':', ' ')
text = re.sub('[\\/:*?\"<>|]', ' ', text).strip()
text = re.sub('[\\/:*?\"<>|]', ' ', text).strip()
text = re.sub("\s{2,}", ' ', text)
return text
@classmethod
def size(cls, start_path = '.'):
total_size = 0
for dirpath, dirnames, filenames in os.walk(start_path):
for f in filenames:
fp = os.path.join(dirpath, f)
if not os.path.islink(fp):
total_size += os.path.getsize(fp)
return total_size
def unzip(cls, zip_filepath, extract_folderpath):
with zipfile.ZipFile(zip_filepath, 'r') as zip_ref:
zip_ref.extractall(extract_folderpath)
# 파일처리에서 사용. 중복이면 시간값
@classmethod
def file_move(cls, source_path, target_dir, target_filename):
try:
@@ -208,9 +109,160 @@ class SupportFile(object):
new_target_filename = f"{tmp[0]} {str(time.time()).split('.')[0]}{tmp[1]}"
target_path = os.path.join(target_dir, new_target_filename)
shutil.move(source_path, target_path)
except Exception as exception:
logger.debug('Exception:%s', exception)
except Exception as e:
logger.debug(f"Exception:{str(e)}")
logger.debug(traceback.format_exc())
@classmethod
def size(cls, start_path = '.'):
if os.path.exists(start_path):
if os.path.isdir(start_path):
total_size = 0
for dirpath, dirnames, filenames in os.walk(start_path):
for f in filenames:
fp = os.path.join(dirpath, f)
if not os.path.islink(fp):
total_size += os.path.getsize(fp)
return total_size
else:
return os.path.getsize(start_path)
return 0
@classmethod
def size_info(cls, start_path = '.'):
ret = {
'size':0,
'file_count':0,
'folder_count':0.
}
for dirpath, dirnames, filenames in os.walk(start_path):
for f in filenames:
fp = os.path.join(dirpath, f)
if not os.path.islink(fp):
ret['size'] += os.path.getsize(fp)
ret['folder_count'] += len(dirnames)
ret['file_count'] += len(filenames)
return ret
@classmethod
def rmtree(cls, folderpath):
import shutil
try:
for root, dirs, files in os.walk(folderpath):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
shutil.rmtree(os.path.join(root, name))
shutil.rmtree(folderpath)
except Exception as e:
logger.debug(f"Exception:{str(e)}")
logger.debug(traceback.format_exc())
return False
@classmethod
def file_move(cls, source_path, target_dir, target_filename):
try:
import shutil
import time
os.makedirs(target_dir, exist_ok=True)
target_path = os.path.join(target_dir, target_filename)
if source_path != target_path:
if os.path.exists(target_path):
tmp = os.path.splitext(target_filename)
new_target_filename = f"{tmp[0]} {str(time.time()).split('.')[0]}{tmp[1]}"
target_path = os.path.join(target_dir, new_target_filename)
shutil.move(source_path, target_path)
except Exception as e:
logger.debug(f"Exception:{str(e)}")
logger.debug(traceback.format_exc())
"""
@classmethod
@@ -234,13 +286,13 @@ class SupportFile(object):
import shutil
shutil.rmtree(zip_path)
return zipfilepath
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return
"""
"""
@classmethod
def rmtree(cls, folderpath):
import shutil
@@ -253,19 +305,9 @@ class SupportFile(object):
return True
except:
return False
"""
@classmethod
def rmtree2(cls, folderpath):
import shutil
try:
for root, dirs, files in os.walk(folderpath):
for name in files:
os.remove(os.path.join(root, name))
for name in dirs:
shutil.rmtree(os.path.join(root, name))
except:
return False
@@ -279,8 +321,8 @@ class SupportFile(object):
try:
with open(filename, 'wb') as f:
f.write(data)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@@ -361,8 +403,8 @@ class SupportFile(object):
if isinstance(data, bytes):
data = data.decode('utf-8')
return data
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
"""

View File

@@ -1,7 +1,13 @@
import os, sys, traceback, requests
import os
import sys
import traceback
from io import BytesIO
import requests
from . import logger
class SupportImage(object):
@classmethod
@@ -20,6 +26,6 @@ class SupportImage(object):
from . import SupportDiscord
return SupportDiscord.discord_proxy_image_bytes(img_byte_arr)
except Exception as e:
logger.error('Exception:%s', e)
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())

View File

@@ -0,0 +1,44 @@
import os
import platform
from . import logger
class SupportOSCommand(object):
@classmethod
def get_size(cls, path):
from support import SupportFile, SupportSubprocess, SupportUtil
if platform.system() == 'Windows':
#https://docs.microsoft.com/en-us/sysinternals/downloads/du
"""
bin = r'C:\SJVA3\data\bin\du64.exe'
command = [bin, '-c', '-nobanner', f'"{path}"']
data = ToolSubprocess.execute_command_return(command, force_log=True)
logger.warning(data)
ret = {}
tmp = data.split('\t')
ret['target'] = tmp[1].strip()
ret['size'] = int(tmp[0].strip())
ret['sizeh'] = ToolUtil.sizeof_fmt(ret['size'])
"""
ret = {}
ret['target'] = path
if os.path.exists(path):
if os.path.isdir(path):
ret['size'] = SupportFile.size(start_path=path)
else:
ret['size'] = os.stat(path).st_size
ret['sizeh'] = SupportUtil.sizeof_fmt(ret['size'])
return ret
else:
command = ['du', '-bs', path]
data = SupportSubprocess.execute_command_return(command)
ret = {}
tmp = data['log'].split('\t')
ret['target'] = tmp[1].strip()
ret['size'] = int(tmp[0].strip())
ret['sizeh'] = SupportUtil.sizeof_fmt(ret['size'])
return ret

25
lib/support/base/slack.py Normal file
View File

@@ -0,0 +1,25 @@
import os
import traceback
try:
from slack_sdk.webhook import WebhookClient
except:
os.system('pip3 install slack-sdk')
from slack_sdk.webhook import WebhookClient
from . import logger
class SupportSlack:
@classmethod
def send_slack_message(cls, text, webhook_url=None, image_url=None, disable_notification=None):
try:
if webhook_url is None:
return False
webhook = WebhookClient(webhook_url)
if image_url is not None:
webhook.send(text=text, blocks=[{"type": "image", "title": {"type": "plain_text", "text": "Image", "emoji": True}, "image_url": image_url, "alt_text": "Image"}])
webhook.send(text=text)
return True
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False

View File

@@ -1,3 +1,6 @@
import re
import traceback
from . import logger
@@ -5,22 +8,78 @@ class SupportString(object):
@classmethod
def get_cate_char_by_first(cls, title): # get_first
value = ord(title[0].upper())
if value >= ord('0') and value <= ord('9'): return '0Z'
elif value >= ord('A') and value <= ord('Z'): return '0Z'
elif value >= ord('') and value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value < ord(''): return ''
elif value <= ord(''): return ''
else: return '0Z'
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
if ord('') <= value < ord(''): return ''
return '0Z'
@classmethod
def is_include_hangul(cls, text):
try:
hanCount = len(re.findall(u'[\u3130-\u318F\uAC00-\uD7A3]+', text))
return hanCount > 0
except:
return False
@classmethod
def language_info(cls, text):
try:
text = text.strip().replace(' ', '')
all_count = len(text)
han_count = len(re.findall('[\u3130-\u318F\uAC00-\uD7A3]', text))
eng_count = len(re.findall('[a-zA-Z]', text))
etc_count = len(re.findall('[0-9]', text))
etc_count += len(re.findall('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\|\(\)\[\]\<\>`\'…》:]', text))
if all_count == etc_count:
return (0,0)
han_percent = int(han_count * 100 / (all_count-etc_count))
eng_percent = int(eng_count * 100 / (all_count-etc_count))
return (han_percent, eng_percent)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False
@classmethod
def remove_special_char(cls, text):
return re.sub('[-=+,#/\?:^$.@*\"※~&%ㆍ!』\\|\(\)\[\]\<\>`\'…》:]', '', text)
@classmethod
def remove_emoji(cls, text, char=''):
import re
emoji_pattern = re.compile("["
u"\U0001F600-\U0001F64F" # emoticons
u"\U0001F300-\U0001F5FF" # symbols & pictographs
u"\U0001F680-\U0001F6FF" # transport & map symbols
u"\U0001F1E0-\U0001F1FF" # flags (iOS)
u"\U00002500-\U00002BEF" # chinese char
u"\U00002702-\U000027B0"
u"\U00002702-\U000027B0"
#u"\U000024C2-\U0001F251"
u"\U0001f926-\U0001f937"
u"\U00010000-\U0010ffff"
u"\u2640-\u2642"
u"\u2600-\u2B55"
u"\u200d"
u"\u23cf"
u"\u23e9"
u"\u231a"
u"\ufe0f" # dingbats
u"\u3030"
"]+", flags=re.UNICODE)
# Remove emojis from the text
text = emoji_pattern.sub(char, text)
return text

View File

@@ -1,5 +1,6 @@
import io
import json
import locale
import os
import platform
import queue
@@ -20,7 +21,7 @@ def demote(user_uid, user_gid):
class SupportSubprocess(object):
@classmethod
def command_for_windows(cls, command: list) -> str or list:
def command_for_windows(cls, command: list):
if platform.system() == 'Windows':
tmp = []
if type(command) == type([]):
@@ -43,17 +44,32 @@ class SupportSubprocess(object):
iter_arg = ''
if platform.system() == 'Windows':
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=shell, env=env, encoding='utf8')
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=shell, env=env, encoding='utf8', bufsize=0)
else:
if uid == None:
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=shell, env=env, encoding='utf8')
else:
process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=shell, env=env, preexec_fn=demote(uid, gid), encoding='utf8')
new_ret = {'status':'finish', 'log':None}
def func(ret):
with process.stdout:
try:
for line in iter(process.stdout.readline, iter_arg):
ret.append(line.strip())
if log:
logger.debug(ret[-1])
except:
pass
result = []
thread = threading.Thread(target=func, args=(result,))
thread.setDaemon(True)
thread.start()
#thread.join()
try:
#process.communicate()
process_ret = process.wait(timeout=timeout) # wait for the subprocess to exit
except:
import psutil
@@ -62,14 +78,17 @@ class SupportSubprocess(object):
proc.kill()
process.kill()
new_ret['status'] = "timeout"
ret = []
with process.stdout:
for line in iter(process.stdout.readline, iter_arg):
ret.append(line.strip())
if log:
logger.debug(ret[-1])
#logger.error(process_ret)
thread.join()
#ret = []
#with process.stdout:
# for line in iter(process.stdout.readline, iter_arg):
# ret.append(line.strip())
# if log:
# logger.debug(ret[-1])
ret = result
#logger.error(ret)
if format is None:
ret2 = '\n'.join(ret)
elif format == 'json':
@@ -82,20 +101,29 @@ class SupportSubprocess(object):
break
ret2 = json.loads(''.join(ret[index:]))
except:
ret2 = None
ret2 = ret
new_ret['log'] = ret2
return new_ret
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
logger.error('command : %s', command)
finally:
try:
if process.stdout:
process.stdout.close()
if process.stdin:
process.stdin.close()
except Exception as e:
pass
__instance_list = []
def __init__(self, command, print_log=False, shell=False, env=None, timeout=None, uid=None, gid=None, stdout_callback=None, call_id=None):
def __init__(self, command, print_log=False, shell=False, env=None, timeout=None, uid=None, gid=None, stdout_callback=None, call_id=None, callback_line=True):
self.command = command
self.print_log = print_log
self.shell = shell
@@ -108,6 +136,7 @@ class SupportSubprocess(object):
self.stdout_queue = None
self.call_id = call_id
self.timestamp = time.time()
self.callback_line = callback_line
def start(self, join=True):
@@ -127,13 +156,15 @@ class SupportSubprocess(object):
self.command = self.command_for_windows(self.command)
logger.debug(f"{self.command=}")
if platform.system() == 'Windows':
self.process = subprocess.Popen(self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=self.shell, env=self.env, encoding='utf8', bufsize=0)
self.process = subprocess.Popen(self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=self.shell, env=self.env, encoding='utf8', bufsize=0)
else:
if self.uid == None:
self.process = subprocess.Popen(self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=self.shell, env=self.env, encoding='utf8', bufsize=0)
self.process = subprocess.Popen(self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=self.shell, env=self.env, encoding='utf8', bufsize=0)
else:
self.process = subprocess.Popen(self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True, shell=self.shell, env=self.env, preexec_fn=demote(self.uid, self.gid), encoding='utf8', bufsize=0)
self.process = subprocess.Popen(self.command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, shell=self.shell, env=self.env, preexec_fn=demote(self.uid, self.gid), encoding='utf8', bufsize=0)
SupportSubprocess.__instance_list.append(self)
self.send_stdout_callback(self.call_id, 'START', None)
self.__start_communicate()
self.__start_send_callback()
if self.process is not None:
@@ -142,17 +173,18 @@ class SupportSubprocess(object):
self.process_close()
else:
self.process.wait()
self.remove_instance(self)
logger.info(f"{self.command} END")
except Exception as e:
logger.error(f'Exception:{str(e)}')
logger.error(traceback.format_exc())
logger.warning(self.command)
if self.stdout_callback != None:
self.stdout_callback('error', str(e))
self.stdout_callback('error', str(traceback.format_exc()))
self.send_stdout_callback(self.call_id, 'ERROR', str(e))
self.send_stdout_callback(self.call_id, 'ERROR', str(traceback.format_exc()))
finally:
if self.stdout_callback != None:
self.stdout_callback('thread_end', None)
#self.stdout_callback(self.call_id, 'thread_end', None)
pass
def __start_communicate(self):
@@ -164,7 +196,11 @@ class SupportSubprocess(object):
def rdr():
while True:
buf = self.process.stdout.read(1)
try:
buf = self.process.stdout.read(1)
except:
continue
#print(buf)
if buf:
_queue.put( buf )
else:
@@ -192,7 +228,9 @@ class SupportSubprocess(object):
if r is not None:
#print(f"{r=}")
self.stdout_queue.put(r)
self.stdout_queue.put('\n')
self.stdout_queue.put('<END>')
self.stdout_queue.put('\n')
for tgt in [rdr, clct]:
th = threading.Thread(target=tgt)
th.setDaemon(True)
@@ -204,16 +242,36 @@ class SupportSubprocess(object):
def func():
while self.stdout_queue:
line = self.stdout_queue.get()
#logger.error(line)
if line == '<END>':
if self.stdout_callback != None:
self.stdout_callback('end', None)
self.send_stdout_callback(self.call_id, 'END', None)
break
else:
if self.stdout_callback != None:
self.stdout_callback('log', line)
self.send_stdout_callback(self.call_id, 'LOG', line)
self.remove_instance(self)
th = threading.Thread(target=func, args=())
def func_callback_line():
previous = ''
while self.stdout_queue:
receive = previous + self.stdout_queue.get()
lines = receive.split('\n')
previous = lines[-1]
for line in lines[:-1]:
line = line.strip()
# TODO
#logger.error(line)
if line == '<END>':
self.send_stdout_callback(self.call_id, 'END', None)
break
else:
self.send_stdout_callback(self.call_id, 'LOG', line)
self.remove_instance(self)
if self.callback_line:
th = threading.Thread(target=func_callback_line, args=())
else:
th = threading.Thread(target=func, args=())
th.setDaemon(True)
th.start()
@@ -243,6 +301,15 @@ class SupportSubprocess(object):
self.process.stdin.write(f'{cmd}\n')
self.process.stdin.flush()
def send_stdout_callback(self, call_id, mode, data):
try:
if self.stdout_callback != None:
self.stdout_callback(self.call_id, mode, data)
except Exception as e:
logger.error(f'Exception:{str(e)}')
logger.error(f"[{call_id}] [{mode}] [{data}]")
#logger.error(traceback.format_exc())
@classmethod
def all_process_close(cls):
@@ -271,4 +338,7 @@ class SupportSubprocess(object):
for instance in cls.__instance_list:
if instance.call_id == call_id:
return instance
@classmethod
def get_list(cls):
return cls.__instance_list

View File

@@ -1,5 +1,7 @@
import time
import traceback
import requests
from telepot_mod import Bot
from . import logger
@@ -12,11 +14,21 @@ class SupportTelegram:
try:
bot = Bot(bot_token)
if image_url is not None:
bot.sendPhoto(chat_id, image_url, disable_notification=disable_notification)
logger.debug(image_url)
for i in range(5):
if requests.get(image_url).status_code == 200:
break
else:
time.sleep(3)
try:
bot.sendPhoto(chat_id, image_url, disable_notification=disable_notification)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
bot.sendMessage(chat_id, text, disable_web_page_preview=True, disable_notification=disable_notification)
return True
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
logger.debug('Chatid:%s', chat_id)
return False

View File

@@ -1,8 +1,11 @@
import os, traceback, io, re, json, codecs
import json
import time
import traceback
from functools import wraps
from . import logger
from functools import wraps
import time
def pt(f):
@wraps(f)
def wrapper(*args, **kwds):
@@ -26,9 +29,9 @@ class SupportUtil(object):
def sizeof_fmt(cls, num, suffix='Bytes'):
for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
if abs(num) < 1024.0:
return "%3.1f%s%s" % (num, unit, suffix)
return "%3.1f %s%s" % (num, unit, suffix)
num /= 1024.0
return "%.1f%s%s" % (num, 'Y', suffix)
return "%.1f %s%s" % (num, 'Y', suffix)
@classmethod
def is_arm(cls):

View File

@@ -1,5 +1,11 @@
import os
import traceback
import yaml
from . import logger
class SupportYaml(object):
@classmethod
def write_yaml(cls, filepath, data):
@@ -11,3 +17,41 @@ class SupportYaml(object):
with open(filepath, encoding='utf8') as file:
data = yaml.load(file, Loader=yaml.FullLoader)
return data
@classmethod
def copy_section(cls, source_file, target_file, section_name):
from support import SupportFile
try:
if os.path.exists(source_file) == False:
return 'not_exist_source_file'
if os.path.exists(target_file) == False:
return 'not_exist_target_file'
lines = SupportFile.read_file(source_file).split('\n')
section = {}
current_section_name = None
current_section_data = None
for line in lines:
line = line.strip()
if line.startswith('# SECTION START : '):
current_section_name = line.split(':')[1].strip()
current_section_data = []
if current_section_data is not None:
current_section_data.append(line)
if line.startswith('# SECTION END'):
section[current_section_name] = current_section_data
current_section_name = current_section_data = None
if section_name not in section:
return 'not_include_section'
data = '\n'.join(section[section_name])
source_data = SupportFile.read_file(target_file)
source_data = source_data + f"\n{data}\n"
SupportFile.write_file(target_file, source_data)
return 'success'
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return 'exception'

View File

@@ -116,7 +116,11 @@ class SupportFfmpeg(object):
header_count = 0
if self.proxy is None:
if self.headers is None:
command = [self.__ffmpeg_path, '-y', '-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
if platform.system() == 'Windows':
command = [self.__ffmpeg_path, '-y', '-i', f'"{self.url}"', '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
else:
command = [self.__ffmpeg_path, '-y', '-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
else:
headers_command = []
tmp = ""
@@ -136,9 +140,15 @@ class SupportFfmpeg(object):
if len(tmp) > 0:
headers_command.append('-headers')
headers_command.append(f'{tmp}')
command = [self.__ffmpeg_path, '-y'] + headers_command + ['-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
if platform.system() == 'Windows':
command = [self.__ffmpeg_path, '-y'] + headers_command + ['-i', f'"{self.url}"', '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
else:
command = [self.__ffmpeg_path, '-y'] + headers_command + ['-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
else:
command = [self.__ffmpeg_path, '-y', '-http_proxy', self.proxy, '-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
if platform.system() == 'Windows':
command = [self.__ffmpeg_path, '-y', '-http_proxy', self.proxy, '-i', f'"{self.url}"', '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
else:
command = [self.__ffmpeg_path, '-y', '-http_proxy', self.proxy, '-i', self.url, '-c', 'copy', '-bsf:a', 'aac_adtstoasc']
if platform.system() == 'Windows':
@@ -159,7 +169,7 @@ class SupportFfmpeg(object):
return
except:
pass
#logger.error(' '.join(command))
logger.error(' '.join(command))
command = SupportSubprocess.command_for_windows(command)
if platform.system() == 'Windows' and header_count > 1:
@@ -216,8 +226,8 @@ SET CRLF=^
else:
if os.path.exists(self.temp_fullpath):
os.remove(self.temp_fullpath)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
arg = {'type':'last', 'status':self.status, 'data' : self.get_data()}
@@ -347,6 +357,7 @@ SET CRLF=^
return data
def send_to_listener(self, **arg):
print(arg)
if self.total_callback_function != None:
self.total_callback_function(**arg)
if self.callback_function is not None and self.callback_function != self.total_callback_function:

View File

@@ -0,0 +1,27 @@
import traceback
from support import SupportSubprocess, logger
class SupportFfprobe:
__ffprobe_path = 'ffprobe'
@classmethod
def initialize(cls, __ffprobe_path):
cls.__ffprobe_path = __ffprobe_path
@classmethod
def ffprobe(cls, filepath, ffprobe_path=None, option=None):
try:
if ffprobe_path == None:
ffprobe_path = cls.__ffprobe_path
command = [ffprobe_path, '-v', 'quiet', '-print_format', 'json', '-show_format', '-show_streams', filepath]
if option is not None:
command += option
logger.warning(' '.join(command))
ret = SupportSubprocess.execute_command_return(command, format='json')
return ret['log']
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())

View File

@@ -204,9 +204,9 @@ class GoogleSheetBase:
break
except gspread.exceptions.APIError:
self.sleep_exception()
except Exception as exception:
except Exception as e:
logger.error(f"{key} - {value}")
logger.error('Exception:%s', exception)
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
logger.error(self.header_info)
self.sleep_exception()

View File

@@ -0,0 +1,197 @@
import json
import os
import traceback
from support import SupportSubprocess, d, logger
class SupportRclone(object):
__instance_list = []
__rclone_path = 'rclone'
__rclone_config_path = 'rclone.conf'
@classmethod
def initialize(cls, __rclone_path, __rclone_config_path):
cls.__rclone_path = __rclone_path
cls.__rclone_config_path = __rclone_config_path
@classmethod
def get_rclone_path(cls):
return cls.__rclone_path
@classmethod
def __get_cmd(cls, config_path=None):
command = [cls.__rclone_path]
if config_path == None:
command += ['--config', cls.__rclone_config_path]
else:
command += ['--config', config_path]
return command
@classmethod
def rclone_cmd(cls):
return [cls.__rclone_path, '--config', cls.__rclone_config_path]
@classmethod
def get_version(cls, rclone_path=None):
try:
if rclone_path == None:
rclone_path = cls.__rclone_path
cmd = [rclone_path, '--version']
result = SupportSubprocess.execute_command_return(cmd)
if result != None and result['status'] == 'finish':
return result['log']
except Exception as e:
logger.error(f'Exception:{str(e)}')
logger.error(traceback.format_exc())
@classmethod
def config_list(cls, rclone_path=None, rclone_config_path=None, option=None):
try:
if rclone_path == None:
rclone_path = cls.__rclone_path
if rclone_config_path == None:
rclone_config_path = cls.__rclone_config_path
if os.path.exists(rclone_config_path) == False:
return
command = [rclone_path, '--config', rclone_config_path, 'config', 'dump']
if option is not None:
command += option
result = SupportSubprocess.execute_command_return(command, format='json')
for key, value in result['log'].items():
if 'token' in value and value['token'].startswith('{'):
value['token'] = json.loads(value['token'])
return result['log']
except Exception as e:
logger.error(f'Exception:{str(e)}')
logger.error(traceback.format_exc())
@classmethod
def get_config(cls, remote_name, rclone_path=None, rclone_config_path=None, option=None):
try:
data = cls.config_list(rclone_path=rclone_path, rclone_config_path=rclone_config_path, option=option)
return data.get(remote_name, None)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@classmethod
def lsjson(cls, remote_path, config_path=None, option=None):
return cls.__execute_one_param('lsjson', remote_path, config_path=config_path, option=option, format='json')
@classmethod
def lsf(cls, remote_path, config_path=None, option=None):
if option == None:
option = ['--max-depth=1']
return cls.__execute_one_param('lsf', remote_path, config_path=config_path, option=option, format='json')
@classmethod
def size(cls, remote_path, config_path=None, option=None):
if option == None:
option = ['--json']
return cls.__execute_one_param('size', remote_path, config_path=config_path, option=option, format='json')
@classmethod
def mkdir(cls, remote_path, config_path=None, option=None):
return cls.__execute_one_param('mkdir', remote_path, config_path=config_path, option=option, format='json')
@classmethod
def purge(cls, remote_path, config_path=None, option=None):
return cls.__execute_one_param('purge', remote_path, config_path=config_path, option=option, format='json')
@classmethod
def __execute_one_param(cls, command, remote_path, config_path=None, option=None, format=None):
try:
command = cls.__get_cmd(config_path) + [command, remote_path]
if option is not None:
command += option
result = SupportSubprocess.execute_command_return(command, format=format)
ret = None
if result != None and result['status'] == 'finish':
ret = result['log']
return ret
except Exception as e:
logger.error(f'Exception:{str(e)}')
logger.error(traceback.format_exc())
@classmethod
def copy(cls, src, tar, config_path=None, option=None):
return cls.__execute_two_param('copy', src, tar, config_path=config_path, option=option)
@classmethod
def copy_server_side(cls, src, tar, config_path=None, option=None):
if option == None:
option = ['--drive-server-side-across-configs=true', '--delete-empty-src-dirs']
return cls.__execute_two_param('copy', src, tar, config_path=config_path, option=option)
@classmethod
def move(cls, src, tar, config_path=None, option=None):
return cls.__execute_two_param('move', src, tar, config_path=config_path, option=option)
@classmethod
def move_server_side(cls, src, tar, config_path=None, option=None):
if option == None:
option = ['--drive-server-side-across-configs=true', '--delete-empty-src-dirs']
return cls.__execute_two_param('move', src, tar, config_path=config_path, option=option)
@classmethod
def __execute_two_param(cls, command, src, tar, config_path=None, option=None, format=None):
try:
command = cls.__get_cmd(config_path) + [command, src, tar]
if option is not None:
command += option
result = SupportSubprocess.execute_command_return(command, format=format)
ret = None
if result != None and result['status'] == 'finish':
ret = result['log']
return ret
except Exception as e:
logger.error(f'Exception:{str(e)}')
logger.error(traceback.format_exc())
@classmethod
def getid(cls, remote_path, config_path=None, option=None):
try:
command = cls.__get_cmd(config_path) + ['backend', 'getid', remote_path]
if option is not None:
command += option
result = SupportSubprocess.execute_command_return(command)
ret = None
if result != None and result['status'] == 'finish':
ret = result['log']
if ret is not None and (len(ret.split(' ')) > 1 or ret == ''):
ret = None
return ret
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@classmethod
def chpar(cls, src, tar, config_path=None, option=None):
try:
command = cls.__get_cmd(config_path) + ['backend', 'chpar', src, tar, '-o', 'depth=1', '-o', 'delete-empty-src-dir', '--drive-use-trash=false']
if option is not None:
command += option
result = SupportSubprocess.execute_command_return(command)
ret = None
if result != None and result['status'] == 'finish':
ret = result['log']
return True
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False

View File

@@ -0,0 +1,114 @@
import os
from support import d, logger
try:
from selenium import webdriver
except:
os.system("pip install --upgrade selenium")
from selenium import webdriver
import base64
import threading
import time
import traceback
from selenium import webdriver
from selenium.common.exceptions import UnexpectedAlertPresentException
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import Select, WebDriverWait
from webdriver_manager.chrome import ChromeDriverManager
class SupportSimpleSelenium(object):
def __init__(self, P, mode="local", headless=False, remote=None):
self.P = P
self.driver = None
self.timeout = 5
self.driver_init(mode=mode, headless=headless, remote=remote)
def driver_init(self, mode='local', headless=False, remote=None):
if mode == 'local':
from selenium.webdriver.chrome.options import Options
options = Options()
if headless:
options.add_argument('headless')
self.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
elif mode == 'remote':
from selenium.webdriver.chrome.options import Options
options = Options()
#options.set_preference("general.useragent.override", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36")
#options.set_preference("general.platform.override", "Win32")
self.driver = webdriver.Remote(remote, options=options)
def get_pagesoruce(self, url, wait_xpath="/html/body", retry=True):
try:
self.driver.get(url)
WebDriverWait(self.driver, self.timeout).until(lambda driver: driver.find_element(By.XPATH, wait_xpath))
return self.driver.page_source
except Exception as e:
logger.error('Exception:%s', e)
logger.error(traceback.format_exc())
self.driver_quit()
if retry:
return self.get_pagesoruce(url, wait_xpath=wait_xpath, retry=False)
def driver_quit(self):
if self.driver != None:
def func():
self.driver.quit()
self.driver = None
#self.logger.debug('driver quit..')
th = threading.Thread(target=func, args=())
th.setDaemon(True)
th.start()
def get_downloaded_files(self):
if not self.driver.current_url.startswith("chrome://downloads"):
self.driver.get("chrome://downloads/")
#driver.implicitly_wait(4)
self.driver.implicitly_wait(2)
return self.driver.execute_script( \
"return document.querySelector('downloads-manager') "
" .shadowRoot.querySelector('#downloadsList') "
" .items.filter(e => e.state === 'COMPLETE') "
" .map(e => e.filePath || e.file_path || e.fileUrl || e.file_url); ")
def get_file_content(self, path):
elem = self.driver.execute_script( \
"var input = window.document.createElement('INPUT'); "
"input.setAttribute('type', 'file'); "
"input.hidden = true; "
"input.onchange = function (e) { e.stopPropagation() }; "
"return window.document.documentElement.appendChild(input); " )
elem._execute('sendKeysToElement', {'value': [ path ], 'text': path})
result = self.driver.execute_async_script( \
"var input = arguments[0], callback = arguments[1]; "
"var reader = new FileReader(); "
"reader.onload = function (ev) { callback(reader.result) }; "
"reader.onerror = function (ex) { callback(ex.message) }; "
"reader.readAsDataURL(input.files[0]); "
"input.remove(); "
, elem)
if not result.startswith('data:') :
raise Exception("Failed to get file content: %s" % result)
return base64.b64decode(result[result.find('base64,') + 7:])
# docker run -d --name selenium_chromium -it -p 4446:4444 -p 5902:5900 -p 7902:7900 --shm-size 2g seleniarm/standalone-chromium:latest

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -36,7 +36,7 @@ class CustomFormatter(logging.Formatter):
# pathname filename
#format = "[%(asctime)s|%(name)s|%(levelname)s - %(message)s (%(filename)s:%(lineno)d)"
__format = '[{yellow}%(asctime)s{reset}|{color}%(levelname)s{reset}|{green}%(name)s{reset} %(pathname)s:%(lineno)s] {color}%(message)s{reset}' if os.environ.get('LOGGER_PATHNAME', "False") == "True" else '[{yellow}%(asctime)s{reset}|{color}%(levelname)s{reset}|{green}%(name)s{reset} %(filename)s:%(lineno)s] {color}%(message)s{reset}'
__format = '[{yellow}%(asctime)s{reset}|{color}%(levelname)s{reset}|{green}%(name)s{reset}|%(pathname)s:%(lineno)s] {color}%(message)s{reset}' if os.environ.get('LOGGER_PATHNAME', "False") == "True" else '[{yellow}%(asctime)s{reset}|{color}%(levelname)s{reset}|{green}%(name)s{reset}|%(filename)s:%(lineno)s] {color}%(message)s{reset}'
FORMATS = {
logging.DEBUG: __format.format(color=grey, reset=reset, yellow=yellow, green=green),
@@ -53,15 +53,13 @@ class CustomFormatter(logging.Formatter):
def get_logger(name=None, log_path=None):
if os.environ.get('FF') == 'true':
name = 'framework'
if name == None:
name = sys.argv[0].rsplit('.', 1)[0]
logger = logging.getLogger(name)
if not logger.handlers:
level = logging.DEBUG
logger.setLevel(level)
formatter = logging.Formatter(u'[%(asctime)s|%(levelname)s|%(filename)s:%(lineno)s] %(message)s')
formatter = logging.Formatter(u'[%(asctime)s|%(levelname)s|%(name)s|%(filename)s:%(lineno)s] %(message)s')
def customTime(*args):
utc_dt = utc.localize(datetime.utcnow())
my_tz = timezone("Asia/Seoul")

View File

@@ -4,6 +4,7 @@ from support import SupportSC
try:
if os.path.exists(os.path.join(os.path.dirname(__file__), 'tving.py')):
#from .cppl import SupportCppl
from .kakaotv import SupportKakaotv
from .seezn import SupportSeezn
from .tving import SupportTving
@@ -13,5 +14,6 @@ try:
SupportWavve = SupportSC.load_module_f(__file__, 'wavve').SupportWavve
SupportSeezn = SupportSC.load_module_f(__file__, 'seezn').SupportSeezn
SupportKakaotv = SupportSC.load_module_f(__file__, 'kakaotv').SupportKakaotv
#SupportCppl = SupportSC.load_module_f(__file__, 'cppl').SupportCppl
except:
pass

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,277 @@
- name: "기본"
list:
- title: "파일 매니저"
package_name: "flaskfilemanager"
developer: "stevelittlefish"
home: "https://github.com/flaskfarm/flaskfilemanager"
description: "RichFilemanager를 Flask에서 동작하도록 한 FlaskFileManager 포크"
- title: "터미널"
package_name: "terminal"
developer: "joyful"
home: "https://github.com/flaskfarm/terminal"
description: "리눅스 전용 심플 터미널. xterm.js client"
- title: "편집기"
package_name: "flaskcode"
developer: "sujeetkv"
home: "https://github.com/flaskfarm/flaskcode"
description: "flaskcode를 fork한 문서 편집기"
- name: "보조 & 라이브러리"
list:
- title: "번역"
package_name: "trans"
developer: "FlaskFarm"
home: "https://github.com/flaskfarm/trans"
description: "번역 관련 설정 및 API 제공"
- title: "support_site"
package_name: "support_site"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/support_site"
description: "사이트 크롤링 라이브러리"
- title: "Gdrive 라이브러리 for FlaskFarm"
package_name: "libgdrive"
developer: "orial"
home: "https://github.com/byorial/libgdrive"
description: "구글드라이브 API 라이브러리"
- name: "외부 연결"
list:
- title: "Rclone"
package_name: "rclone"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/rclone"
description: "Rclone을 좀 더 쉽게 사용하기 위한 플러그인"
- title: "PLEX MATE"
package_name: "plex_mate"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/plex_mate"
description: "PLEX와 동일 기기에서 동작하는 툴"
- title: "vnStat"
package_name: "vnStat"
developer: "by275"
home: "https://github.com/by275/vnStat"
description: "vnStat 정보를 보여주는 플러그인"
- title: "115 TOOL"
package_name: "tool_115"
developer: "soju6jan"
home: "https://github.com/soju6jan/tool_115"
description: "115 TOOL"
- name: "도구"
list:
- title: "디스코드 봇"
package_name: "discord_bot"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/discord_bot"
description: "디스코드 봇"
- title: "정적 호스트"
package_name: "static_host"
developer: "by275"
description: "정적 웹을 호스팅 하는 플러그인"
home: "https://github.com/by275/static_host"
- title: 'Flaskfarmaider'
package_name: 'flaskfarmaider'
developer: 'halfaider'
description: 'Flaskfarm 보조 플러그인'
home: 'https://github.com/halfaider/flaskfarmaider'
- title: "메타데이터"
package_name: "metadata"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/metadata"
description: "VIDEO 메타데이터 제공"
- title: "lotto"
package_name: "lotto"
developer: "honeypig5"
home: "https://github.com/hudulgi/lotto"
description: "로또 구매"
- title: "핫딜 알람"
package_name: "hotdeal_alarm"
developer: "dbswnschl"
home: "https://github.com/dbswnschl/hotdeal_alarm"
description: "핫딜 알리미"
- name: "파일처리"
list:
- title: "국내TV 파일처리"
package_name: "fp_ktv"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/fp_ktv"
description: "국내TV 영상 파일 전용 파일처리"
- title: "영화 파일처리"
package_name: "fp_movie"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/fp_movie"
description: "영화 파일처리"
- title: "musicProc2"
package_name: "musicProc2"
developer: "dyllislev"
home: "https://github.com/dyllisLev/musicProc2"
description: "음악정리"
- title: "해외TV 파일처리"
package_name: "fp_ftv"
developer: "kihyyo"
home: "https://github.com/kihyyo/fp_ftv"
description: "해외TV 영상 파일 전용 파일처리"
- title: "자막 툴"
package_name: "subtitle_tool"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/subtitle_tool"
description: "자막 관련 툴"
- name: "토렌트 & RSS"
list:
- title: "토렌트 정보"
package_name: "torrent_info"
developer: "by275"
home: "https://github.com/by275/torrent_info"
description: "토렌트 마그넷/파일 정보를 보여주는 플러그인"
- title: "티프리카"
package_name: "tfreeca"
developer: "by275"
home: "https://github.com/by275/tfreeca"
description: "티프리카 게시판 뷰어"
- name: "OTT"
list:
- title: "티빙 검색"
package_name: "tving_search"
developer: "by275"
home: "https://github.com/by275/tving_search"
description: "티빙 검색 플러그인"
- title: "FFMPEG"
package_name: "ffmpeg"
developer: "FlaskFarm"
home: "https://github.com/flaskfarm/ffmpeg"
description: "m3u8 다운로드 & 비디오파일 분석"
- title: "웨이브"
package_name: "wavve"
developer: "halfaider"
home: "https://github.com/halfaider/wavve"
description: "웨이브 VOD 다운로더"
- title: "티빙"
package_name: "tving"
developer: "kihyyo"
home: "https://github.com/kihyyo/tving"
description: "티빙 VOD 다운로더"
- title: "쿠팡플레이"
package_name: "cppl"
developer: "soju6jan"
home: "https://github.com/soju6jan/cppl"
description: "쿠팡플레이 VOD 다운로더"
- title: "DRM 다운로드"
package_name: "wv_tool"
developer: "soju6jan"
home: "https://github.com/soju6jan/wv_tool"
description: "DRM 영상 다운로드"
- title: "make_yaml"
package_name: "make_yaml"
developer: "kihyyo"
home: "https://github.com/kihyyo/make_yaml"
description: "OTT 정보로 직접 yaml로 만드는 플러그인"
- title: "RE_tool"
package_name: "RE_tool"
developer: "kihyyo"
home: "https://github.com/kihyyo/RE_tool"
description: "M3U8_RE 다운로드"
- name: "라이브"
list:
- title: "ALive"
package_name: "alive"
developer: "by275"
home: "https://github.com/by275/alive"
description: "라이브 방송 플러그인"
- title: "EPG"
package_name: "epg"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/epg"
description: "EPG 생성"
- title: "MY EPG"
package_name: "myepg"
developer: "include"
home: "https://github.com/myepg/myepg"
description: "EPG API 플러그인"
- title: "HDHomerun"
package_name: "hdhomerun"
developer: "flaskfarm"
home: "https://github.com/flaskfarm/hdhomerun"
description: "HDHomerun 도구"
- title: "스포TV"
package_name: "ff_spotv"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_spotv"
description: "스포TV 방송 FF플러그인"
- title: "삼성TV플러스"
package_name: "ff_sstvplus"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_sstvplus"
description: "삼성TV플러스 방송 FF플러그인"
- title: "네이버스포츠"
package_name: "ff_nsports"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_nsports"
description: "네이버스포츠 방송 FF플러그인"
- title: "쿠팡플레이"
package_name: "ff_cpp"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_cpp"
description: "쿠팡플레이 방송 FF플러그인"
- title: "아프리카TV"
package_name: "ff_afrtv"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_afrtv"
description: "아프리카TV 방송 FF플러그인"
- title: "REYSTREAM"
package_name: "ff_reystream"
developer: "ssagajikorea"
description: "REYSTREAM 방송 FF플러그인"
home: "https://github.com/ssagajikorea/ff_reystream"
- title: "팝콘TV"
package_name: "ff_pktv"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_pktv"
description: "팝콘TV 방송 FF플러그인"
- title: "팬더TV"
package_name: "ff_pdtv"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_pdtv"
description: "팬더TV 방송 FF플러그인"
- title: "NEXTCAST"
package_name: "ff_nextcast"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_nextcast"
description: "NEXTCAST 방송 FF플러그인"
- title: "LIFETV365"
package_name: "ff_lifetv365"
developer: "ssagajikorea"
home: "https://github.com/ssagajikorea/ff_lifetv365"
description: "LIFETV365 방송 FF플러그인"
- title: "KLive+"
package_name: "klive_plus"
developer: "soju6jan"
home: "https://github.com/soju6jan/klive_plus"
description: "KLive+"
- name: "SJVA"
list:
- title: "SJVA"
package_name: "sjva"
developer: "soju6jan"
home: "https://github.com/soju6jan/sjva"
description: "SJVA 인증 & 사이트 연동"
- title: "구드공 툴"
package_name: "gds_tool"
developer: "soju6jan"
home: "https://github.com/soju6jan/gds_tool"
description: "복사요청, 제공, GDS 변경사항"
- title: "봇 다운로더"
package_name: "bot_downloader"
developer: "flaskfarm"
home: "https://github.com/soju6jan/bot_downloader"
description: "봇 다운로더"
# name: "기타"
# list:
# - title: "숫자 야구"
# package_name: "number_baseball"
# developer: "FlaskFarm"
# home: "https://github.com/flaskfarm/number_baseball"
# description: "숫자 야구 - 샘플 플러그인"
# - title: "샘플 플러그인"
# package_name: "sample"
# developer: "FlaskFarm"
# home: "https://github.com/flaskfarm/sample"
# description: "샘플 플러그인"

View File

@@ -0,0 +1,9 @@
### 알림
##### 텔레그램
* @BotFather 에게서 Bot 생성
* https://api.telegram.org/bot봇토큰/getUpdates 접속
* 본인의 봇과 대화
* https://api.telegram.org/bot봇토큰/getUpdates 접속
"from":{"id":879500000, 숫자가 본인의 Chat ID

View File

@@ -31,7 +31,7 @@ class ModuleHome(PluginModuleBase):
for key, value in F.app.config.items():
if key not in ['SECRET_KEY']:
data[key] = str(value)
ret = {'json':{'Framework':F.config, 'Flask':data}}
ret = {'json':{'Framework':F.config, 'Flask':data}, 'title':'config'}
return jsonify(ret)
@@ -56,7 +56,7 @@ class ModuleHome(PluginModuleBase):
ret = {}
ret['system'] = self.get_info()
ret['scheduler'] = scheduler.get_job_list_info()
F.socketio.emit("status", ret, namespace=f'/{P.package_name}/{name}', broadcast=True)
F.socketio.emit("status", ret, namespace=f'/{P.package_name}/{name}')
def get_info(self, mode=''):

View File

@@ -22,14 +22,11 @@ class ModuleLog(PluginModuleBase):
log_list.append(x)
arg['log_list'] = '|'.join(log_list)
arg['all_list'] = '|'.join(log_files)
arg['filename'] = 'framework.log'
print(request.form)
print(request.form)
print(request.form)
print(request.form)
arg['filename'] = 'all.log'
if 'filename' in request.form:
arg['filename'] = request.form['filename']
arg['filename'] = req.args.get('filename', arg['filename'])
return render_template(f'{__package__}_{name}.html', arg=arg)
except Exception as e:
P.logger.error(f'Exception:{str(e)}')

View File

@@ -1,14 +1,16 @@
import shutil
from support import SupportFile
from support import SupportFile, SupportYaml
from .setup import *
name = 'plugin'
class ModulePlugin(PluginModuleBase):
def __init__(self, P):
super(ModulePlugin, self).__init__(P, name=name, first_menu='list')
self.all_plugin_list = None
def process_menu(self, page, req):
@@ -36,14 +38,16 @@ class ModulePlugin(PluginModuleBase):
"""
for name, entity in F.PluginManager.all_package_list.items():
try:
if entity.get('version') == '3':
#data.append(entity)
data.append({'package_name':name})
else:
if 'P' in entity:
data.append(entity['P'].plugin_info)
data[-1]['loading'] = entity.get('loading')
data[-1]['status'] = entity.get('status')
data[-1]['log'] = entity.get('log')
else:
data.append({'package_name':name})
data[-1]['loading'] = entity.get('loading')
data[-1]['status'] = entity.get('status')
data[-1]['log'] = entity.get('log')
except Exception as e:
data.append({'package_name':name})
P.logger.error(f'Exception:{str(e)}')
@@ -64,6 +68,36 @@ class ModulePlugin(PluginModuleBase):
else:
ret['msg'] = info['path'] + "<br>폴더가 없습니다."
ret['ret'] = 'danger'
elif command == 'get_plugin_list_all':
if self.all_plugin_list == None:
filepath = os.path.join(os.path.dirname(__file__), 'files', 'all_plugin.yaml')
self.all_plugin_list = SupportYaml.read_yaml(filepath)
def get_plugin(_name):
for _cate in self.all_plugin_list:
for _plugin in _cate['list']:
if _plugin['package_name'] == _name:
P.logger.info(_name)
if _name == 'ff_reystream':
P.logger.info(_name)
return _plugin
for name, entity in F.PluginManager.all_package_list.items():
try:
_plugin = get_plugin(name)
if _plugin != None:
_plugin['loading'] = entity.get('loading')
_plugin['status'] = entity.get('status')
_plugin['log'] = entity.get('log')
_plugin['version'] = entity['P'].plugin_info['version']
except Exception as e:
data.append({'package_name':name})
P.logger.error(f'Exception:{str(e)}')
P.logger.error(traceback.format_exc())
ret['data'] = self.all_plugin_list
return jsonify(ret)

View File

@@ -53,7 +53,7 @@ class ModuleRoute(PluginModuleBase):
@F.socketio.on('connect', namespace=f'/{P.package_name}/restart')
def restart_socket_connect():
F.socketio.emit('connect', {}, namespace='/{P.package_name}/restart', broadcast=True)
F.socketio.emit('connect', {}, namespace='/{P.package_name}/restart')
def process_menu(self, page, req):

View File

@@ -2,8 +2,8 @@ import random
import string
import time
from support import (SupportDiscord, SupportFile, SupportSubprocess,
SupportTelegram)
from support import (SupportDiscord, SupportFile, SupportSlack,
SupportSubprocess, SupportTelegram, SupportYaml)
from tool import ToolModalCommand
from .setup import *
@@ -12,7 +12,7 @@ name = 'setting'
class ModuleSetting(PluginModuleBase):
db_default = {
'db_version' : '1',
'db_version' : '1.1',
'port' : '9999',
'ddns' : 'http://localhost:9999',
'use_login' : 'False',
@@ -34,6 +34,8 @@ class ModuleSetting(PluginModuleBase):
'notify_telegram_disable_notification' : 'False',
'notify_discord_use' : 'False',
'notify_discord_webhook' : '',
'notify_slack_use' : 'False',
'notify_slack_webhook' : '',
'notify_advaned_use' : 'False',
'notify.yaml': '', #직접 사용하지 않으나 저장 편의상.
'command_text': '',
@@ -91,10 +93,15 @@ class ModuleSetting(PluginModuleBase):
ret['msg'] = 'export.sh 파일이 없습니다.'
elif command == 'menu_save':
SupportFile.write_file(F.config['menu_yaml_filepath'], arg1 )
ret['msg'] = '저장하였습니다.'
from framework.init_menu import MenuManager
MenuManager.init_menu()
F.socketio.emit("refresh", {}, namespace='/framework', broadcast=True)
try:
SupportYaml.read_yaml(F.config['menu_yaml_filepath'])
ret['msg'] = '저장하였습니다.'
from framework.init_menu import MenuManager
MenuManager.init_menu()
F.socketio.emit("refresh", {}, namespace='/framework')
except:
ret['ret'] = "danger"
ret['msg'] = "yaml 형식에 맞지 않습니다"
elif command == 'notify_test':
if arg1 == 'telegram':
token, chatid, sound, text = arg2.split('||')
@@ -104,6 +111,9 @@ class ModuleSetting(PluginModuleBase):
elif arg1 == 'discord':
SupportDiscord.send_discord_message(arg3, webhook_url=arg2)
ret['msg'] = '메시지를 전송했습니다.'
elif arg1 == 'slack':
SupportSlack.send_slack_message(arg3, webhook_url=arg2)
ret['msg'] = '메시지를 전송했습니다.'
elif arg1 == 'advanced':
from tool import ToolNotify
ToolNotify.send_advanced_message(arg3, message_id=arg2)
@@ -122,7 +132,31 @@ class ModuleSetting(PluginModuleBase):
ret['type'] = 'warning'
elif command == 'command_run':
ret['msg'] = arg1
pass
SystemModelSetting.set('command_text', arg1)
# db이름 set/get key value
try:
tmp = arg1.strip().split(' ')
if tmp[0].startswith('setting'):
plugin = F.PluginManager.get_plugin_instance(tmp[1])
if len(tmp) == 2 or tmp[2] == 'all':
ret['json'] = plugin.ModelSetting.to_dict()
ret['ret'] = 'success'
elif tmp[2] == 'get':
ret['msg'] = plugin.ModelSetting.get(tmp[3])
ret['ret'] = 'success'
elif tmp[2] == 'set':
value = ""
if len(tmp) == 5:
value = tmp[4]
plugin.ModelSetting.set(tmp[3], value)
ret['msg'] = f"{tmp[1]} DB에 {tmp[3]}={value} 저장"
except Exception as e:
P.logger.error(f'Exception:{str(e)}')
P.logger.error(traceback.format_exc())
ret['msg'] = f"실행 실패: {str(e)}"
ret['type'] = 'danger'
elif command == 'celery_execute':
self.celery_execute(arg1, mode='foreground')
elif command == 'celery_execute_back':
@@ -138,8 +172,7 @@ class ModuleSetting(PluginModuleBase):
try:
if F.config['run_flask'] == False:
return
if SystemModelSetting.get_bool('celery_start_by_web'):
self.celery_execute()
if F.config['arg_repeat'] == 0 or SystemModelSetting.get('system_start_time') == '':
SystemModelSetting.set('system_start_time', datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
SystemModelSetting.set('repeat', str(F.config['arg_repeat']))
@@ -162,7 +195,11 @@ class ModuleSetting(PluginModuleBase):
from tool import ToolNotify
msg = f"시스템이 시작되었습니다.\n재시작: {F.config['arg_repeat']}"
ToolNotify.send_message(msg, message_id='system_start')
if SystemModelSetting.get_bool('celery_start_by_web'):
# 2022-11-14 DB는 flask가 만드는데 만들기전 celery를 실행해버림
from threading import Timer
Timer(10, self.celery_execute).start()
#self.celery_execute()
except Exception as e:
P.logger.error(f'Exception:{str(e)}')
@@ -173,9 +210,14 @@ class ModuleSetting(PluginModuleBase):
def setting_save_after(self, change_list):
if 'theme' in change_list or 'web_title' in change_list:
F.socketio.emit("refresh", {}, namespace='/framework', broadcast=True)
F.socketio.emit("refresh", {}, namespace='/framework')
elif 'notify.yaml' in change_list:
SupportFile.write_file(F.config['notify_yaml_filepath'], SystemModelSetting.get('notify.yaml'))
try:
SupportFile.write_file(F.config['notify_yaml_filepath'], SystemModelSetting.get('notify.yaml'))
SupportYaml.read_yaml(F.config['notify_yaml_filepath'])
except:
data = {'type':'danger', 'msg' : "알림 정책이 yaml 형식에 맞지 않습니다."}
F.socketio.emit("notify", data, namespace='/framework')
elif 'web_pw' in change_list:
import hashlib
enc = hashlib.md5()
@@ -185,6 +227,9 @@ class ModuleSetting(PluginModuleBase):
if SystemModelSetting.get('restart_interval') == '':
SystemModelSetting.set('restart_interval', '0')
self.__set_restart_scheduler()
elif 'log_level' in change_list:
F.set_level(SystemModelSetting.get_int('log_level'))
def __set_restart_scheduler(self):
@@ -258,6 +303,7 @@ class ModuleSetting(PluginModuleBase):
try:
time.sleep(1)
data = '정상입니다. 이 메시지는 celery 에서 반환됩니다. '
P.logger.info(data)
return data
except Exception as e:
P.logger.error(f'Exception:{str(e)}')

View File

@@ -1,4 +1,5 @@
import queue
import shlex
from support import SupportSubprocess
from tool import ToolModalCommand
@@ -25,7 +26,7 @@ class PageCommand(PluginPageBase):
ret = {'ret':'success'}
if command == 'foreground_command':
P.ModelSetting.set(f'{self.parent.name}_{self.name}_recent', arg1)
self.__foreground_execute(arg1, arg1.split(' '))
self.__foreground_execute(arg1, shlex.split(arg1))
return jsonify('')
elif command == 'job_new':
@@ -54,7 +55,7 @@ class PageCommand(PluginPageBase):
elif command == 'job_fore_execute':
db_item = ModelCommand.get_by_id(arg1)
cmd = (db_item.command + ' ' + db_item.args).strip()
self.__foreground_execute(f"Command ID: {db_item.id}", cmd.split(' '), db_item.id)
self.__foreground_execute(f"Command ID: {db_item.id}", shlex.split(cmd), db_item.id)
elif command == 'job_back_execute':
self.execute_thread_start(arg1)
ret['msg'] = "실행 요청을 하였습니다.<br>로그를 확인하세요."
@@ -64,8 +65,8 @@ class PageCommand(PluginPageBase):
ret['ret'] = 'danger'
ret['msg'] = "로그 파일이 없습니다."
elif command == 'task_sched':
job_id = req.form['arg1']
flag = (req.form['arg2'] == 'true')
job_id = arg1
flag = (arg2 == 'true')
scheduler_id = f'command_{job_id}'
if flag and F.scheduler.is_include(scheduler_id):
ret['msg'] = '이미 스케쥴러에 등록되어 있습니다.'
@@ -92,7 +93,7 @@ class PageCommand(PluginPageBase):
if command[0] != 'LOAD':
ToolModalCommand.start(title, [command])
else:
F.socketio.emit("command_modal_show", title, namespace='/framework', broadcast=True)
F.socketio.emit("command_modal_show", title, namespace='/framework')
def start_communicate_load(load_log_list):
def func():
while True:
@@ -100,7 +101,7 @@ class PageCommand(PluginPageBase):
load_log_list.truncate(0)
if logs:
P.logger.error(logs)
F.socketio.emit("command_modal_add_text", logs.strip() + '\n', namespace='/framework', broadcast=True)
F.socketio.emit("command_modal_add_text", logs.strip() + '\n', namespace='/framework')
if logs == '<<END>>':
break
time.sleep(0.3)
@@ -154,11 +155,12 @@ class PageCommand(PluginPageBase):
th = threading.Thread(target=self.execute_thread_function_by_job_id, args=(job_id,))
th.setDaemon(True)
th.start()
return th
def execute_thread_function_by_job_id(self, *args, **kwargs):
P.logger.error(d(args))
P.logger.error(d(kwargs))
#P.logger.error(d(args))
#P.logger.error(d(kwargs))
db_item = ModelCommand.get_by_id(args[0])
kwargs['id'] = args[0]
self.execute_thread_function((db_item.command + ' ' + db_item.args).strip(), **kwargs)
@@ -166,7 +168,7 @@ class PageCommand(PluginPageBase):
def execute_thread_function(self, command, **kwargs):
try:
cmd = command.split(' ')
cmd = shlex.split(command)
if cmd[0] == 'LOAD':
command_logger = F.get_logger(f"command_{kwargs['id']}")
@@ -177,8 +179,8 @@ class PageCommand(PluginPageBase):
def __init__(self, logger):
self.logger = logger
def stdout_callback(self, mode, text):
if mode == 'log':
def stdout_callback(self, call_id, mode, text):
if mode == 'LOG':
self.logger.debug(text)
else:
self.logger.debug(mode)
@@ -194,14 +196,17 @@ class PageCommand(PluginPageBase):
def plugin_load(self):
def plugin_load_thread():
try:
while F.config['loading_completed'] == False:
time.sleep(1)
db_items = ModelCommand.get_list()
for db_item in db_items:
if db_item.schedule_mode == 'startup':
self.execute_thread_start(db_item.id)
elif db_item.schedule_mode == 'scheduler' and db_item.schedule_auto_start:
self.__sched_add(db_item.id, db_item=db_item)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
try:
th = threading.Thread(target=plugin_load_thread)
@@ -219,7 +224,7 @@ class PageCommand(PluginPageBase):
job_id = f"command_{db_item.id}"
if scheduler.is_include(job_id):
return
job = Job(self.P.package_name, job_id, db_item.schedule_interval, self.execute_thread_function_by_job_id, db_item.description, args=db_item.id)
job = Job(self.P.package_name, job_id, db_item.schedule_interval, self.execute_thread_function_by_job_id, db_item.description, args=(db_item.id,))
scheduler.add_job_instance(job)
return True
except Exception as e:
@@ -286,6 +291,6 @@ class ModelCommand(ModelBase):
item['process'] = (SupportSubprocess.get_instance_by_call_id(f"command_{item['id']}") != None)
return data
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())

View File

@@ -14,6 +14,7 @@ __menu = {
{'uri': 'export', 'name': 'export.sh 파일'},
{'uri': 'celery', 'name': '비동기 작업(celery)'},
{'uri': 'notify', 'name': '알림'},
{'uri': 'manual/files/매뉴얼_설정.md', 'name': '매뉴얼'},
],
},
{
@@ -22,7 +23,7 @@ __menu = {
'list': [
#{'uri': 'setting', 'name': '설정'},
{'uri': 'list', 'name': '로딩 플러그인'},
#{'uri': 'all', 'name': '플러그인 목록'},
{'uri': 'all', 'name': '전체 플러그인 목록'},
],
},
{

View File

@@ -1,33 +1,9 @@
{% extends "base.html" %}
{% block content %}
</style>
<div>
{{ macros.setting_select_empty('log_select1', '로그 파일 선택 (.log)') }}
<!--{{ macros.setting_select_empty('log_select2', '로그 파일 선택 (.logX)') }}-->
<nav>
{{ macros.m_tab_head_start() }}
{{ macros.m_tab_head('old', '이전', true) }}
{{ macros.m_tab_head('new', '실시간', false) }}
{{ macros.m_tab_head_end() }}
</nav>
<div class="tab-content" id="nav-tabContent">
{{ macros.m_tab_content_start('old', true) }}
<div>
<textarea id="log" class="col-md-12" rows="30" charswidth="23" disabled style="background-color:#ffffff;visibility:hidden"></textarea>
</div>
{{ macros.m_tab_content_end() }}
{{ macros.m_tab_content_start('new', false) }}
<div>
<textarea id="add" class="col-md-12" rows="30" charswidth="23" disabled style="background-color:#ffffff;visibility:visible"></textarea>
</div>
<div class="form-inline">
<label class="form-check-label" for="auto_scroll">자동 스크롤</label>
<input id="auto_scroll" name="auto_scroll" class="form-control form-control-sm" type="checkbox" data-toggle="toggle" checked>
<span class='text-left' style="padding-left:25px; padding-top:0px">
<button id="clear" class="btn btn-sm btn-outline-success">리셋</button>
</span>
</div>
{{ macros.m_tab_content_end() }}
<div id="log_div" class="bg-dark" style="overflow:auto; border-color: blue; border: solid 1px;">
</div>
</div>
@@ -56,14 +32,6 @@ function make_form() {
str += '<option value="' + data[i] + '">' + data[i] + '</option>';
}
$("#log_select1_div").html(str);
/*
str = '<select id="log_select" name="log_select" class="form-control form-control-sm">';
data = all_list.split('|')
for(var i in data) {
str += '<option value="' + data[i] + '">' + data[i] + '</option>';
}
$("#log_select2_div").html(str);
*/
}
$("body").on('change', '#log_select', function(e){
@@ -75,11 +43,9 @@ $("body").on('change', '#log_select', function(e){
socket.emit("start", {'filename':filename} );
});
function ResizeTextAreaAllLog() {
ClientHeight = window.innerHeight
$("#log").height(ClientHeight-300);
$("#add").height(ClientHeight-320);
$("#log_div").height(ClientHeight-180);
}
$(window).resize(function() {
@@ -88,17 +54,20 @@ $(window).resize(function() {
socket.on('on_start', function(data){
document.getElementById("log").innerHTML += data.data;
document.getElementById("log").scrollTop = document.getElementById("log").scrollHeight;
document.getElementById("log").style.visibility = 'visible';
lines = splitLines(data.data);
var html = '';
for (i in lines) {
html += logline(lines[i]);
}
$('#log_div').html(html)
document.getElementById("log_div").scrollTop = document.getElementById("log_div").scrollHeight;
$('#loading').hide();
});
socket.on('add', function(data){
if (data.filename == current_filename) {
var chk = $('#auto_scroll').is(":checked");
document.getElementById("add").innerHTML += data.data;
if (chk) document.getElementById("add").scrollTop = document.getElementById("add").scrollHeight;
$('#log_div').append(logline(data.data.trim()));
document.getElementById("log_div").scrollTop = document.getElementById("log_div").scrollHeight;
}
});

View File

@@ -8,7 +8,7 @@
<div>
<div class="row">
<div class="col-sm-12">
<canvas id="mycanvas" height="100vh"></canvas>
<h3>시스템</h3>
<hr>
{{ macros.info_text_and_buttons('python_version', 'Python', [['globalLinkBtn', '패키지 관리', [('url','/system/tool/python')]]], info['python_version']) }}
@@ -45,21 +45,102 @@
<h3>스케쥴</h3>
<div id="scheduler_list_div"></div>
</div> <!--전체-->
</div>
<!--전체-->
<script src="{{ url_for('static', filename='js/chartjs-utils.js') }}"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js"></script>
<script>
const Utils = ChartUtils.init()
</script>
<script src="https://cdn.jsdelivr.net/npm/luxon@3.0.4"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@1.2.0"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-streaming@2.0.0"></script>
<script type="text/javascript">
$(document).ready(function(){
var socket = io.connect(window.location.href);
Chart.defaults.set("plugins.streaming", {
duration: 20000,
})
socket.on('start', function(data){
});
// used for example purposes
function getRandomIntInclusive(min, max) {
min = Math.ceil(min)
max = Math.floor(max)
return Math.floor(Math.random() * (max - min + 1)) + min
}
socket.on('status', function(data) {
make_system(data.system);
make_scheduler_list(data.scheduler);
});
});
$(document).ready(function () {
var socket = io.connect(window.location.href)
var postId = 1
socket.on("start", function (data) {})
socket.on("status", function (data) {
console.log(data.system.cpu_percent)
const now = Date.now()
//myChart.data.labels.push("T " + postId++)
/* */
myChart.data.datasets[0].data.push({ x: now, y: data.system.cpu_percent.replace(/.%/g, "") })
/* */
myChart.update()
make_system(data.system)
make_scheduler_list(data.scheduler)
})
/* const onRefresh = (chart) => {
const now = Date.now()
chart.data.datasets.forEach((dataset) => {
dataset.data.push({
x: now,
y: Utils.rand(0, 100),
})
})
}
*/
var ctx_live = document.getElementById("mycanvas")
var myChart = new Chart(ctx_live, {
type: "line",
data: {
labels: [],
datasets: [
{
label: "CPU",
backgroundColor: Utils.transparentize(Utils.CHART_COLORS.blue, 0.5),
borderColor: Utils.CHART_COLORS.blue,
cubicInterpolationMode: "monotone",
data: [],
},
],
},
options: {
plugins: {
// Change options for ALL axes of THIS CHART
streaming: {
duration: 20000,
},
},
scales: {
x: {
type: "realtime",
realtime: {
duration: 60000,
refresh: 1000,
delay: 1000,
//onRefresh: onRefresh,
},
},
y: {
title: {
display: true,
//text: "Value",
},
},
},
interaction: {
intersect: false,
},
},
})
})
$("body").on('click', '#recent_version_btn', function(e){
e.preventDefault();
@@ -70,7 +151,7 @@ $("body").on('click', '#recent_version_btn', function(e){
$("body").on('click', '#config_show_btn', function(e){
e.preventDefault();
globalSendCommand('get_config', null, null, null, 'Config');
globalSendCommand('get_config');
});
@@ -184,4 +265,4 @@ function make_scheduler_list(data) {
</script>
{% endblock %}
{% endblock %}

View File

@@ -45,7 +45,6 @@ $("body").on('click', '#login_btn', function(e){
$('#username').val(),
$('#password').val(),
$("#remember").is(":checked"),
'',
function(data) {
if (data == 'redirect') {
next = document.getElementById("next").value;
@@ -53,7 +52,6 @@ $("body").on('click', '#login_btn', function(e){
if (next == '' || next == 'None' || next == '/system/restart' || '/system/shutdown') {
next = '/'
}
//console.log(next)
window.location.href = next;
} else if (data == 'no_id') {
$.notify('<strong>ID가 없습니다.</strong>', {

View File

@@ -0,0 +1,153 @@
{% extends "base.html" %}
{% block content %}
<div>
<div id="plugin_list_div"></div>
</div>
<script type="text/javascript">
$(document).ready(function(){
globalSendCommand('get_plugin_list_all', null, null, null, function(data){
make_plugin_list(data.data);
});
});
$("body").on('click', '#plugin_install_btn', function(e){
e.preventDefault();
globalSendCommand('plugin_install', $('#_plugin_git').val());
});
function make_plugin_list(data) {
current_data = data;
str = ''
count = 0;
for (i in data) {
console.log(data[i])
str += j_row_start();
str += j_col(12, '<b><span style="font-size:150%; font-style:italic; margin=0px;">' + data[i].name + '</span></b>', 'left');
//str += '<hr style="width: 100%; margin:0px; margin-bottom:10px; margin-top:2px; margin-left:15px; margin-right:15px; background-color:black; height:2px" />'
//str += '<div class="d-inline-block"></div><hr style="width: 100%; margin:0px; margin-left:15px; margin-right:15px;background-color:#808080;">';
str += j_row_end();
//str += j_hr_black(0);
str += head();
for (j in data[i].list) {
console.log(data[i].list[j]);
str += j_row_start();
count += 1
str += j_col_wide(1, (parseInt(count)), 'center')
tmp = text_color_bootstrap(data[i].list[j].title, 'text-info');
str += j_col_wide(2, tmp);
tmp = data[i].list[j].package_name;
str += j_col_wide(2, tmp);
str += j_col_wide(1, data[i].list[j].developer);
if (data[i].list[j].version == null) {
str += j_col_wide(1, "미설치");
} else {
str += j_col_wide(1, text_color_bootstrap(data[i].list[j].version, 'text-danger'));
}
if (data[i].list[j].loading == false) {
tmp = data[i].list[j].description + '<br>' + text_color('[로딩 실패] ') + data[i].list[j].status;
str += j_col_wide(3, tmp);
} else {
str += j_col_wide(3, data[i].list[j].description);
}
tmp = ''
tmp += j_button_small('globalOpenBtn', '홈페이지', {'url':data[i].list[j].home}, 'primary', false, true);
if (data[i].list[j].version == null) {
tmp += j_button_small('install_btn', '설치', {'package_name':data[i].list[j].package_name, 'title':data[i].list[j].title, 'home':data[i].list[j].home}, 'info', false, true);
} else {
tmp += j_button_small('uninstall_btn', '삭제', {'package_name':data[i].list[j].package_name, 'title':data[i].list[j].title}, 'danger', false, true);
}
tmp = j_button_group(tmp)
str += j_col_wide(2, tmp, 'right')
str += j_row_end();
if (i != current_data.length -1) str += j_hr(0);
}
str += j_row_start();
str += j_row_end();
}
$("#plugin_list_div").html(str);
}
$("body").on('click', '#json_btn', function(e){
e.preventDefault();
item_id = $(this).data('idx');
showModal(current_data[item_id]);
});
$("body").on('click', '#install_btn', function(e){
e.preventDefault();
$("#confirm_title").html("설치 확인");
$("#confirm_body").html($(this).data('title') + " 플러그인을 설치 하시겠습니까?");
home = $(this).data('home');
$('#confirm_button').attr('onclick', "javascript:install(home);");
$("#confirm_modal").modal();
});
function install(git) {
globalSendCommand('plugin_install', git);
}
$("body").on('click', '#uninstall_btn', function(e){
e.preventDefault();
$("#confirm_title").html("삭제 확인");
$("#confirm_body").html($(this).data('title') + " 플러그인을 삭제 하시겠습니까?");
package_name = $(this).data('package_name');
$('#confirm_button').attr('onclick', "javascript:uninstall(package_name);");
$("#confirm_modal").modal();
});
function uninstall(package_name) {
globalSendCommand('uninstall', package_name, null, null, function(ret) {
});
}
$("body").on('click', '#plugin_uninstall_btn', function(e){
e.preventDefault();
plugin_name = $(this).data('plugin_name')
$.ajax({
url: '/' + package_name + '/ajax/plugin_uninstall',
type: "POST",
cache: false,
data:{plugin_name:plugin_name},
success: function (data) {
if (data == 'success') {
$.notify('<strong>재시작시 적용됩니다.</strong>', {
type: 'success'
});
} else {
$.notify('<strong>실패하였습니다.</strong>', {
type: 'warning'
});
}
}
});
});
function head(str) {
tmp = '<hr style="width: 100%; margin:0px; background-color:#808080;"> \
<div class="row chover" style="padding:0px; align-items:center;"> \
<div class="col-sm-1" style="padding:0px; margin:0px; text-align:center; word-break:break-all;"><strong>Idx</strong></div> \
<div class="col-sm-2" style="padding:0px; margin:0px; text-align:left; word-break:break-all;"><strong>Title</strong></div> \
<div class="col-sm-2" style="padding:0px; margin:0px; text-align:left; word-break:break-all;"><strong>Package Name</strong></div> \
<div class="col-sm-1" style="padding:0px; margin:0px; text-align:left; word-break:break-all;"><strong>Dev.</strong></div> \
<div class="col-sm-1" style="padding:0px; margin:0px; text-align:left; word-break:break-all;"><strong>Version</strong></div> \
<div class="col-sm-5" style="padding:0px; margin:0px; text-align:left; word-break:break-all;"><strong>Description</strong></div> \
</div> \
<hr style="width: 100%; margin:0px; margin-bottom:10px; margin-top:2px; background-color:#808080; height:2px" />';
return tmp;
}
</script>
{% endblock %}

View File

@@ -19,7 +19,7 @@
<script type="text/javascript">
$(document).ready(function(){
globalSendCommand('get_plugin_list', null, null, null, null, function(data){
globalSendCommand('get_plugin_list', null, null, null, function(data){
make_plugin_list(data.data);
});
});
@@ -31,7 +31,6 @@ $("body").on('click', '#plugin_install_btn', function(e){
function make_plugin_list(data) {
current_data = data;
console.log(data);
str = ''
console.log(data)
for (i in data) {
@@ -41,7 +40,7 @@ function make_plugin_list(data) {
if (data[i].title == null) {
str += j_col_wide(2, '');
str += j_col_wide(2, data[i].package_name);
str += j_col_wide(5, '');
str += j_col_wide(5, data[i].status, 'center');
tmp = j_button('uninstall_btn', '삭제', {'package_name':data[i].package_name}, 'danger', false, true);
} else {
str += j_col_wide(2, data[i].title);
@@ -74,7 +73,7 @@ function make_plugin_list(data) {
$("body").on('click', '#json_btn', function(e){
e.preventDefault();
item_id = $(this).data('idx');
m_modal(current_data[item_id]);
showModal(current_data[item_id]);
});
@@ -89,7 +88,7 @@ $("body").on('click', '#uninstall_btn', function(e){
function uninstall(package_name) {
globalSendCommand('uninstall', package_name, null, null, null, function(ret) {
globalSendCommand('uninstall', package_name, null, null, function(ret) {
});
}

View File

@@ -28,7 +28,6 @@ $(document).ready(function() {
var restartSocket = io.connect(window.location.href);
restartSocket.on('connect', function(data){
console.log('접속 받음')
window.location.href = referer;
});
}, 3000);

View File

@@ -33,7 +33,7 @@ $('#use_login').change(function() {
$("body").on('click', '#apikey_generate_btn', function(e) {
e.preventDefault();
globalSendCommand('apikey_generate', null, null, null, null, function(ret){
globalSendCommand('apikey_generate', null, null, null, function(ret){
$("#apikey").val(ret);
});
});

View File

@@ -1,22 +1,20 @@
{% extends "base.html" %}
{% block content %}
<div>
{{ macros.m_button_group([['globalSettingSaveBtn', '설정 저장']])}}
{{ macros.m_row_start('5') }}
{{ macros.m_row_end() }}
{{ macros.m_hr() }}
{{ macros.m_button_group([['globalSettingSaveBtn', '설정 저장']])}}
{{ macros.m_row_start('5') }}
{{ macros.m_row_end() }}
{{ macros.m_hr() }}
<form id='setting' name='setting'>
{{ macros.setting_input_int('port', 'Port', value=arg['port'], min='1', placeholder='Port', desc=['포트 번호입니다.', '네이티브 설치 혹은 도커 네트워크 타입이 호스트일 경우 반영됩니다.', '도커 브릿지 모드인 경우는 docker run -p 옵션에서 변경하시기 바랍니다.', '경고 : -p 브릿지 모드로 사용중 일 경우 9999번을 변경하지 마세요.']) }}
{{ macros.setting_input_text_and_buttons('ddns', 'DDNS', [['ddns_test_btn', '테스트']], value=arg['ddns'], desc=['외부에서 접근시 사용할 DDNS. http:// 나 https:// 로 시작해야합니다.', 'URL생성시 사용합니다.', '테스트 버튼 클릭 후 버전을 확인 할 수 있어야 합니다.']) }}
{{ macros.setting_input_text('restart_interval', '자동 재시작 시간', value=arg['restart_interval'], col='3', desc=['자동 재시작 간격(시간단위)이나 Cron 설정을 입력합니다.', '0이면 재시작 안함.']) }}
{{ macros.setting_checkbox('restart_notify', '시작시 알림', value=arg['restart_notify'], desc=['메시지 ID: system_start']) }}
{{ macros.setting_select('log_level', '로그 레벨', [['10', 'DEBUG'],['20', 'INFO'],['30', 'WARNING'],['40', 'ERROR'], ['50', 'CRITICAL'] ], value=arg['log_level'], col='3') }}
{{ macros.m_hr() }}
{{ macros.setting_input_text_and_buttons('command_text', 'Command', [['command_run_btn', 'Run']], value='', desc='') }}
</form>
</div><!--전체-->
<form id='setting' name='setting'>
{{ macros.setting_input_int('port', 'Port', value=arg['port'], min='1', placeholder='Port', desc=['포트 번호입니다.', '네이티브 설치 혹은 도커 네트워크 타입이 호스트일 경우 반영됩니다.', '도커 브릿지 모드인 경우는 docker run -p 옵션에서 변경하시기 바랍니다.', '경고 : -p 브릿지 모드로 사용중 일 경우 9999번을 변경하지 마세요.']) }}
{{ macros.setting_input_text_and_buttons('ddns', 'DDNS', [['ddns_test_btn', '테스트']], value=arg['ddns'], desc=['외부에서 접근시 사용할 DDNS. http:// 나 https:// 로 시작해야합니다.', 'URL생성시 사용합니다.', '테스트 버튼 클릭 후 버전을 확인 할 수 있어야 합니다.']) }}
{{ macros.setting_input_text('restart_interval', '자동 재시작 시간', value=arg['restart_interval'], col='3', desc=['자동 재시작 간격(시간단위)이나 Cron 설정을 입력합니다.', '0이면 재시작 안함.']) }}
{{ macros.setting_checkbox('restart_notify', '시작시 알림', value=arg['restart_notify'], desc=['메시지 ID: system_start']) }}
{{ macros.setting_select('log_level', '로그 레벨', [['10', 'DEBUG'],['20', 'INFO'],['30', 'WARNING'],['40', 'ERROR'], ['50', 'CRITICAL'] ], value=arg['log_level'], col='3') }}
{{ macros.m_hr() }}
{{ macros.setting_input_text_and_buttons('command_text', 'Command', [['command_run_btn', 'Run']], value=arg['command_text'], desc='') }}
</form>
<script type="text/javascript">
$("body").on('click', '#ddns_test_btn', function(e){

View File

@@ -9,7 +9,7 @@
<div class="tab-content" id="nav-tabContent">
{{ macros.info_text('use_celery', 'use_celery 값', arg['use_celery']) }}
{{ macros.info_text('running_type', 'running_type 값', arg['running_type']) }}
{{ macros.info_text('_tmp', '설명', "Docker는 celery가 서비스로 동작하기 때문에 설정이 불필요하며 '테스트' 버튼으로 작동 여부 확인만 가능합니다.", desc=['','native로 동작하는 경우 celery 실행을 따로 하지 않고 한번에 실행하기 위한 설정', 'Redis는 설정된 Port로 동작중인 상태여야 함.']) }}
{{ macros.info_text('_tmp', '설명', "보통 시작시 celery 실행 On 상태로 동작하며 개발시에만 Off로 설정.", desc=None) }}
{{ macros.m_hr() }}
<form id='setting' name='setting'>
{{ macros.setting_checkbox('celery_start_by_web', '시작시 celery 실행', value=arg['celery_start_by_web']) }}

View File

@@ -29,6 +29,14 @@
{{ macros.setting_input_text('notify_discord_webhook', '웹훅', value=arg['notify_discord_webhook']) }}
{{ macros.setting_input_text_and_buttons('tmp_text_discord', 'Test', [['tmp_discord_test_btn', '전송']], value='테스트 메시지입니다.', col='9') }}
</div>
{{ macros.m_hr() }}
{{ macros.setting_checkbox('notify_slack_use', '슬랙 사용', value=arg['notify_slack_use']) }}
<div id="notify_slack_use_div" class="collapse">
{{ macros.setting_input_text('notify_slack_webhook', '웹훅', value=arg['notify_slack_webhook']) }}
{{ macros.setting_input_text_and_buttons('tmp_text_slack', 'Test', [['tmp_slack_test_btn', '전송']], value='테스트 메시지입니다.', col='9') }}
</div>
{{ macros.m_tab_content_end() }}
{{ macros.m_tab_content_start('advanced', false) }}
@@ -53,6 +61,7 @@
$(document).ready(function(){
use_collapse("notify_telegram_use");
use_collapse("notify_discord_use");
use_collapse("notify_slack_use");
use_collapse("notify_advaned_use");
});
@@ -64,6 +73,10 @@ $('#notify_discord_use').change(function() {
use_collapse('notify_discord_use');
});
$('#notify_slack_use').change(function() {
use_collapse('notify_slack_use');
});
$('#notify_advaned_use').change(function() {
use_collapse('notify_advaned_use');
});
@@ -80,6 +93,11 @@ $("body").on('click', '#tmp_discord_test_btn', function(e){
globalSendCommand('notify_test', 'discord', $('#notify_discord_webhook').val(), $('#tmp_text_discord').val());
});
$("body").on('click', '#tmp_slack_test_btn', function(e){
e.preventDefault();
globalSendCommand('notify_test', 'slack', $('#notify_slack_webhook').val(), $('#tmp_text_slack').val());
});
$("body").on('click', '#tmp_advanced_test_btn', function(e){
e.preventDefault();
globalSendCommand('notify_test', 'advanced', $('#tmp_message_id').val(), $('#tmp_text_advanced').val());

View File

@@ -63,7 +63,7 @@ $("body").on('click', '#foreground_command_btn', function(e){
$("body").on('click', '#job_new_btn', function(e){
e.preventDefault();
globalSendCommandPage('job_new', $('#command').val(), null, null, null, function(ret){
globalSendCommandPage('job_new', $('#command').val(), null, null, function(ret){
request_list();
});
});
@@ -79,7 +79,7 @@ $("body").on('click', '#select_file_btn', function(e){
function request_list() {
globalSendCommandPage('job_list', null, null, null, null, function(ret){
globalSendCommandPage('job_list', null, null, null, function(ret){
make_list(ret.data);
});
}
@@ -92,18 +92,16 @@ function make_list(data) {
<th style="width:60%; text-align:center;">Command & arg & Desc</th> \
<th style="width:5%; text-align:center;">자동</th> \
<th colspan="2" style="width:20%; text-align:center;">스케쥴 상태</th> \
<th style="width:10%; text-align:center;">스케쥴</th> \
<th style="width:10%; text-align:center;">스케쥴주기</th> \
</tr></thead><tbody id="list">';
if (data.length == 0) str += '<tr><td colspan="6"><h4>작업이 없습니다.</h4></td></tr>';
for(i in data) {
console.log(data[i]);
//console.log(data[i]);
str += '<tr class="chover" style="cursor: pointer;" data-toggle="collapse" data-target="#collapse_' + i + '" aria-expanded="true" >';
str += '<td rowspan="2" scope="col" style="width:5%; text-align:center;">'+ (data[i].id) + '</td>';
// command
tmp = '';
tmp += text_color(data[i].command, 'blue') + '<br>';
@@ -123,7 +121,7 @@ function make_list(data) {
tmp1 = "시작시 한번 실행";
} else if (data[i].schedule_mode == 'scheduler') {
tmp1 = "스케쥴링";
tmp2 = '<input id="use_checkbox|'+data[i].id+'" type="checkbox" data-id='+data[i].id+' data-toggle="toggle" data-on="On" data-off="Off" data-onstyle="info" data-offstyle="danger" data-size="small" ' + ((data[i].scheduler_is_include) ? 'checked' : '') + '>';
tmp2 = '<input id="use_checkbox|'+data[i].id+'" type="checkbox" data-id='+data[i].id+' data-toggle="toggle" data-on="On" data-off="Off" data-onstyle="danger" data-offstyle="info" data-size="small" ' + ((data[i].scheduler_is_include) ? 'checked' : '') + '>';
if (data[i].scheduler_is_include) {
tmp2 += (data[i].scheduler_is_running) ? "<br>실행중" : "<br>대기중";
}
@@ -197,7 +195,7 @@ $("body").on('click', '#job_save_btn', function(e){
}
var formData = getFormdata('#item_setting');
globalSendCommandPage('job_save', formData, null, null, null, function(ret){
globalSendCommandPage('job_save', formData, null, null, function(ret){
if (ret.ret == 'success') {
$('#job_modal').modal('hide');
request_list();
@@ -216,7 +214,7 @@ $("body").on('click', '#job_remove_btn', function(e){
});
function remove_job(job_id) {
globalSendCommandPage('job_remove', job_id, null, null, null, function(ret){
globalSendCommandPage('job_remove', job_id, null, null, function(ret){
if (ret.ret == 'success') {
$('#job_modal').modal('hide');
request_list();
@@ -267,14 +265,14 @@ $("body").on('click', '#job_fore_execute_btn', function(e){
$("body").on('click', '#job_back_execute_btn', function(e){
e.preventDefault();
globalSendCommandPage('job_back_execute', $(this).data('id'), null, null, null, function(e) {
globalSendCommandPage('job_back_execute', $(this).data('id'), null, null, function(e) {
request_list();
});
});
$("body").on('click', '#job_log_btn', function(e){
e.preventDefault();
globalSendCommandPage('job_log', $(this).data('id'), null, null, null, function(data){
globalSendCommandPage('job_log', $(this).data('id'), null, null, function(data){
if (data.ret == 'success') {
redirect = '/system/all_log/list';
$.redirectPost(redirect, {filename: data.filename});
@@ -293,14 +291,14 @@ $("body").on('click', '#job_cmd_input_btn', function(e){
$("body").on('change', 'input[id^="use_checkbox|"]', function(e){
e.preventDefault();
globalSendCommandPage('task_sched', $(this).data('id'), $(this).prop('checked'), null, null, function(e) {
globalSendCommandPage('task_sched', $(this).data('id'), $(this).prop('checked'), null, function(e) {
request_list();
});
});
$("body").on('click', '#job_process_stop_btn', function(e){
e.preventDefault();
globalSendCommandPage('job_process_stop', $(this).data('id'), null, null, null, function(e) {
globalSendCommandPage('job_process_stop', $(this).data('id'), null, null, function(e) {
request_list();
});
});

View File

@@ -16,7 +16,7 @@ $(document).ready(function(){
});
function refresh() {
globalSendCommandPage('get_freeze', null, null, null, null, function(ret){
globalSendCommandPage('get_freeze', null, null, null, function(ret){
make_list(ret.data);
});
}

View File

@@ -1,5 +1,6 @@
from framework import logger
from .fp_entity_ktv import EntityKtv
from .modal_command import ToolModalCommand
from .notify import ToolNotify
from .util import ToolUtil

391
lib/tool/fp_entity_ktv.py Normal file
View File

@@ -0,0 +1,391 @@
import re
import time
import traceback
from datetime import datetime
from support import SupportFile, SupportString
from . import logger
EXTENSION = 'mp4|avi|mkv|ts|wmv|m2ts|smi|srt|ass|m4v|flv|asf|mpg|ogm'
REGEXS = [
r'^(?P<name>.*?)\.([sS](?P<sno>\d+))?[eE](?P<no>\d+)(\-E\d{1,4})?\.?(?P<a>.*?\.)?(?P<date>\d{6})\.(?P<etc>.*?)((?P<quality>\d+)[p|P])?(\-?(?P<release>.*?))?(\.(.*?))?$',
r'^(?P<name>.*?)\s([sS](?P<sno>\d+))?[eE](?P<no>\d+)(\-E\d{1,4})?\.?(END\.)?(?P<date>\d{6})\.(?P<etc>.*?)(?P<quality>\d+)[p|P](?P<more>\..*?)(?P<ext>\.[\w|\d]{3})$',
r'^(?P<name>.*?)\.([sS](?P<sno>\d+))?(E(?P<no>\d+)\.?)?(END\.)?(?P<date>\d{6})\.(?P<etc>.*?)(?P<quality>\d+)[p|P](\-?(?P<release>.*?))?(\.(.*?))?$',
r'^(?P<name>.*?)([sS](?P<sno>\d+))?[eE](?P<no>\d+)', # 외국 릴
r'^(?P<name>.*?)\.(Series\.(?P<sno>\d+)\.)?(?P<no>\d+)of', # 외국 릴
r'^(?P<name>.*?)[\s\(](?P<no>\d+)[회화]',
]
#합본처리 제외
#_REGEX_FILENAME_RENAME = r'(?P<title>.*?)[\s\.]E?(?P<no>\d{1,2})[\-\~\s\.]?E?\d{1,2}'
class EntityKtv(object):
meta_cache = {}
def __init__(self, filename, dirname=None, meta=False, is_title=False, config=None):
self.data = {
'filename' : {
'original' : filename,
'dirname' : dirname,
'is_matched' : False,
'match_index' : -1,
'name' : '',
'original_name': '',
},
'meta' : {
'find':False,
},
'process_info' : {
'rebuild':'',
'status':''
}
}
if is_title == False:
self.analyze(config=config)
self.data['filename']['original_name'] = self.data['filename']['name']
if self.data['filename']['name'] != '' and config is not None:
rule = config.get('검색어 변경', None)
if rule is not None:
self.change_name(rule)
else:
self.data['filename']['name'] = filename
self.data['filename']['is_matched'] = True
self.data['filename']['match_index'] = -1
self.data['filename']['date'] = ''
search_try = False
if meta and self.data['filename']['is_matched']:
if self.data['filename']['match_index'] in [3, 4]:
info = SupportString.language_info(self.data['filename']['name'])
if info[0] == 0:
search_try = True
self.find_meta_tmdb()
if search_try == False:
self.find_meta()
if self.data['meta']['find']:
self.find_meta_season()
try:
#logger.warning(f"찾은 메타 : {self.data['meta']['info']['title']} {self.data['meta']['info']['code']}")
if self.data['filename']['date'] == '':
self.data['process_info']['status'] = 'no_date'
else:
self.check_episode_no()
except Exception as e:
logger.debug(f"Exception:{str(e)}")
logger.debug(traceback.format_exc())
def analyze(self, config=None):
def get(md, field):
if field in md and md[field] is not None:
return md[field]
return ''
for idx, regex in enumerate(REGEXS):
match = re.compile(regex).match(self.data['filename']['original'])
if not match:
continue
md = match.groupdict()
self.data['filename']['is_matched'] = True
self.data['filename']['match_index'] = idx
self.data['filename']['name'] = get(md, 'name').replace('.', ' ').strip()
tmp = get(md, 'sno')
self.data['filename']['sno'] = int(tmp) if tmp != '' else 1
tmp = get(md, 'no')
try:
self.data['filename']['no'] = int(tmp) if tmp != '' else -1
if self.data['filename']['no'] == 0:
raise Exception('0')
except:
self.data['process_info']['rebuild'] += 'remove_episode'
self.data['filename']['no'] = -1
self.data['filename']['date'] = get(md, 'date')
self.data['filename']['etc'] = get(md, 'etc')
self.data['filename']['quality'] = get(md, 'quality')
self.data['filename']['release'] = get(md, 'release')
self.data['filename']['more'] = get(md, 'more')
self.data['filename']['day_delta'] = 0
if self.data['filename']['date'] != '':
today = datetime.now()
try:
tmp = str(self.data['filename']['date'])
if tmp[0] in ['8', '9']:
tmp = '19' + tmp
else:
tmp = '20' + tmp
max_date = datetime.strptime(tmp, '%Y%m%d')
except:
max_date = today
self.data['filename']['day_delta'] = (today - max_date).days
#logger.warning(d(self.data['filename']))
break
if config is not None:
rule = config.get('에피소드 번호 삭제 목록', [])
if self.data['filename']['name'] in rule:
self.data['process_info']['rebuild'] += 'remove_episode_by_rule'
self.data['filename']['no'] = -1
def change_name(self, rules):
name = self.data['filename']['name']
for rule in rules:
try:
name = re.sub(rule['source'], rule['target'], name, flags=re.I).strip()
except Exception as e:
logger.error(f"Exception:{e}")
logger.error(traceback.format_exc())
self.data['filename']['name'] = name
def check_episode_no(self):
if self.data['filename']['no'] > 0 and self.data['filename']['no'] in self.data['meta']['info']['extra_info']['episodes']:
#logger.warning(f"에피소드 정보 있음")
#logger.warning(self.data['meta']['info']['extra_info']['episodes'][self.data['filename']['no']])
tmp = self.data['meta']['info']['extra_info']['episodes'][self.data['filename']['no']]
# daum만 체크
if 'daum' in tmp:
value = tmp['daum']
tmp2 = value['premiered']
if self.data['filename']['date'] == tmp2.replace('-', '')[2:]:
self.data['process_info']['status'] = 'number_and_date_match'
self.data['process_info']['episode'] = value
self.data['process_info']['episode']['no'] = self.data['filename']['no']
return
else:
#하루차이는 매칭시킴
if abs(int(self.data['filename']['date']) - int(tmp2.replace('-', '')[2:])) in [1, 70, 71, 72, 73, 8870]:
self.data['process_info']['status'] = 'number_and_date_match'
self.data['process_info']['rebuild'] += 'change_date'
self.data['process_info']['change_date'] = tmp2.replace('-', '')[2:]
self.data['process_info']['episode'] = value
self.data['process_info']['episode']['no'] = self.data['filename']['no']
return
# 맞는 에피소드 몾찾음
if len(self.data['meta']['info']['extra_info']['episodes']) == 0:
# 메타검색은 했지만 에피소드 목록이 없음.
self.data['process_info']['status'] = 'meta_epi_empty'
return
# 방송일에 맞는 에피 번호 찾기
#logger.warning(f"에피소드 목록")
for epi_no, value in self.data['meta']['info']['extra_info']['episodes'].items():
if 'daum' in value:
site_info = value['daum']
tmp2 = site_info['premiered']
if self.data['filename']['date'] == tmp2.replace('-', '')[2:]:
self.data['process_info']['status'] = 'number_and_date_match'
self.data['process_info']['rebuild'] += 'change_epi_number'
self.data['process_info']['change_epi_number'] = epi_no
self.data['process_info']['episode'] = value['daum']
self.data['process_info']['episode']['no'] = epi_no
return
# 다음에서 몾찾았지만 티빙 웨이브에 있다면 그대로 유지해야함.
# 굳이 찾을 필요없이 릴리즈로 맞다고 넘김
# 근데 받을때는 에피번호가 없고 나중에 메타가 생기는 경우가 잇는 것 같음
if self.data['filename']['no'] != -1:
if self.data['filename']['release'] in ['ST', 'SW', 'SWQ', 'STQ', 'ODK']:
self.data['process_info']['status'] = 'number_and_date_match_by_release'
return
else:
for epi_no, value in self.data['meta']['info']['extra_info']['episodes'].items():
for site, site_info in value.items():
if site == 'daum':
continue
tmp2 = site_info['premiered']
if self.data['filename']['date'] == tmp2.replace('-', '')[2:]:
self.data['process_info']['status'] = 'number_and_date_match_ott'
self.data['process_info']['rebuild'] += 'change_epi_number'
self.data['process_info']['change_epi_number'] = epi_no
self.data['process_info']['episode'] = site_info
self.data['process_info']['episode']['no'] = epi_no
return
#logger.error("에피소드 목록이 있지만 맞는 메타를 찾지 못함")
#logger.error(f"에피소드 번호 {epi_no}")
#logger.error(f"에피소드 번호 {self.data['filename']['original']}")
#logger.warning(d(self.data['meta']['info']['extra_info']['episodes']))
#logger.debug("티빙, 웨이브에서 찾음")
if self.data['filename']['no'] > 0 and self.data['filename']['no'] in self.data['meta']['info']['extra_info']['episodes']:
#logger.warning(f"에피소드 정보 있음 22")
#logger.warning(self.data['meta']['info']['extra_info']['episodes'][self.data['filename']['no']])
tmp = self.data['meta']['info']['extra_info']['episodes'][self.data['filename']['no']]
# daum만 체크
for site, value in tmp.items():
if site == 'daum':
continue
tmp2 = value['premiered']
if self.data['filename']['date'] == tmp2.replace('-', '')[2:]:
self.data['process_info']['status'] = 'number_and_date_match'
self.data['process_info']['episode'] = value
self.data['process_info']['episode']['no'] = self.data['filename']['no']
return
else:
#하루차이는 매칭시킴
if abs(int(self.data['filename']['date']) - int(tmp2.replace('-', '')[2:])) in [1, 70, 71, 72, 73, 8870]:
self.data['process_info']['status'] = 'number_and_date_match'
self.data['process_info']['rebuild'] += 'change_date'
self.data['process_info']['change_date'] = tmp2.replace('-', '')[2:]
self.data['process_info']['episode'] = value
self.data['process_info']['episode']['no'] = self.data['filename']['no']
return
for epi_no, value in self.data['meta']['info']['extra_info']['episodes'].items():
if epi_no == 0:
continue
for site, site_info in value.items():
if site == 'daum':
continue
tmp2 = site_info['premiered']
if self.data['filename']['date'] == tmp2.replace('-', '')[2:]:
#logger.warning(f"2222 다음에서 새로운 에피소드 번호 찾음 : {epi_no}")
#logger.warning(d(site_info))
self.data['process_info']['status'] = 'number_and_date_match'
self.data['process_info']['rebuild'] += 'change_epi_number'
self.data['process_info']['change_epi_number'] = epi_no
self.data['process_info']['episode'] = site_info
self.data['process_info']['episode']['no'] = epi_no
return
if epi_no < self.data['filename']['no']:
self.data['process_info']['status'] = 'meta_epi_not_find'
self.data['process_info']['status'] = 'meta_epi_not_find'
#for tmp in self.data['meta']['info']['extra_info']['episode']:
# logger.debug((tmp))
def find_meta(self):
from support_site import SiteDaumTv, SiteTvingTv, SiteWavveTv
module_map = [('daum', SiteDaumTv), ('tving',SiteTvingTv), ('wavve',SiteWavveTv)]
#if self.data['filename']['name'] in EntityKtv.meta_cache:
# self.data['meta'] = EntityKtv.meta_cache[self.data['filename']['name']]
# return
#module_list = [SiteDaumTv, SiteTvingTv, SiteWavveTv]
#module_list = [SiteDaumTv]
for site, site_class in module_map:
try:
if self.data['filename']['name'] in EntityKtv.meta_cache and site in EntityKtv.meta_cache[self.data['filename']['name']]:
self.data['meta'] = EntityKtv.meta_cache[self.data['filename']['name']][site]
# 없는 것도 저장하여 중복검색 방지
if self.data['meta']['find']:
return
site_data = site_class.search(self.data['filename']['name'])
#logger.warning(f"{site} {d(site_data)}")
if site_data['ret'] == 'success':
if site == 'daum':
self.data['meta']['search'] = site_data['data']
self.data['meta']['info'] = site_class.info(self.data['meta']['search']['code'], self.data['meta']['search']['title'])['data']
# Daum이 미국드라마, 일본드라마 등의 외국 드라마 장르이면서 ST SW 릴이면 Daum보다는 OTT 메타를 사용하도록 함
if self.data['meta']['info']['genre'][0] != '드라마' and self.data['meta']['info']['genre'][0].find('드라마') != -1 and self.data['filename'].get('release', '') in ['ST', 'SW']:
continue
SiteTvingTv.apply_tv_by_search(self.data['meta']['info'], force_search_title=self.data['filename']['name'])
SiteWavveTv.apply_tv_by_search(self.data['meta']['info'], force_search_title=self.data['filename']['name'])
if self.data['meta']['info']['episode'] == -1:
self.data['meta']['info']['episode'] = len(self.data['meta']['info']['extra_info']['episodes'].keys())
self.data['meta']['find'] = True
else:
if len(site_data['data']) > 0 and site_data['data'][0]['score'] > 90:
self.data['meta']['search'] = site_data['data'][0]
self.data['meta']['info'] = site_class.info(self.data['meta']['search']['code'])['data']
self.data['meta']['find'] = True
if self.data['meta']['find']:
if len(self.data['meta']['info']['genre']) == 0:
self.data['meta']['info']['genre'].append('기타')
if self.data['filename']['name'] not in EntityKtv.meta_cache:
EntityKtv.meta_cache[self.data['filename']['name']] = {}
EntityKtv.meta_cache[self.data['filename']['name']][site] = self.data['meta']
return
else:
if self.data['filename']['name'] not in EntityKtv.meta_cache:
EntityKtv.meta_cache[self.data['filename']['name']] = {}
EntityKtv.meta_cache[self.data['filename']['name']][site] = self.data['meta']
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
def get_newfilename(self):
if self.data['filename']['match_index'] == 2:
self.data['process_info']['rebuild'] += f"match_{self.data['filename']['match_index']}"
if self.data['process_info']['rebuild'] in ['', 'match_2', 'meta_epi_not_find', 'match_3']:
return self.data['filename']['original']
elif self.data['process_info']['rebuild'] == 'remove_episode' or self.data['process_info']['rebuild'].find('remove_episode_by_rule') != -1:
return re.sub('\.[eE].*?\.', '.', self.data['filename']['original'])
elif self.data['process_info']['rebuild'] == 'remove_episodechange_epi_number' and self.data['process_info']['change_epi_number'] == 0:
return re.sub('\.[eE].*?\.', '.', self.data['filename']['original'])
elif self.data['process_info']['rebuild'] == 'change_epi_number':
return re.sub('\.[eE].*?\.', f".E{str(self.data['process_info']['change_epi_number']).zfill(2)}.", self.data['filename']['original'])
elif self.data['process_info']['rebuild'] == 'change_epi_numbermatch_2':
# 날짜만 있는 원본 에피소드 삽입
return self.data['filename']['original'].replace(f".{self.data['filename']['date']}.", f".E{str(self.data['process_info']['change_epi_number']).zfill(2)}.{self.data['filename']['date']}.")
elif self.data['process_info']['rebuild'] == 'change_date':
return self.data['filename']['original'].replace(f".{self.data['filename']['date']}.", f".{self.data['process_info']['change_date']}.")
time.sleep(10)
else:
pass
def find_meta_tmdb(self):
from support_site import SiteTmdbFtv
module_map = [('tmdb', SiteTmdbFtv)]
for site, site_class in module_map:
try:
if self.data['filename']['name'] in EntityKtv.meta_cache and site in EntityKtv.meta_cache[self.data['filename']['name']]:
self.data['meta'] = EntityKtv.meta_cache[self.data['filename']['name']][site]
# 없는 것도 저장하여 중복검색 방지
if self.data['meta']['find']:
return
site_data = site_class.search(self.data['filename']['name'])
#logger.warning(f"{site} {d(site_data)}")
if site_data['ret'] == 'success':
if len(site_data['data']) > 0 and site_data['data'][0]['score'] >= 80:
self.data['filename']['name'] = site_data['data'][0]['title']
self.find_meta()
if self.data['meta']['find'] == False:
self.data['process_info']['status'] = 'ftv'
self.data['process_info']['ftv_title'] = SupportFile.text_for_filename(site_data['data'][0]['title'])
self.data['process_info']['ftv_year'] = site_data['data'][0]['year']
return
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
def find_meta_season(self):
if self.data['meta']['info']['code'][1] == 'D':
for idx, season in enumerate(self.data['meta']['search']['series']):
if self.data['meta']['info']['code'] == season['code']:
self.data['meta']['info']['season'] = idx + 1
return
else:
self.data['meta']['info']['season'] = -1

View File

@@ -16,7 +16,7 @@ class ToolModalCommand(object):
__wait = None
__ss_process = None
__abort = None
__return_log = None
@classmethod
def start(cls, title, commands, clear=True, wait=False, show_modal=True):
@@ -33,7 +33,9 @@ class ToolModalCommand(object):
cls.__show_modal = show_modal
cls.__thread = None
cls.__abort = False
cls.__start()
cls.__return_log = ''
return cls.__start()
@classmethod
@@ -41,63 +43,66 @@ class ToolModalCommand(object):
try:
if cls.__show_modal:
if cls.__clear:
F.socketio.emit("command_modal_clear", None, namespace='/framework', broadcast=True)
F.socketio.emit("command_modal_clear", None, namespace='/framework')
cls.__thread = threading.Thread(target=cls.__execute_thread_function, args=())
cls.__thread.setDaemon(True)
cls.__thread.start()
if cls.__wait:
time.sleep(1)
cls.__thread.join()
except Exception as exception:
F.logger.error('Exception:%s', exception)
return cls.__return_log
except Exception as e:
F.logger.error(f"Exception:{str(e)}")
F.logger.error(traceback.format_exc())
@classmethod
def __execute_thread_function(cls):
try:
if cls.__show_modal:
F.socketio.emit("command_modal_show", cls.__title, namespace='/framework', broadcast=True)
F.socketio.emit("loading_hide", None, namespace='/framework', broadcast=True)
F.socketio.emit("command_modal_show", cls.__title, namespace='/framework')
F.socketio.emit("loading_hide", None, namespace='/framework')
for command in cls.__commands:
if cls.__abort:
return
if command[0] == 'msg':
if cls.__show_modal:
F.socketio.emit("command_modal_add_text", '%s\n\n' % command[1], namespace='/framework', broadcast=True)
F.socketio.emit("command_modal_add_text", '%s\n\n' % command[1], namespace='/framework')
elif command[0] == 'system':
if cls.__show_modal:
F.socketio.emit("command_modal_add_text", '$ %s\n\n' % command[1], namespace='/framework', broadcast=True)
F.socketio.emit("command_modal_add_text", '$ %s\n\n' % command[1], namespace='/framework')
os.system(command[1])
else:
#show_command = True
#if command[0] == 'hide':
# show_command = False
# command = command[1:]
cls.__ss_process = SupportSubprocess(command, stdout_callback=cls.process_callback)
cls.__ss_process = SupportSubprocess(command, stdout_callback=cls.process_callback, callback_line=False)
cls.__ss_process.start()
cls.__ss_process.process_close()
cls.__ss_process = None
time.sleep(1)
except Exception as exception:
if cls.__show_modal:
F.socketio.emit("command_modal_show", cls.__title, namespace='/framework', broadcast=True)
F.socketio.emit("command_modal_add_text", str(exception), namespace='/framework', broadcast=True)
F.socketio.emit("command_modal_add_text", str(traceback.format_exc()), namespace='/framework', broadcast=True)
F.socketio.emit("command_modal_show", cls.__title, namespace='/framework')
F.socketio.emit("command_modal_add_text", str(exception), namespace='/framework')
F.socketio.emit("command_modal_add_text", str(traceback.format_exc()), namespace='/framework')
@classmethod
def process_callback(cls, mode, text):
def process_callback(cls, call_id, mode, text):
#F.logger.warning(text)
if cls.__show_modal == False:
return
if mode == 'end':
F.socketio.emit("command_modal_add_text", "\n\n<<프로세스 종료>>", namespace='/framework', broadcast=True)
F.socketio.emit("command_modal_input_disable", "", namespace='/framework', broadcast=True)
F.socketio.emit("command_modal_add_text", "\n\n<<프로세스 종료>>", namespace='/framework')
F.socketio.emit("command_modal_input_disable", "", namespace='/framework')
elif mode == 'thread_end':
#F.socketio.emit("command_modal_add_text", "\n\n<<프로세스 종료>>", namespace='/framework', broadcast=True)
F.socketio.emit("command_modal_input_disable", "", namespace='/framework', broadcast=True)
#F.socketio.emit("command_modal_add_text", "\n\n<<프로세스 종료>>", namespace='/framework')
F.socketio.emit("command_modal_input_disable", "", namespace='/framework')
else:
F.socketio.emit("command_modal_add_text", text, namespace='/framework', broadcast=True)
if text != None:
cls.__return_log += text
F.socketio.emit("command_modal_add_text", text, namespace='/framework')
@classmethod
@@ -114,3 +119,7 @@ class ToolModalCommand(object):
if cls.__ss_process != None:
cls.__ss_process.input_command(cmd)
@classmethod
def send_message(cls, text):
F.socketio.emit("command_modal_add_text", '%s\n\n' % text, namespace='/framework')

View File

@@ -2,7 +2,7 @@ import traceback
from datetime import datetime
from framework import F
from support import SupportDiscord, SupportTelegram, SupportYaml
from support import SupportDiscord, SupportTelegram, SupportYaml, SupportSlack
from . import logger
@@ -18,6 +18,8 @@ class ToolNotify(object):
SupportTelegram.send_telegram_message(text, image_url=image_url, bot_token=F.SystemModelSetting.get('notify_telegram_token'), chat_id=F.SystemModelSetting.get('notify_telegram_chat_id'))
if F.SystemModelSetting.get_bool('notify_discord_use'):
SupportDiscord.send_discord_message(text, image_url=image_url, webhook_url=F.SystemModelSetting.get('notify_discord_webhook'))
if F.SystemModelSetting.get_bool('notify_slack_use'):
SupportSlack.send_slack_message(text, image_url=image_url, webhook_url=F.SystemModelSetting.get('notify_slack_webhook'))
@classmethod
@@ -40,9 +42,13 @@ class ToolNotify(object):
elif item.get('type') == 'discord':
if item.get('webhook', '') == '':
continue
SupportDiscord.send_discord_message(text, webhook_url=item.get('webhook'))
SupportDiscord.send_discord_message(text, image_url=image_url, webhook_url=item.get('webhook'))
elif item.get('type') == 'slack':
if item.get('webhook', '') == '':
continue
SupportSlack.send_slack_message(text, image_url=image_url, webhook_url=item.get('webhook'))
return True
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False

View File

@@ -1,3 +1,5 @@
import traceback
from . import logger
@@ -20,4 +22,16 @@ class ToolUtil(object):
def make_path(cls, data):
from framework import F
return data.replace('{PATH_DATA}', F.config['path_data'])
@classmethod
def run_system_command_by_id(cls, command_id):
try:
from system.setup import P as PP
page_ins = PP.logic.get_module('tool').get_page('command')
thread = page_ins.execute_thread_start(command_id)
return thread
except Exception as e:
logger.error(f'Exception:{str(e)}')
logger.error(traceback.format_exc())

View File

@@ -1,6 +1,7 @@
import base64
from Crypto.Cipher import AES
from Crypto import Random
from Crypto.Cipher import AES
from framework import app, logger
BS = 16
@@ -13,8 +14,8 @@ class ToolAESCipher(object):
def encrypt(raw, mykey=None):
try:
Random.atfork()
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
raw = pad(raw)

View File

@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*-
import os
import traceback
import shutil
import traceback
from framework import app, celery, logger
class ToolShutil(object):
# run_in_celery=True 이미 celery안에서 실행된다. 바로 콜한다.
@staticmethod
@@ -15,8 +16,8 @@ class ToolShutil(object):
return result.get()
else:
return ToolShutil._move_task(source_path, target_path)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return ToolShutil._move_task(source_path, target_path)
@@ -28,8 +29,8 @@ class ToolShutil(object):
shutil.move(source_path, target_path)
logger.debug('_move_task end')
return True
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False
@@ -41,8 +42,8 @@ class ToolShutil(object):
return result.get()
else:
return ToolShutil._move_exist_remove_task(source_path, target_path)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return ToolShutil._move_exist_remove_task(source_path, target_path)
@@ -58,8 +59,8 @@ class ToolShutil(object):
shutil.move(source_path, target_path)
logger.debug('_move_exist_remove end')
return True
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False
@@ -71,8 +72,8 @@ class ToolShutil(object):
return result.get()
else:
return ToolShutil._copytree_task(source_path, target_path)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return ToolShutil._copytree_task(source_path, target_path)
@@ -82,8 +83,8 @@ class ToolShutil(object):
try:
shutil.copytree(source_path, target_path)
return True
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False
@@ -96,8 +97,8 @@ class ToolShutil(object):
return result.get()
else:
return ToolShutil._copy_task(source_path, target_path)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return ToolShutil._copy_task(source_path, target_path)
@@ -107,8 +108,8 @@ class ToolShutil(object):
try:
shutil.copy(source_path, target_path)
return True
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False
@@ -121,8 +122,8 @@ class ToolShutil(object):
return result.get()
else:
return ToolShutil._rmtree_task(source_path)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return ToolShutil._rmtree_task(source_path)
@@ -132,8 +133,8 @@ class ToolShutil(object):
try:
shutil.rmtree(source_path)
return True
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False
@@ -146,8 +147,8 @@ class ToolShutil(object):
return result.get()
else:
return ToolShutil._remove_task(remove_path)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return ToolShutil._remove_task(remove_path)
finally:
@@ -159,7 +160,7 @@ class ToolShutil(object):
try:
os.remove(remove_path)
return True
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False

View File

@@ -1,9 +1,17 @@
# -*- coding: utf-8 -*-
#########################################################
import os, sys, traceback, subprocess, json, platform
import json
import os
import platform
import subprocess
import sys
import traceback
from framework import app, logger, path_data
from ..support.base.subprocess import ToolSubprocess
class ToolFfmpeg(object):
@classmethod
@@ -15,7 +23,7 @@ class ToolFfmpeg(object):
logger.warning(' '.join(command))
ret = ToolSubprocess.execute_command_return(command, format='json')
return ret
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())

View File

@@ -24,8 +24,8 @@ class ToolBaseFile(object):
ofp.write(data)
ofp.close()
return True
except Exception as exception:
logger.debug('Exception:%s', exception)
except Exception as e:
logger.debug(f"Exception:{str(e)}")
logger.debug(traceback.format_exc())
return False
@@ -41,23 +41,7 @@ class ToolBaseFile(object):
total_size += os.path.getsize(fp)
return total_size
@classmethod
def file_move(cls, source_path, target_dir, target_filename):
try:
import shutil
import time
if os.path.exists(target_dir) == False:
os.makedirs(target_dir)
target_path = os.path.join(target_dir, target_filename)
if source_path != target_path:
if os.path.exists(target_path):
tmp = os.path.splitext(target_filename)
new_target_filename = f"{tmp[0]} {str(time.time()).split('.')[0]}{tmp[1]}"
target_path = os.path.join(target_dir, new_target_filename)
shutil.move(source_path, target_path)
except Exception as exception:
logger.debug('Exception:%s', exception)
logger.debug(traceback.format_exc())
@classmethod
@@ -81,8 +65,8 @@ class ToolBaseFile(object):
import shutil
shutil.rmtree(zip_path)
return zipfilepath
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return
@@ -120,8 +104,8 @@ class ToolBaseFile(object):
os.makedirs(os.path.dirname(filepath))
with open(filepath, "w", encoding='utf8') as json_file:
json.dump(data, json_file, indent=4, ensure_ascii=False)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@classmethod
@@ -130,8 +114,8 @@ class ToolBaseFile(object):
with open(filepath, "r", encoding='utf8') as json_file:
data = json.load(json_file)
return data
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@@ -142,8 +126,8 @@ class ToolBaseFile(object):
ofp = codecs.open(filename, 'w', encoding='utf8')
ofp.write(data)
ofp.close()
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@classmethod
@@ -153,8 +137,8 @@ class ToolBaseFile(object):
data = ifp.read()
ifp.close()
return data
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())

View File

@@ -4,12 +4,13 @@
import os
import traceback
from discord_webhook import DiscordWebhook, DiscordEmbed
from discord_webhook import DiscordEmbed, DiscordWebhook
from telepot2 import Bot, glance
from telepot2.loop import MessageLoop
from . import logger
class ToolBaseNotify(object):
@classmethod
@@ -46,8 +47,8 @@ class ToolBaseNotify(object):
tmp2 = tmp.split(',')
cls.send_telegram_message(text, image_url=image_url, bot_token=tmp2[0], chat_id=tmp2[1])
return True
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
#logger.debug('Chatid:%s', chat_id)
return False
@@ -66,8 +67,8 @@ class ToolBaseNotify(object):
continue
ret[tmp2[0].strip()] = [x.strip() for x in tmp2[1].split('|')]
return ret
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False
@@ -88,8 +89,8 @@ class ToolBaseNotify(object):
#discord = response.json()
#logger.debug(discord)
return True
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return False
@@ -114,8 +115,8 @@ class ToolBaseNotify(object):
#elif mime == 'video':
# bot.sendVideo(chat_id, text, disable_notification=disable_notification)
return True
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
logger.debug('Chatid:%s', chat_id)
return False

View File

@@ -25,8 +25,8 @@ class ToolRclone(object):
if ret is not None:
ret = list(sorted(ret, key=lambda k:k['Path']))
return ret
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@@ -38,8 +38,8 @@ class ToolRclone(object):
command += option
ret = ToolSubprocess.execute_command_return(command, format='json')
return ret
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@@ -53,8 +53,8 @@ class ToolRclone(object):
if ret is not None and (len(ret.split(' ')) > 1 or ret == ''):
ret = None
return ret
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@@ -66,8 +66,8 @@ class ToolRclone(object):
command += option
ret = ToolSubprocess.execute_command_return(command)
return ret
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@classmethod
@@ -78,8 +78,8 @@ class ToolRclone(object):
command += option
ret = ToolSubprocess.execute_command_return(command)
return ret
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@@ -94,8 +94,8 @@ class ToolRclone(object):
if 'token' in value and value['token'].startswith('{'):
value['token'] = json.loads(value['token'])
return ret
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@@ -112,8 +112,8 @@ class ToolRclone(object):
logger.debug(' '.join(command))
ret = ToolSubprocess.execute_command_return(command)
return ret
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@@ -124,8 +124,8 @@ class ToolRclone(object):
data = cls.config_list(rclone_path=rclone_path, config_path=config_path, option=option)
return data.get(remote_name, None)
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@@ -138,6 +138,6 @@ class ToolRclone(object):
logger.debug(' '.join(command))
ret = ToolSubprocess.execute_command_return(command)
return ret
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())

View File

@@ -19,8 +19,8 @@ class ToolUtil(object):
ofp = codecs.open(filepath, 'w', encoding='utf8')
ofp.write(data)
ofp.close()
except Exception as exception:
logger.debug('Exception:%s', exception)
except Exception as e:
logger.debug(f"Exception:{str(e)}")
logger.debug(traceback.format_exc())

View File

@@ -1,13 +1,15 @@
# -*- coding: utf-8 -*-
#########################################################
# python
import os, re
import traceback
import time
import threading
import os
import re
import shutil
import threading
import time
import traceback
from framework import app
from . import logger
EXTENSION = 'mp4|avi|mkv|ts|wmv|m2ts|smi|srt|ass|m4v|flv|asf|mpg|ogm'
@@ -85,11 +87,11 @@ class ToolExpandFileProcess(object):
except UnicodeDecodeError:
pass
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
except Exception as exception:
logger.error('Exception:%s', exception)
except Exception as e:
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
@@ -120,9 +122,9 @@ class ToolExpandFileProcess(object):
elif len(tmp2) == 2:
tmp = '%s-%scd%s%s' % (tmps[0], str(int(tmp2[0])).zfill(3), tmp2[1], ext)
return tmp
except Exception as exception:
except Exception as e:
logger.debug('filename : %s', filename)
logger.error('Exception:%s', exception)
logger.error(f"Exception:{str(e)}")
logger.error(traceback.format_exc())
return filename

Some files were not shown because too many files have changed in this diff Show More