mirror of
https://github.com/star-inc/yuuki.git
synced 2024-09-20 07:16:30 +08:00
Turn PEP8 branch to v6.6
This commit is contained in:
parent
6a63836de0
commit
452dcfc566
|
@ -79,4 +79,4 @@ Welcome to help us for improving and making `Yuuki` better!
|
|||
|
||||
Copyright of `logo.png` belongs to "[©川原 礫/ASCII Media Works/SAO Project](https://www.aniplex.co.jp)" and its artists.
|
||||
|
||||
> (c) 2020 Star Inc.
|
||||
> (c) 2021 Star Inc.
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
from .config import Config
|
||||
from .yuuki import Yuuki
|
||||
|
||||
__all__ = ['connection', 'data', 'mds.py', 'thread.py', 'Yuuki', 'Config']
|
|
@ -1,91 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
import os
|
||||
|
||||
import yaml
|
||||
|
||||
|
||||
class Config:
|
||||
"""
|
||||
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
!! DO NOT TOUCH DEFAULT SETTINGS !!
|
||||
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
||||
|
||||
Please config the value you want to set though `config.yaml`.
|
||||
It will overwrite these settings.
|
||||
|
||||
"""
|
||||
|
||||
connect_info = {
|
||||
"Host": "",
|
||||
"Command_Path": "",
|
||||
"LongPoll_path": "",
|
||||
}
|
||||
|
||||
connect_header = {
|
||||
"X-Line-Application": "",
|
||||
"X-Line-Access": "",
|
||||
"User-Agent": ""
|
||||
}
|
||||
|
||||
system_config = {
|
||||
"name": "Yuuki",
|
||||
"version": "v6.5.3-beta_RC0",
|
||||
"version_check": True,
|
||||
"project_url": "https://tinyurl.com/syb-yuuki",
|
||||
"man_page": "https://tinyurl.com/yuuki-manual",
|
||||
"privacy_page": "OpenSource - Licensed under MPL 2.0",
|
||||
"copyright": "(c)2020 Star Inc.",
|
||||
|
||||
"Seq": 0,
|
||||
"Admin": [],
|
||||
"Advanced": False,
|
||||
"WebAdmin": False, # Advanced Mode Required
|
||||
"MDS_Port": 2019,
|
||||
"WebAdmin_Port": 2020,
|
||||
"SecurityService": False,
|
||||
"Hour_KickLimit": 10,
|
||||
"Hour_CancelLimit": 10,
|
||||
"Default_Language": "en",
|
||||
"GroupMembers_Demand": 100,
|
||||
"helper_LINE_ACCESS_KEYs": [],
|
||||
}
|
||||
|
||||
def __init__(self, config_path="config.yaml"):
|
||||
assert os.path.isfile(config_path), "The config file, `config.yaml` was not found."
|
||||
with open(config_path, "r") as configfile:
|
||||
self.config = yaml.load(configfile, Loader=yaml.FullLoader)
|
||||
self._config_yuuki()
|
||||
|
||||
def _config_yuuki(self):
|
||||
assert self.config is not None, "Invalid config file"
|
||||
if "Yuuki" in self.config:
|
||||
for key in self.config["Yuuki"]:
|
||||
if key in self.system_config:
|
||||
self.system_config[key] = self.config["Yuuki"][key]
|
||||
return self._config_server()
|
||||
|
||||
def _config_server(self):
|
||||
if "Server" in self.config.get("LINE"):
|
||||
for key in self.config["LINE"]["Server"]:
|
||||
if key in self.connect_info:
|
||||
self.connect_info[key] = self.config["LINE"]["Server"][key]
|
||||
return self._config_account()
|
||||
|
||||
def _config_account(self):
|
||||
if "Account" in self.config.get("LINE"):
|
||||
for key in self.config["LINE"]["Account"]:
|
||||
if key in ["X-Line-Application", "User-Agent"]:
|
||||
self.config["LINE"]["Account"][key] = self.config["LINE"]["Account"][key].replace("\\t", "\t")
|
||||
if key in self.connect_header:
|
||||
self.connect_header[key] = self.config["LINE"]["Account"][key]
|
||||
|
||||
if "helper_LINE_ACCESS_KEYs" in self.config.get("LINE"):
|
||||
self.system_config["helper_LINE_ACCESS_KEYs"] = self.config["LINE"]["helper_LINE_ACCESS_KEYs"]
|
|
@ -1,77 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from thrift.protocol import TCompactProtocol
|
||||
from thrift.transport import THttpClient
|
||||
from yuuki_core.TalkService import Client, TalkException
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .config import Config
|
||||
|
||||
# NC HighSpeed Library
|
||||
try:
|
||||
from thrift.protocol import fastbinary
|
||||
except ImportError:
|
||||
print("[No fast_binary using]")
|
||||
|
||||
|
||||
class Connect:
|
||||
def __init__(self, configs: Config):
|
||||
|
||||
self.helper = {}
|
||||
|
||||
self.host = configs.connect_info["Host"]
|
||||
self.com_path = configs.connect_info["Command_Path"]
|
||||
self.poll_path = configs.connect_info["LongPoll_path"]
|
||||
|
||||
self.con_header = configs.connect_header
|
||||
|
||||
def connect(self, listen_timeout=600000):
|
||||
transport = THttpClient.THttpClient(self.host + self.com_path)
|
||||
transport_in = THttpClient.THttpClient(self.host + self.poll_path)
|
||||
|
||||
transport_in.setTimeout(listen_timeout)
|
||||
|
||||
transport.setCustomHeaders(self.con_header)
|
||||
transport_in.setCustomHeaders(self.con_header)
|
||||
|
||||
protocol = TCompactProtocol.TCompactProtocol(transport)
|
||||
protocol_in = TCompactProtocol.TCompactProtocol(transport_in)
|
||||
|
||||
client = Client(protocol)
|
||||
listen = Client(protocol_in)
|
||||
|
||||
transport.open()
|
||||
transport_in.open()
|
||||
|
||||
return client, listen
|
||||
|
||||
def helper_connect(self, auth_token):
|
||||
helper_connect_header = self.con_header.copy()
|
||||
helper_connect_header["X-Line-Access"] = auth_token
|
||||
|
||||
transport = THttpClient.THttpClient(self.host + self.com_path)
|
||||
transport.setCustomHeaders(helper_connect_header)
|
||||
protocol = TCompactProtocol.TCompactProtocol(transport)
|
||||
client = Client(protocol)
|
||||
transport.open()
|
||||
|
||||
try:
|
||||
profile = client.getProfile()
|
||||
self.helper[profile.mid] = {
|
||||
"client": client,
|
||||
"profile": profile
|
||||
}
|
||||
return True
|
||||
|
||||
except TalkException:
|
||||
print("Error:\n%s\nNot Acceptable\n" % (auth_token,))
|
258
libs/data.py
258
libs/data.py
|
@ -1,258 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
|
||||
from tornado.httpclient import HTTPClient, HTTPRequest
|
||||
from yuuki_core.ttypes import OpType
|
||||
|
||||
from .mds import PythonMDS
|
||||
from .thread import Multiprocess
|
||||
from .thread import YuukiThread
|
||||
|
||||
|
||||
class Data:
|
||||
# Data Struct Define
|
||||
|
||||
Data = {}
|
||||
|
||||
DataType = {
|
||||
"Global": {
|
||||
"LastResetLimitTime": None,
|
||||
},
|
||||
"Group": {},
|
||||
"LimitInfo": {},
|
||||
"BlackList": []
|
||||
}
|
||||
|
||||
GroupType = {
|
||||
"SEGroup": None,
|
||||
"Ext_Admin": [],
|
||||
"GroupTicket": {}
|
||||
}
|
||||
|
||||
LimitType = {
|
||||
"KickLimit": {},
|
||||
"CancelLimit": {}
|
||||
}
|
||||
|
||||
SEGrouptype = {
|
||||
OpType.NOTIFIED_UPDATE_GROUP: False,
|
||||
OpType.NOTIFIED_INVITE_INTO_GROUP: False,
|
||||
OpType.NOTIFIED_ACCEPT_GROUP_INVITATION: False,
|
||||
OpType.NOTIFIED_KICKOUT_FROM_GROUP: False
|
||||
}
|
||||
|
||||
DataPath = "data/"
|
||||
DataName = "{}.json"
|
||||
|
||||
# Log Struct Define
|
||||
|
||||
LogType = {
|
||||
"JoinGroup": "<li>%s: %s(%s) -> Inviter: %s</li>",
|
||||
"KickEvent": "<li>%s: %s(%s) -(%s)> Kicker: %s | Kicked: %s | Status: %s</li>",
|
||||
"CancelEvent": "<li>%s: %s(%s) -(%s)> Inviter: %s | Canceled: %s</li>",
|
||||
"BlackList": "<li>%s: %s(%s)</li>"
|
||||
}
|
||||
|
||||
LogPath = "logs/"
|
||||
LogName = "{}.html"
|
||||
|
||||
initHeader = "<title>{} - SYB</title>" \
|
||||
"<meta charset='utf-8' />"
|
||||
|
||||
def __init__(self, threading: bool, mds_port: int):
|
||||
self.threading = threading
|
||||
self.mds_port = mds_port
|
||||
self.ThreadControl = YuukiThread()
|
||||
self.MdsThreadControl = Multiprocess()
|
||||
self._data_initialize()
|
||||
|
||||
def _data_initialize(self):
|
||||
if not os.path.isdir(self.DataPath):
|
||||
os.mkdir(self.DataPath)
|
||||
|
||||
for data_type in self.DataType:
|
||||
name = self.DataPath + self.DataName.format(data_type)
|
||||
|
||||
if not os.path.isfile(name):
|
||||
with open(name, "w") as f:
|
||||
self.Data[data_type] = self.DataType[data_type]
|
||||
json.dump(self.Data[data_type], f)
|
||||
else:
|
||||
with open(name, "r") as f:
|
||||
try:
|
||||
self.Data[data_type] = json.load(f)
|
||||
except ValueError:
|
||||
assert "{}\nJson Test Error".format(name)
|
||||
|
||||
return self._mds_initialize()
|
||||
|
||||
def _mds_initialize(self):
|
||||
if self.threading:
|
||||
mds = PythonMDS(self.mds_port)
|
||||
self.mdsHost = "http://localhost:{}/".format(self.mds_port)
|
||||
self.mdsCode = "{}.{}".format(random.random(), time.time())
|
||||
self.MdsThreadControl.add(mds.mds_listen, (self.mdsCode,))
|
||||
|
||||
# MDS Sync
|
||||
|
||||
time.sleep(1)
|
||||
self.mds_shake("SYC", self.Data)
|
||||
return self._log_initialize()
|
||||
|
||||
def _log_initialize(self):
|
||||
if not os.path.isdir(self.LogPath):
|
||||
os.mkdir(self.LogPath)
|
||||
|
||||
for Type in self.LogType:
|
||||
name = self.LogPath + self.LogName.format(Type)
|
||||
if not os.path.isfile(name):
|
||||
with open(name, "w") as f:
|
||||
f.write(self.initHeader.format(Type))
|
||||
|
||||
def thread_append(self, function_, args):
|
||||
if self.threading:
|
||||
self.ThreadControl.lock.acquire()
|
||||
self.ThreadControl.add(function_, args)
|
||||
self.ThreadControl.lock.release()
|
||||
else:
|
||||
function_(*args)
|
||||
|
||||
def mds_shake(self, do, path, data=None):
|
||||
status = 0
|
||||
if self.threading:
|
||||
http_client = HTTPClient()
|
||||
http_request = HTTPRequest(
|
||||
url=self.mdsHost,
|
||||
method="POST",
|
||||
body=json.dumps({
|
||||
"code": self.mdsCode,
|
||||
"do": do,
|
||||
"path": path,
|
||||
"data": data
|
||||
})
|
||||
)
|
||||
response = http_client.fetch(http_request)
|
||||
result = json.loads(response.body)
|
||||
if "status" in result:
|
||||
status = result["status"]
|
||||
assert status == 200, f"mds - ERROR {status}\n{do} on {path}"
|
||||
return result
|
||||
else:
|
||||
status = {"status": status}
|
||||
return json.dumps(status)
|
||||
|
||||
def _local_query(self, query_data):
|
||||
if type(query_data) is list:
|
||||
result = self.Data
|
||||
query_len = len(query_data) - 1
|
||||
for count, key in enumerate(query_data):
|
||||
if key in result:
|
||||
if count < query_len:
|
||||
if type(result.get(key)) is not dict:
|
||||
return 1
|
||||
result = result.get(key)
|
||||
else:
|
||||
return 2
|
||||
return result
|
||||
return 0
|
||||
|
||||
def _local_update(self, path, data):
|
||||
result = self._local_query(path)
|
||||
if not str(result).isnumeric():
|
||||
result.update(data)
|
||||
return False
|
||||
|
||||
def file(self, type_, mode_, format_):
|
||||
if format_ == "Data":
|
||||
return open(self.DataPath + self.DataName.format(type_), mode_)
|
||||
elif format_ == "Log":
|
||||
return open(self.LogPath + self.LogName.format(type_), mode_)
|
||||
|
||||
def sync_data(self):
|
||||
if self.threading:
|
||||
self.Data = self.get_data([])
|
||||
for Type in self.DataType:
|
||||
with self.file(Type, "w", "Data") as f:
|
||||
json.dump(self.Data[Type], f)
|
||||
return self.get_data(["Global", "Power"])
|
||||
|
||||
def update_data(self, path, data):
|
||||
if self.threading:
|
||||
self.thread_append(self._update_data, (path, data))
|
||||
else:
|
||||
self._update_data(path, data)
|
||||
|
||||
def _update_data(self, path, data):
|
||||
assert path and type(path) is list, "Empty path - update_data"
|
||||
if len(path) == 1:
|
||||
origin_data = self.get_data([])
|
||||
assert type(origin_data) is dict, "Error origin data type (1) - update_data"
|
||||
origin = origin_data.copy()
|
||||
origin[path[0]] = data
|
||||
path = []
|
||||
else:
|
||||
origin_data = self.get_data(path[:-1])
|
||||
assert type(origin_data) is dict, "Error origin data type (2) - update_data"
|
||||
origin = origin_data.copy()
|
||||
origin[path[-1]] = data
|
||||
path = path[:-1]
|
||||
assert type(origin) is dict, "Error request data type - update_data"
|
||||
if self.threading:
|
||||
self.mds_shake("UPT", path, origin)
|
||||
else:
|
||||
self._local_update(path, origin)
|
||||
|
||||
def update_log(self, type_, data):
|
||||
if self.threading:
|
||||
self.thread_append(self._update_log, (type_, data))
|
||||
else:
|
||||
self._update_log(type_, data)
|
||||
|
||||
def _update_log(self, type_, data):
|
||||
with self.file(type_, "a", "Log") as f:
|
||||
f.write(self.LogType[type_] % data)
|
||||
|
||||
@staticmethod
|
||||
def get_time(time_format="%b %d %Y %H:%M:%S %Z"):
|
||||
time_ = time.localtime(time.time())
|
||||
return time.strftime(time_format, time_)
|
||||
|
||||
def get_data(self, path):
|
||||
if self.threading:
|
||||
return self.mds_shake("GET", path).get("data")
|
||||
else:
|
||||
return self._local_query(path)
|
||||
|
||||
def get_group(self, group_id):
|
||||
group_ids = self.get_data(["Group"])
|
||||
if group_id not in group_ids:
|
||||
self.update_data(["Group", group_ids], self.GroupType)
|
||||
return self.GroupType
|
||||
return group_ids.get(group_ids)
|
||||
|
||||
def get_se_group(self, group_id):
|
||||
group = self.get_group(group_id)
|
||||
mode = group.get("SEGroup")
|
||||
if mode is None:
|
||||
return None
|
||||
mode_ = {}
|
||||
for mode__ in mode:
|
||||
mode_num = int(mode__)
|
||||
mode_[mode_num] = mode[mode__]
|
||||
return mode_
|
||||
|
||||
def trigger_limit(self, limit_type, user_id):
|
||||
if self.threading:
|
||||
self.mds_shake("YLD", limit_type, user_id)
|
||||
else:
|
||||
self.Data["LimitInfo"][limit_type][user_id] -= 1
|
|
@ -1,15 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
|
||||
from .callback import YuukiCallback
|
||||
from .command import YuukiCommand
|
||||
from .join_group import YuukiJoinGroup
|
||||
from .security import YuukiSecurity
|
||||
|
||||
__all__ = ["YuukiCommand", "YuukiJoinGroup", "YuukiSecurity", "YuukiCallback"]
|
|
@ -1,53 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from yuuki_core.ttypes import ContentType
|
||||
|
||||
from ..tools import DynamicTools
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..yuuki import Yuuki
|
||||
|
||||
|
||||
class Callback:
|
||||
def __init__(self, handler: Yuuki):
|
||||
"""
|
||||
Event Type:
|
||||
SEND_MESSAGE(25)
|
||||
"""
|
||||
self.Yuuki = handler
|
||||
self.DynamicTools = DynamicTools(self.Yuuki)
|
||||
|
||||
def _shutdown(self, operation):
|
||||
self.Yuuki.Thread_Control.add(self._shutdown_reply, (operation,))
|
||||
self.Yuuki.exit()
|
||||
|
||||
def _shutdown_reply(self, operation):
|
||||
time.sleep(1)
|
||||
self.DynamicTools.send_text(
|
||||
operation.message.to,
|
||||
self.Yuuki.get_text("Exit.")
|
||||
)
|
||||
|
||||
def _text(self, operation):
|
||||
actions = {
|
||||
'[Yuuki] Remote Shutdown': self._shutdown,
|
||||
}
|
||||
if operation.message.text in actions:
|
||||
function_ = actions[operation.message.text]
|
||||
if callable(function_):
|
||||
function_(operation)
|
||||
|
||||
def action(self, operation):
|
||||
if operation.message.contentType == ContentType.NONE:
|
||||
self._text(operation)
|
|
@ -1,417 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from yuuki_core.ttypes import MIDType, ContentType, OpType
|
||||
|
||||
from ..tools import StaticTools, DynamicTools
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..yuuki import Yuuki
|
||||
|
||||
|
||||
class Command:
|
||||
def __init__(self, handler: Yuuki):
|
||||
"""
|
||||
Event Type:
|
||||
RECEIVE_MESSAGE (26)
|
||||
"""
|
||||
self.Yuuki = handler
|
||||
self.DynamicTools = DynamicTools(self.Yuuki)
|
||||
|
||||
def _help(self, operation):
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text(
|
||||
"%s\n\t%s\n\nCommands Info:\n%s\n\nPrivacy:\n%s\n\nMore Information:\n%s\n\n%s") %
|
||||
(
|
||||
self.Yuuki.configs["name"],
|
||||
self.Yuuki.configs["version"],
|
||||
self.Yuuki.configs["man_page"],
|
||||
self.Yuuki.configs["privacy_page"],
|
||||
self.Yuuki.configs["project_url"],
|
||||
self.Yuuki.configs["copyright"],
|
||||
)
|
||||
)
|
||||
|
||||
def _version(self, operation):
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.configs["version"]
|
||||
)
|
||||
|
||||
def _user_id(self, operation):
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("LINE System UserID:\n") + operation.message.from_
|
||||
)
|
||||
|
||||
def _get_all_helper(self, operation):
|
||||
if operation.message.toType == MIDType.GROUP:
|
||||
group = self.DynamicTools \
|
||||
.get_client(self.Yuuki.MyMID) \
|
||||
.getGroup(operation.message.to)
|
||||
group_privilege = [
|
||||
*self.Yuuki.Admin,
|
||||
StaticTools.get_group_creator(group).mid,
|
||||
*self.Yuuki.data.get_group(group.id)["Ext_Admin"]
|
||||
]
|
||||
if operation.message.from_ in group_privilege:
|
||||
for user_id in self.Yuuki.Connect.helper:
|
||||
self.DynamicTools \
|
||||
.send_user(StaticTools.send_to_who(operation), user_id)
|
||||
|
||||
def _speed(self, operation):
|
||||
timer_start = time.time()
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("Testing...")
|
||||
)
|
||||
timer_end = time.time()
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("Speed:\n %s com/s") % (timer_end - timer_start,)
|
||||
)
|
||||
|
||||
def _security_mode(self, operation):
|
||||
message_separated = operation.message.text.split(" ")
|
||||
if operation.message.from_ in self.Yuuki.Admin:
|
||||
if len(message_separated) == 2:
|
||||
if message_separated[1].isdigit() and 1 >= int(message_separated[1]) >= 0:
|
||||
self.Yuuki.data.update_data(
|
||||
["Global", "SecurityService"], bool(message_separated[1]))
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("Okay")
|
||||
)
|
||||
else:
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("Enable(True): 1\nDisable(False): 0")
|
||||
)
|
||||
else:
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
str(bool(self.Yuuki.data.get_data(["Global", "SecurityService"])))
|
||||
)
|
||||
|
||||
def _switch(self, operation):
|
||||
if operation.message.toType == MIDType.GROUP:
|
||||
group = self.DynamicTools \
|
||||
.get_client(self.Yuuki.MyMID) \
|
||||
.getGroup(operation.message.to)
|
||||
group_privilege = [
|
||||
*self.Yuuki.Admin,
|
||||
StaticTools.get_group_creator(group).mid,
|
||||
*self.Yuuki.data.get_group(group.id)["Ext_Admin"]
|
||||
]
|
||||
if not self.Yuuki.data.get_data(["Global", "SecurityService"]):
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("SecurityService of %s was disable") % (self.Yuuki.configs["name"],)
|
||||
)
|
||||
elif operation.message.from_ in group_privilege:
|
||||
self._switch_action(operation)
|
||||
|
||||
def _switch_action(self, operation):
|
||||
message_separated = operation.message.text.split(" ")
|
||||
status = []
|
||||
unknown_msg = []
|
||||
unknown_msg_text = ""
|
||||
for count, code in enumerate(message_separated):
|
||||
if code.isdigit() and 3 >= int(code) >= 0:
|
||||
status.append(int(code))
|
||||
elif count != 0:
|
||||
unknown_msg.append(code.strip())
|
||||
self.DynamicTools.config_security_status(
|
||||
operation.message.to, status)
|
||||
if unknown_msg:
|
||||
unknown_msg_text = ", ".join(unknown_msg)
|
||||
if status:
|
||||
self.DynamicTools.send_text(StaticTools.send_to_who(
|
||||
operation), self.Yuuki.get_text("Okay"))
|
||||
else:
|
||||
self.DynamicTools.send_text(StaticTools.send_to_who(
|
||||
operation), self.Yuuki.get_text("Not Found"))
|
||||
if unknown_msg_text != "":
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("Notice: Unknown command line argument(s)") + f"\n({unknown_msg_text})"
|
||||
)
|
||||
|
||||
def _disable_all(self, operation):
|
||||
if operation.message.toType == MIDType.GROUP:
|
||||
group = self.DynamicTools \
|
||||
.get_client(self.Yuuki.MyMID) \
|
||||
.getGroup(operation.message.to)
|
||||
group_privilege = [
|
||||
*self.Yuuki.Admin,
|
||||
StaticTools.get_group_creator(group).mid,
|
||||
*self.Yuuki.data.get_group(group.id)["Ext_Admin"]
|
||||
]
|
||||
if not self.Yuuki.data.get_data(["Global", "SecurityService"]):
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("SecurityService of %s was disable") % (self.Yuuki.configs["name"],)
|
||||
)
|
||||
elif operation.message.from_ in group_privilege:
|
||||
self.DynamicTools.config_security_status(operation.message.to, [])
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("Okay")
|
||||
)
|
||||
|
||||
def _ext_admin(self, operation):
|
||||
message_separated = operation.message.text.split(" ")
|
||||
if operation.message.toType == MIDType.GROUP:
|
||||
group = self.DynamicTools \
|
||||
.get_client(self.Yuuki.MyMID) \
|
||||
.getGroup(operation.message.to)
|
||||
group_privilege = [
|
||||
*self.Yuuki.Admin,
|
||||
StaticTools.get_group_creator(group).mid
|
||||
]
|
||||
if len(message_separated) == 3:
|
||||
if operation.message.from_ in group_privilege:
|
||||
if message_separated[1] == "add":
|
||||
self._ext_admin_add(operation, message_separated, group)
|
||||
elif message_separated[1] == "delete":
|
||||
self._ext_admin_delete(operation, message_separated, group)
|
||||
else:
|
||||
self._ext_admin_query(operation, group)
|
||||
|
||||
def _ext_admin_add(self, operation, message_separated, group):
|
||||
if message_separated[2] in [Member.mid for Member in group.members]:
|
||||
if message_separated[2] in self.Yuuki.data.get_group(group.id)["Ext_Admin"]:
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("Added")
|
||||
)
|
||||
elif message_separated[2] not in self.Yuuki.data.get_data(["BlackList"]):
|
||||
origin = self.Yuuki.data.get_data(["Group", group.id, "Ext_Admin"])
|
||||
ext_admin_list = origin.copy()
|
||||
ext_admin_list.append(message_separated[2])
|
||||
self.Yuuki.data.update_data(["Group", group.id, "Ext_Admin"], ext_admin_list)
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("Okay")
|
||||
)
|
||||
else:
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("The User(s) was in our blacklist database.")
|
||||
)
|
||||
else:
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("Wrong UserID or the guy is not in Group")
|
||||
)
|
||||
|
||||
def _ext_admin_delete(self, operation, message_separated, group):
|
||||
if message_separated[2] in self.Yuuki.data.get_group(group.id)["Ext_Admin"]:
|
||||
origin = self.Yuuki.data.get_data(["Group", group.id, "Ext_Admin"])
|
||||
ext_admin_list = origin.copy()
|
||||
ext_admin_list.remove(message_separated[2])
|
||||
self.Yuuki.data.update_data(
|
||||
["Group", group.id, "Ext_Admin"],
|
||||
ext_admin_list
|
||||
)
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("Okay")
|
||||
)
|
||||
else:
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("Not Found")
|
||||
)
|
||||
|
||||
def _ext_admin_query(self, operation, group):
|
||||
if self.Yuuki.data.get_group(group.id)["Ext_Admin"]:
|
||||
status = ""
|
||||
status_added = []
|
||||
for member in group.members:
|
||||
if member.mid in self.Yuuki.data.get_group(group.id)["Ext_Admin"]:
|
||||
status += f"{member.displayName}\n"
|
||||
status_added.append(member.mid)
|
||||
for user_id in self.Yuuki.data.get_group(group.id)["Ext_Admin"]:
|
||||
if user_id not in status_added:
|
||||
status += "{}: {}\n".format(
|
||||
self.Yuuki.get_text("Unknown"),
|
||||
user_id
|
||||
)
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
status + self.Yuuki.get_text("\nExtend Administrator(s)")
|
||||
)
|
||||
else:
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("Not Found")
|
||||
)
|
||||
|
||||
def _status(self, operation):
|
||||
if operation.message.toType == MIDType.GROUP:
|
||||
group = self.DynamicTools \
|
||||
.get_client(self.Yuuki.MyMID) \
|
||||
.getGroup(operation.message.to)
|
||||
group_status = self.Yuuki.data.get_se_group(operation.message.to)
|
||||
if not self.Yuuki.data.get_data(["Global", "SecurityService"]):
|
||||
status = self.Yuuki.get_text("SecurityService of %s was disable") % (self.Yuuki.configs["name"],)
|
||||
elif group_status is None:
|
||||
status = self.Yuuki.get_text("Default without Initialize\nMain Admin of the Group:\n%s") % \
|
||||
(StaticTools.get_group_creator(group).displayName,)
|
||||
else:
|
||||
status = self.Yuuki.get_text(
|
||||
"SecurityService is Listening on\n"
|
||||
"\nURL:%s\nInvite:%s\nJoin:%s\nMembers:%s\n"
|
||||
"\nMain Admin of the Group:\n%s") % \
|
||||
(
|
||||
group_status[OpType.NOTIFIED_UPDATE_GROUP],
|
||||
group_status[OpType.NOTIFIED_INVITE_INTO_GROUP],
|
||||
group_status[OpType.NOTIFIED_ACCEPT_GROUP_INVITATION],
|
||||
group_status[OpType.NOTIFIED_KICKOUT_FROM_GROUP],
|
||||
StaticTools.get_group_creator(
|
||||
group).displayName,
|
||||
)
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
status
|
||||
)
|
||||
|
||||
def _group_backup(self, operation):
|
||||
if operation.message.toType == MIDType.GROUP:
|
||||
group = self.DynamicTools \
|
||||
.get_client(self.Yuuki.MyMID) \
|
||||
.getGroup(operation.message.to)
|
||||
group_privilege = [
|
||||
*self.Yuuki.Admin,
|
||||
StaticTools.get_group_creator(group).mid,
|
||||
*self.Yuuki.data.get_group(group.id)["Ext_Admin"]
|
||||
]
|
||||
if operation.message.from_ in group_privilege:
|
||||
group_members = [user.mid for user in group.members]
|
||||
group_invitations = None
|
||||
if group.invitee:
|
||||
group_invitations = [user.mid for user in group.invitee]
|
||||
output_info = {
|
||||
"OriginID": group.id,
|
||||
"Members": group_members,
|
||||
"Invites": group_invitations
|
||||
}
|
||||
self.DynamicTools.send_text(
|
||||
operation.message.from_, group.name)
|
||||
self.DynamicTools.send_text(
|
||||
operation.message.from_, json.dumps(output_info))
|
||||
self.DynamicTools.send_text(
|
||||
operation.message.to, self.Yuuki.get_text("Okay"))
|
||||
|
||||
def _quit(self, operation):
|
||||
if operation.message.toType == MIDType.GROUP:
|
||||
group = self.DynamicTools \
|
||||
.get_client(self.Yuuki.MyMID) \
|
||||
.getGroup(operation.message.to)
|
||||
group_privilege = [
|
||||
*self.Yuuki.Admin,
|
||||
StaticTools.get_group_creator(group).mid
|
||||
]
|
||||
if operation.message.from_ in group_privilege:
|
||||
self.DynamicTools.leave_group(group)
|
||||
|
||||
def _exit(self, operation):
|
||||
if operation.message.from_ in self.Yuuki.Admin:
|
||||
self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("Exit.")
|
||||
)
|
||||
self.Yuuki.exit()
|
||||
|
||||
def _system_call(self, operation):
|
||||
message_separated = operation.message.text.split(" ")
|
||||
if operation.message.from_ in self.Yuuki.Admin:
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
command_message = StaticTools.read_commands(message_separated[1:len(message_separated)])
|
||||
reporting_message = str(eval(command_message))
|
||||
except:
|
||||
(err1, err2, err3, error_info) = StaticTools.report_error()
|
||||
reporting_message = f"Star Yuuki BOT - Eval Error:\n{err1}\n{err2}\n{err3}\n\n{error_info}"
|
||||
self.DynamicTools.send_text(StaticTools.send_to_who(operation), reporting_message)
|
||||
|
||||
def _text(self, operation):
|
||||
yuuki_name = self.Yuuki.configs["name"]
|
||||
message_separated = operation.message.text.split(" ")[0].split("/")
|
||||
actions = {
|
||||
'Help': self._help,
|
||||
'Version': self._version,
|
||||
'UserID': self._user_id,
|
||||
'GetAllHelper': self._get_all_helper,
|
||||
'Speed': self._speed,
|
||||
'SecurityMode': self._security_mode,
|
||||
'Switch': self._switch,
|
||||
'DisableAll': self._disable_all,
|
||||
'ExtAdmin': self._ext_admin,
|
||||
'Status': self._status,
|
||||
'GroupBackup': self._group_backup,
|
||||
'Quit': self._quit,
|
||||
'Exit': self._exit,
|
||||
'SystemCall': self._system_call,
|
||||
}
|
||||
if yuuki_name == message_separated[0]:
|
||||
if len(message_separated) > 1 and message_separated[1] in actions:
|
||||
function_ = actions[message_separated[1]]
|
||||
if callable(function_):
|
||||
return function_(operation)
|
||||
return self.DynamicTools.send_text(
|
||||
StaticTools.send_to_who(operation),
|
||||
self.Yuuki.get_text("Helllo^^\nMy name is %s ><\nNice to meet you OwO") % (yuuki_name,)
|
||||
)
|
||||
|
||||
def _contact(self, operation):
|
||||
cache = operation.message.contentMetadata["mid"]
|
||||
contact_info = self.DynamicTools.get_contact(cache)
|
||||
if not contact_info:
|
||||
msg = self.Yuuki.get_text("Not Found")
|
||||
elif contact_info.mid in self.Yuuki.data.get_data(["BlackList"]):
|
||||
msg = "{}\n{}".format(
|
||||
self.Yuuki.get_text("The User(s) was in our blacklist database."),
|
||||
contact_info.mid
|
||||
)
|
||||
else:
|
||||
msg = self.Yuuki.get_text("Name:%s\nPicture URL:%s/%s\nStatusMessage:\n%s\nLINE System UserID:%s") % \
|
||||
(
|
||||
contact_info.displayName,
|
||||
self.Yuuki.LINE_Media_server,
|
||||
contact_info.pictureStatus,
|
||||
contact_info.statusMessage,
|
||||
contact_info.mid
|
||||
)
|
||||
self.DynamicTools.send_text(StaticTools.send_to_who(operation), msg)
|
||||
|
||||
def action(self, operation):
|
||||
blocked_user = (operation.message.to in self.Yuuki.data.get_data(["BlackList"])) or \
|
||||
(operation.message.from_ in self.Yuuki.data.get_data(["BlackList"]))
|
||||
|
||||
if ('BOT_CHECK' in operation.message.contentMetadata) or blocked_user:
|
||||
pass
|
||||
|
||||
elif operation.message.toType == MIDType.ROOM:
|
||||
self.DynamicTools \
|
||||
.get_client(self.Yuuki.MyMID) \
|
||||
.leaveRoom(self.Yuuki.Seq, operation.message.to)
|
||||
|
||||
elif operation.message.contentType == ContentType.NONE:
|
||||
self._text(operation)
|
||||
|
||||
elif operation.message.contentType == ContentType.CONTACT:
|
||||
self._contact(operation)
|
|
@ -1,98 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from ..tools import StaticTools, DynamicTools
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..yuuki import Yuuki
|
||||
|
||||
|
||||
class JoinGroup:
|
||||
def __init__(self, handler: Yuuki):
|
||||
"""
|
||||
Event Type:
|
||||
NOTIFIED_INVITE_INTO_GROUP (13)
|
||||
"""
|
||||
self.Yuuki = handler
|
||||
self.DynamicTools = DynamicTools(self.Yuuki)
|
||||
|
||||
def _accept(self, group_id, group, inviter):
|
||||
group_list = self.Yuuki.data.get_data(["Global", "GroupJoined"])
|
||||
new_group_list = group_list.copy()
|
||||
new_group_list.append(group_id)
|
||||
self.Yuuki.data.update_data(
|
||||
["Global", "GroupJoined"], new_group_list)
|
||||
self.DynamicTools.send_text(
|
||||
group_id,
|
||||
self.Yuuki.get_text("Helllo^^\nMy name is %s ><\nNice to meet you OwO") %
|
||||
(self.Yuuki.configs["name"],)
|
||||
)
|
||||
self.DynamicTools.send_text(
|
||||
group_id,
|
||||
self.Yuuki.get_text("Type:\n\t%s/Help\nto get more information\n\nMain Admin of the Group:\n%s") %
|
||||
(
|
||||
self.Yuuki.configs["name"],
|
||||
StaticTools.get_group_creator(group).displayName,
|
||||
)
|
||||
)
|
||||
self.DynamicTools.get_group_ticket(group_id, self.Yuuki.MyMID, True)
|
||||
# Log
|
||||
self.Yuuki.data.update_log(
|
||||
"JoinGroup",
|
||||
(self.Yuuki.data.get_time(), group.name, group_id, inviter)
|
||||
)
|
||||
|
||||
def _reject(self, group_id, inviter):
|
||||
self.DynamicTools.send_text(
|
||||
group_id,
|
||||
self.Yuuki.get_text("Sorry...\nThe number of members is not satisfied (%s needed)") %
|
||||
(self.Yuuki.configs["GroupMembers_Demand"],)
|
||||
)
|
||||
self.DynamicTools.get_client(self.Yuuki.MyMID).leave_group(self.Yuuki.Seq, group_id)
|
||||
# Log
|
||||
self.Yuuki.data.update_log(
|
||||
"JoinGroup",
|
||||
(self.Yuuki.data.get_time(), group_id, "Not Join", inviter)
|
||||
)
|
||||
|
||||
def _check_helper(self, operation, group_invitations, blocked_user):
|
||||
if operation.param1 in self.Yuuki.data.get_data(["Global", "GroupJoined"]) and not blocked_user:
|
||||
for user_id in self.Yuuki.Connect.helper:
|
||||
if self.DynamicTools.check_invitation(operation, user_id) or user_id in group_invitations:
|
||||
self.DynamicTools.get_client(user_id).acceptGroupInvitation(self.Yuuki.Seq, operation.param1)
|
||||
self.DynamicTools.get_group_ticket(operation.param1, user_id, True)
|
||||
# Log
|
||||
self.Yuuki.data.update_log("JoinGroup", (
|
||||
self.Yuuki.data.get_time(),
|
||||
operation.param1,
|
||||
user_id,
|
||||
operation.param2
|
||||
))
|
||||
|
||||
def action(self, operation):
|
||||
group_invitations = []
|
||||
blocked_user = operation.param2 in self.Yuuki.data.get_data(["BlackList"])
|
||||
if self.DynamicTools.check_invitation(operation) and not blocked_user:
|
||||
group_id = operation.param1
|
||||
inviter = operation.param2
|
||||
group = self.DynamicTools \
|
||||
.get_client(self.Yuuki.MyMID) \
|
||||
.getGroup(group_id)
|
||||
group_member = [user.mid for user in group.members] if group.members else []
|
||||
group_invitations = [user.mid for user in group.invitee] if group.invitee else []
|
||||
self.DynamicTools.get_client(self.Yuuki.MyMID).acceptGroupInvitation(self.Yuuki.Seq, group_id)
|
||||
if len(group_member) >= self.Yuuki.configs["GroupMembers_Demand"]:
|
||||
self._accept(group_id, group, inviter)
|
||||
else:
|
||||
self._reject(group_id, inviter)
|
||||
self._check_helper(operation, group_invitations, blocked_user)
|
||||
self.Yuuki.Security(operation)
|
|
@ -1,313 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from yuuki_core.ttypes import OpType
|
||||
|
||||
from ..tools import StaticTools, DynamicTools
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..yuuki import Yuuki
|
||||
|
||||
|
||||
def security_access_checker(function):
|
||||
def wrapper(*args):
|
||||
if not args[2].get("Security_Access"):
|
||||
return
|
||||
return function(*args)
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class Security:
|
||||
def __init__(self, handler: Yuuki):
|
||||
"""
|
||||
Event Type:
|
||||
NOTIFIED_UPDATE_GROUP (11)
|
||||
NOTIFIED_INVITE_INTO_GROUP (13)
|
||||
NOTIFIED_ACCEPT_GROUP_INVITATION (17)
|
||||
NOTIFIED_KICKOUT_FROM_GROUP (19)
|
||||
"""
|
||||
self.Yuuki = handler
|
||||
self.DynamicTools = DynamicTools(self.Yuuki)
|
||||
|
||||
@security_access_checker
|
||||
def _notified_update_group(self, group, security_info, operation):
|
||||
if security_info["Another"] == '4':
|
||||
if not group.preventJoinByTicket and security_info["Action"] not in self.Yuuki.Connect.helper:
|
||||
self.Yuuki.thread_append(
|
||||
self.DynamicTools.switch_group_url_status,
|
||||
(group, False)
|
||||
)
|
||||
self.Yuuki.thread_append(
|
||||
self.DynamicTools.send_text,
|
||||
(security_info["GroupID"], self.Yuuki.get_text("DO NOT ENABLE THE GROUP URL STATUS, see you..."))
|
||||
)
|
||||
kicker = self.DynamicTools.modify_group_members(1, group, security_info["Action"])
|
||||
# Log
|
||||
self.Yuuki.data.update_log("KickEvent", (
|
||||
self.Yuuki.data.get_time(),
|
||||
group.name,
|
||||
security_info["GroupID"],
|
||||
kicker,
|
||||
security_info["Action"],
|
||||
security_info["Another"],
|
||||
operation.type
|
||||
))
|
||||
|
||||
@security_access_checker
|
||||
def _notified_invite_into_group(self, group, security_info, operation):
|
||||
canceler = "None"
|
||||
if "\x1e" in security_info["Another"]:
|
||||
canceler = self._notified_invite_into_group_list(group, security_info, operation, canceler)
|
||||
elif security_info["Another"] not in self.Yuuki.AllAccountIds + security_info["GroupPrivilege"]:
|
||||
canceler = self._notified_invite_into_group_single(group, security_info, operation)
|
||||
if canceler != "None":
|
||||
self.DynamicTools.send_text(
|
||||
security_info["GroupID"],
|
||||
self.Yuuki.get_text("Do not invite anyone...thanks")
|
||||
)
|
||||
|
||||
def _notified_invite_into_group_list(self, group, security_info, operation, canceler):
|
||||
for user_id in security_info["Another"].split("\x1e"):
|
||||
if user_id not in self.Yuuki.AllAccountIds + security_info["GroupPrivilege"]:
|
||||
if group.invitee and user_id in [user.mid for user in group.invitee]:
|
||||
canceler = self.DynamicTools.modify_group_members(2, group, user_id)
|
||||
else:
|
||||
canceler = self.DynamicTools.modify_group_members(1, group, user_id)
|
||||
# Log
|
||||
self.Yuuki.data.update_log("KickEvent", (
|
||||
self.Yuuki.data.get_time(),
|
||||
group.name,
|
||||
security_info["GroupID"],
|
||||
canceler,
|
||||
security_info["Action"],
|
||||
user_id,
|
||||
operation.type * 10
|
||||
))
|
||||
# Log
|
||||
self.Yuuki.data.update_log("CancelEvent", (
|
||||
self.Yuuki.data.get_time(),
|
||||
group.name,
|
||||
security_info["GroupID"],
|
||||
canceler,
|
||||
security_info["Action"],
|
||||
security_info["Another"].replace("\x1e", ",")
|
||||
))
|
||||
return canceler
|
||||
|
||||
def _notified_invite_into_group_single(self, group, security_info, operation):
|
||||
if group.invitee and security_info["Another"] in [user.mid for user in group.invitee]:
|
||||
canceler = self.DynamicTools.modify_group_members(2, group, security_info["Another"])
|
||||
else:
|
||||
canceler = self.DynamicTools.modify_group_members(1, group, security_info["Another"])
|
||||
# Log
|
||||
self.Yuuki.data.update_log("KickEvent", (
|
||||
self.Yuuki.data.get_time(),
|
||||
group.name, security_info["GroupID"],
|
||||
canceler, security_info["Action"],
|
||||
security_info["Another"],
|
||||
operation.type * 10
|
||||
))
|
||||
# Log
|
||||
self.Yuuki.data.update_log("CancelEvent", (
|
||||
self.Yuuki.data.get_time(),
|
||||
group.name,
|
||||
security_info["GroupID"],
|
||||
canceler,
|
||||
security_info["Action"],
|
||||
security_info["Another"]
|
||||
))
|
||||
return canceler
|
||||
|
||||
@security_access_checker
|
||||
def _notified_accept_group_invitation(self, group, security_info, operation):
|
||||
for user_id in self.Yuuki.data.get_data(["BlackList"]):
|
||||
if user_id == security_info["Action"]:
|
||||
self.Yuuki.thread_append(
|
||||
self.DynamicTools.send_text,
|
||||
(security_info["GroupID"], self.Yuuki.get_text("You are our blacklist. Bye~"))
|
||||
)
|
||||
kicker = self.DynamicTools.modify_group_members(1, group, security_info["Action"])
|
||||
# Log
|
||||
self.Yuuki.data.update_log("KickEvent", (
|
||||
self.Yuuki.data.get_time(),
|
||||
group.name,
|
||||
security_info["GroupID"],
|
||||
kicker,
|
||||
kicker,
|
||||
security_info["Action"],
|
||||
operation.type
|
||||
))
|
||||
|
||||
def _notified_kickout_from_group(self, group, security_info, operation):
|
||||
if security_info["Action"] in self.Yuuki.Connect.helper:
|
||||
# Log
|
||||
self.Yuuki.data.update_log("KickEvent", (
|
||||
self.Yuuki.data.get_time(),
|
||||
group.name,
|
||||
security_info["GroupID"],
|
||||
security_info["Action"],
|
||||
security_info["Action"],
|
||||
security_info["Another"],
|
||||
operation.type * 10 + 1
|
||||
))
|
||||
elif security_info["Another"] in self.Yuuki.AllAccountIds:
|
||||
kicker = "None"
|
||||
try:
|
||||
kicker = self.DynamicTools.modify_group_members(
|
||||
1,
|
||||
group,
|
||||
security_info["Action"],
|
||||
security_info["Another"]
|
||||
)
|
||||
self._notified_kickout_from_group_rescue(group, security_info, operation, kicker)
|
||||
except:
|
||||
self._notified_kickout_from_group_rescue_failure(group, security_info, operation, kicker)
|
||||
black_list = self.Yuuki.data.get_data(["BlackList"])
|
||||
if security_info["Action"] not in black_list:
|
||||
new_black_list = black_list.copy()
|
||||
new_black_list.append(security_info["Action"])
|
||||
self.Yuuki.data.update_data(["BlackList"], new_black_list)
|
||||
# Log
|
||||
self.Yuuki.data.update_log("BlackList", (
|
||||
self.Yuuki.data.get_time(),
|
||||
security_info["Action"],
|
||||
security_info["GroupID"]
|
||||
))
|
||||
self.Yuuki.thread_append(
|
||||
self.DynamicTools.send_text,
|
||||
(
|
||||
security_info["Action"],
|
||||
self.Yuuki.get_text("You had been blocked by our database.")
|
||||
)
|
||||
)
|
||||
elif security_info["Security_Access"]:
|
||||
self._notified_kickout_from_group_normal(group, security_info, operation)
|
||||
|
||||
def _notified_kickout_from_group_rescue(self, group, security_info, operation, kicker):
|
||||
# Log
|
||||
self.Yuuki.data.update_log("KickEvent", (
|
||||
self.Yuuki.data.get_time(),
|
||||
group.name,
|
||||
security_info["GroupID"],
|
||||
kicker,
|
||||
security_info["Action"],
|
||||
security_info["Another"],
|
||||
operation.type * 10 + 2
|
||||
))
|
||||
assert kicker != "None", "No Helper Found"
|
||||
if group.preventJoinByTicket:
|
||||
self.Yuuki.thread_append(
|
||||
self.DynamicTools.switch_group_url_status,
|
||||
(group, True, kicker)
|
||||
)
|
||||
group_ticket = self.DynamicTools.get_group_ticket(
|
||||
security_info["GroupID"], kicker)
|
||||
try:
|
||||
self.DynamicTools.get_client(security_info["Another"]).acceptGroupInvitationByTicket(
|
||||
self.Yuuki.Seq,
|
||||
security_info["GroupID"],
|
||||
group_ticket
|
||||
)
|
||||
except:
|
||||
if group.preventJoinByTicket:
|
||||
self.DynamicTools.switch_group_url_status(
|
||||
group,
|
||||
True,
|
||||
kicker
|
||||
)
|
||||
group_ticket = self.DynamicTools.get_group_ticket(
|
||||
security_info["GroupID"], kicker, True)
|
||||
self.DynamicTools.get_client(security_info["Another"]).acceptGroupInvitationByTicket(
|
||||
self.Yuuki.Seq,
|
||||
security_info["GroupID"],
|
||||
group_ticket
|
||||
)
|
||||
if group.preventJoinByTicket:
|
||||
self.Yuuki.thread_append(
|
||||
self.DynamicTools.switch_group_url_status, (group, False, security_info["Another"]))
|
||||
self.DynamicTools.get_group_ticket(
|
||||
security_info["GroupID"], security_info["Another"], True)
|
||||
|
||||
def _notified_kickout_from_group_rescue_failure(self, group, security_info, operation, kicker):
|
||||
(err1, err2, err3, error_info) = StaticTools.report_error()
|
||||
for Root in self.Yuuki.Admin:
|
||||
self.DynamicTools.send_text(
|
||||
Root,
|
||||
"Star Yuuki BOT - SecurityService Failure\n\n%s\n%s\n%s\n\n%s" % (err1, err2, err3, error_info)
|
||||
)
|
||||
if security_info["Another"] == self.Yuuki.MyMID:
|
||||
group_list = self.Yuuki.data.get_data(["Global", "GroupJoined"])
|
||||
new_group_list = group_list.copy()
|
||||
new_group_list.remove(security_info["GroupID"])
|
||||
self.Yuuki.data.update_data(["Global", "GroupJoined"], new_group_list)
|
||||
# Log
|
||||
self.Yuuki.data.update_log("KickEvent", (
|
||||
self.Yuuki.data.get_time(),
|
||||
group.name,
|
||||
security_info["GroupID"],
|
||||
kicker,
|
||||
security_info["Action"],
|
||||
security_info["Another"],
|
||||
operation.type * 10 + 3
|
||||
))
|
||||
|
||||
def _notified_kickout_from_group_normal(self, group, security_info, operation):
|
||||
self.Yuuki.thread_append(self.DynamicTools.send_text, (
|
||||
security_info["GroupID"], self.Yuuki.get_text("DO NOT KICK, thank you ^^")))
|
||||
kicker = self.DynamicTools.modify_group_members(1, group, security_info["Action"])
|
||||
# Log
|
||||
self.Yuuki.data.update_log("KickEvent", (
|
||||
self.Yuuki.data.get_time(),
|
||||
group.name,
|
||||
security_info["GroupID"],
|
||||
kicker,
|
||||
security_info["Action"],
|
||||
security_info["Another"],
|
||||
operation.type
|
||||
))
|
||||
self.Yuuki.thread_append(self.DynamicTools.send_text, (security_info["GroupID"], self.Yuuki.get_text(
|
||||
"The one who was been kicked:")))
|
||||
self.Yuuki.thread_append(
|
||||
self.DynamicTools.send_user, (security_info["GroupID"], security_info["Another"]))
|
||||
|
||||
def action(self, operation):
|
||||
security_info = StaticTools.security_for_where(operation)
|
||||
|
||||
group = self.DynamicTools.get_client(self.Yuuki.MyMID).getGroup(security_info["GroupID"])
|
||||
security_info["GroupPrivilege"] = [
|
||||
*self.Yuuki.Admin,
|
||||
StaticTools.get_group_creator(group).mid,
|
||||
*self.Yuuki.data.get_group(group.id)["Ext_Admin"]
|
||||
]
|
||||
if security_info["Action"] in security_info["GroupPrivilege"] or \
|
||||
security_info["Another"] in security_info["GroupPrivilege"]:
|
||||
if operation.type != OpType.NOTIFIED_KICKOUT_FROM_GROUP:
|
||||
return
|
||||
elif security_info["Action"] in security_info["GroupPrivilege"]:
|
||||
return
|
||||
|
||||
se_group = self.Yuuki.data.get_se_group(security_info["GroupID"])
|
||||
if se_group is None:
|
||||
security_info["Security_Access"] = self.Yuuki.data.get_data(["Global", "SecurityService"])
|
||||
elif se_group[operation.type]:
|
||||
security_info["Security_Access"] = se_group[operation.type]
|
||||
else:
|
||||
security_info["Security_Access"] = False
|
||||
|
||||
if self.Yuuki.data.get_data(["Global", "SecurityService"]):
|
||||
{
|
||||
OpType.NOTIFIED_UPDATE_GROUP: self._notified_update_group,
|
||||
OpType.NOTIFIED_INVITE_INTO_GROUP: self._notified_invite_into_group,
|
||||
OpType.NOTIFIED_ACCEPT_GROUP_INVITATION: self._notified_accept_group_invitation,
|
||||
OpType.NOTIFIED_KICKOUT_FROM_GROUP: self._notified_kickout_from_group
|
||||
}[operation.type](group, security_info, operation)
|
|
@ -1,27 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
from .en import English
|
||||
from .zh_TW import Traditional_Chinese
|
||||
|
||||
|
||||
class LangSetting:
|
||||
def __init__(self, default):
|
||||
self.default = default
|
||||
self.support = {
|
||||
"en": English,
|
||||
"zh-tw": Traditional_Chinese
|
||||
}
|
||||
|
||||
def gettext(self, text, lang=None):
|
||||
try:
|
||||
if lang:
|
||||
return self.support[lang].i18nText[text]
|
||||
return self.support[self.default].i18nText[text]
|
||||
except KeyError:
|
||||
return text + "\n\n{\n\tLanguage Package not work.\n\tPlease inform the Admin of the Yuuki.\n}"
|
|
@ -1,49 +0,0 @@
|
|||
#!/usr/bin/python3
|
||||
# coding=UTF-8
|
||||
"""
|
||||
Star Yuuki Bot - Yuuki_i18n
|
||||
~~~~~~~~~~~
|
||||
This is a Language Package in SYB.
|
||||
It`s belong to Star Yuuki(pYthon) Bot Project of Star Neptune Bot
|
||||
|
||||
The Language Package Contributor:
|
||||
supersonictw <https://github.com/supersonictw>
|
||||
|
||||
Copyright(c) 2020 Star Inc. All Rights Reserved.
|
||||
The software licensed under Mozilla Public License Version 2.0
|
||||
"""
|
||||
|
||||
|
||||
class English:
|
||||
i18nText = {
|
||||
"Helllo^^\nMy name is %s ><\nNice to meet you OwO": "Helllo^^\nMy name is %s ><\nNice to meet you OwO",
|
||||
"Type:\n\t%s/Help\nto get more information\n\nMain Admin of the Group:\n%s": "Type:\n\t%s/Help\nto get more information\n\nMain Admin of the Group:\n%s",
|
||||
"%s\n\t%s\n\nCommands Info:\n%s\n\nPrivacy:\n%s\n\nMore Information:\n%s\n\n%s": "%s\n\t%s\n\nCommands Info:\n%s\n\nPrivacy:\n%s\n\nMore Information:\n%s\n\n%s",
|
||||
"SecurityService is Listening on\n\nURL:%s\nInvite:%s\nJoin:%s\nMembers:%s\n\nMain Admin of the Group:\n%s": "SecurityService is Listening on\n\nURL:%s\nInvite:%s\nJoin:%s\nMembers:%s\n\nMain Admin of the Group:\n%s",
|
||||
"Wrong UserID or the guy is not in Group": "Wrong UserID or the guy is not in Group",
|
||||
"Default without Initialize\nMain Admin of the Group:\n%s": "Default without Initialize\nMain Admin of the Group:\n%s",
|
||||
"Sorry...\nThe number of members is not satisfied (%s needed)": "Sorry...\nThe number of members is not satisfied (%s needed)",
|
||||
"Name:%s\nPicture URL:%s/%s\nStatusMessage:\n%s\nLINE System UserID:%s": "Name:%s\nPicture URL:%s/%s\nStatusMessage:\n%s\nLINE System UserID:%s",
|
||||
"LINE System UserID:\n": "LINE System UserID:\n",
|
||||
"SecurityService of %s was disable": "SecurityService of %s was disable",
|
||||
"DO NOT KICK, thank you ^^": "DO NOT KICK, thank you ^^",
|
||||
"Notice: Unknown command line argument(s)": "Notice: Unknown command line argument(s)",
|
||||
"DO NOT ENABLE THE GROUP URL STATUS, see you...": "DO NOT ENABLE THE GROUP URL STATUS, see you...",
|
||||
"Do not invite anyone...thanks": "Do not invite anyone...thanks",
|
||||
"The User(s) was in our blacklist database.": "The User(s) was in our blacklist database.",
|
||||
"You are our blacklist. Bye~": "You are our blacklist. Bye~",
|
||||
"You had been blocked by our database.": "You had been blocked by our database.",
|
||||
"\nExtend Administrator(s)": "\nExtend Administrator(s)",
|
||||
"Enable(True): 1\nDisable(False): 0": "Enable(True): 1\nDisable(False): 0",
|
||||
"The one who was been kicked:": "The one who was been kicked:",
|
||||
"Kick Limit.": "Kick Limit.",
|
||||
"Cancel Limit.": "Cancel Limit.",
|
||||
"Testing...": "Testing...",
|
||||
"Speed:\n %s com/s": "Speed:\n %s com/s",
|
||||
"Bye Bye": "Bye Bye",
|
||||
"Not Found": "Not Found",
|
||||
"Okay": "Okay",
|
||||
"Unknown": "Unknown",
|
||||
"Exit.": "Exit.",
|
||||
"Added": "Added"
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
#!/usr/bin/python3
|
||||
# coding=UTF-8
|
||||
"""
|
||||
Star Yuuki Bot - Yuuki_i18n
|
||||
~~~~~~~~~~~
|
||||
This is a Language Package in SYB.
|
||||
It`s belong to Star Yuuki(pYthon) Bot Project of Star Neptune Bot
|
||||
|
||||
The Language Package Contributor:
|
||||
supersonictw <https://github.com/supersonictw>
|
||||
|
||||
Copyright(c) 2020 Star Inc. All Rights Reserved.
|
||||
The software licensed under Mozilla Public License Version 2.0
|
||||
"""
|
||||
|
||||
|
||||
class Traditional_Chinese:
|
||||
i18nText = {
|
||||
"Helllo^^\nMy name is %s ><\nNice to meet you OwO": "安安^^\n我是%s呦><\n請多多指教OwO",
|
||||
"Type:\n\t%s/Help\nto get more information\n\nMain Admin of the Group:\n%s": "請輸入:\n\t%s/Help\n以獲得更多資訊\n\n本群組主管理員為:\n%s",
|
||||
"%s\n\t%s\n\nCommands Info:\n%s\n\nPrivacy:\n%s\n\nMore Information:\n%s\n\n%s": "%s\n\t%s\n\n指令資訊:\n%s\n\n隱私權政策:\n%s\n\n更多資訊:\n%s\n\n%s",
|
||||
"SecurityService is Listening on\n\nURL:%s\nInvite:%s\nJoin:%s\nMembers:%s\n\nMain Admin of the Group:\n%s": "安全防護模式正在監控\n\n網址:%s\n邀請:%s\n加入:%s\n成員:%s\n\n本群組主管理員為:\n%s",
|
||||
"Wrong UserID or the guy is not in Group": "錯誤的用戶識別碼或這傢伙不在群組",
|
||||
"Default without Initialize\nMain Admin of the Group:\n%s": "使用系統預設值,並未初始化\n本群組主管理員為:\n%s",
|
||||
"Sorry...\nThe number of members is not satisfied (%s needed)": "抱歉...\n這個群尚未達到可安裝人數 (需要%s人)",
|
||||
"Name:%s\nPicture URL:%s/%s\nStatusMessage:\n%s\nLINE System UserID:%s": "名字:%s\n頭像網址:%s/%s\n個性簽名:\n%s\nLINE用戶識別碼:%s",
|
||||
"LINE System UserID:\n": "LINE用戶識別碼:\n",
|
||||
"SecurityService of %s was disable": "%s的安全防護模式被已關閉",
|
||||
"DO NOT KICK, thank you ^^": "請不要踢人,謝謝 =3=",
|
||||
"Notice: Unknown command line argument(s)": "提醒:未知的指令參數",
|
||||
"DO NOT ENABLE THE GROUP URL STATUS, see you...": "請不要開啟群組網址,再見了 0.0",
|
||||
"Do not invite anyone...thanks": "請不要邀請任何人,感謝配合 OwO",
|
||||
"The User(s) was in our blacklist database.": "這個臭小子在我們的黑名單中 =.=",
|
||||
"You are our blacklist. Bye~": "再見了~黑名單用戶。",
|
||||
"You had been blocked by our database.": "您目前已被本程序封鎖。",
|
||||
"\nExtend Administrator(s)": "\n延伸管理員",
|
||||
"Enable(True): 1\nDisable(False): 0": "開啟(True): 1\n關閉(False): 0",
|
||||
"The one who was been kicked:": "這是那個被踢的可憐孩子:",
|
||||
"Kick Limit.": "踢人次數已達上限",
|
||||
"Cancel Limit.": "取消次數已達上限",
|
||||
"Testing...": "測試中...",
|
||||
"Speed:\n %s com/s": "速度為:\n%s 指令/秒",
|
||||
"Bye Bye": "掰掰 ><",
|
||||
"Not Found": "404 不存在 0.0...",
|
||||
"Okay": "好的",
|
||||
"Unknown": "未知",
|
||||
"Exit.": "已退出",
|
||||
"Added": "已增加"
|
||||
}
|
124
libs/mds.py
124
libs/mds.py
|
@ -1,124 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Star Inc. multiprocessing data switching
|
||||
===
|
||||
To switch data in multiprocessing.
|
||||
|
||||
LICENSE: MPL 2.0
|
||||
(c)2020 Star Inc.
|
||||
"""
|
||||
|
||||
# Initializing
|
||||
import json
|
||||
import types
|
||||
from abc import ABC
|
||||
|
||||
from tornado.httpserver import HTTPServer
|
||||
from tornado.ioloop import IOLoop
|
||||
from tornado.web import Application, RequestHandler
|
||||
|
||||
# Works
|
||||
_work = {}
|
||||
auth_code = 0
|
||||
|
||||
|
||||
class IndexHandler(RequestHandler, ABC):
|
||||
def get(self):
|
||||
self.write('''
|
||||
<b>Python MDS Server</b><br>
|
||||
To switch data in multiprocessing.<hr>
|
||||
(c)2020 <a href="https://starinc.xyz">Star Inc.</a>
|
||||
''')
|
||||
|
||||
def post(self):
|
||||
global auth_code
|
||||
req_body = self.request.body
|
||||
req_str = req_body.decode('utf8')
|
||||
req_res = json.loads(req_str)
|
||||
if req_res.get("code") == auth_code:
|
||||
result = _work[req_res.get("do")](
|
||||
{
|
||||
"path": req_res.get("path"),
|
||||
"data": req_res.get("data")
|
||||
}
|
||||
)
|
||||
else:
|
||||
result = {"status": 401}
|
||||
if isinstance(result, types.GeneratorType):
|
||||
result = {"status": 200}
|
||||
self.write(json.dumps(result))
|
||||
|
||||
|
||||
class PythonMDS:
|
||||
switch_data = {}
|
||||
|
||||
# Main
|
||||
app = Application([
|
||||
('/', IndexHandler)
|
||||
])
|
||||
server = HTTPServer(app)
|
||||
async_lock = IOLoop.current()
|
||||
|
||||
def __init__(self, port):
|
||||
_work["UPT"] = self._update
|
||||
_work["DEL"] = self._delete
|
||||
_work["GET"] = self._query
|
||||
_work["SYC"] = self._sync
|
||||
_work["YLD"] = self._yuuki_limit_trigger
|
||||
_work["EXT"] = self._shutdown
|
||||
self.port = port
|
||||
|
||||
def _query(self, data):
|
||||
query_data = data["path"]
|
||||
if type(self.switch_data) is dict and type(query_data) is list:
|
||||
result = self.switch_data
|
||||
query_len = len(query_data) - 1
|
||||
for count, key in enumerate(query_data):
|
||||
if key in result:
|
||||
if count < query_len:
|
||||
if type(result.get(key)) is not dict:
|
||||
result = 1 # "unknown_type" + type(source_data.get(key))
|
||||
break
|
||||
result = result.get(key)
|
||||
else:
|
||||
result = 2 # "unknown_key"
|
||||
break
|
||||
|
||||
return {"status": 200, "data": result}
|
||||
return {"status": 400}
|
||||
|
||||
def _update(self, data):
|
||||
if type(data["path"]) is list:
|
||||
over = self._query({"path": data["path"]})
|
||||
over.get("data").update(data["data"])
|
||||
return {"status": 200}
|
||||
return {"status": 400}
|
||||
|
||||
def _delete(self, data):
|
||||
if type(data["path"]) is list:
|
||||
over = self._query({"path": data["path"]})
|
||||
over.get("data").pop(data["data"])
|
||||
return {"status": 200}
|
||||
return {"status": 400}
|
||||
|
||||
def _sync(self, data):
|
||||
self.switch_data = data["path"]
|
||||
return {"status": 200}
|
||||
|
||||
def _yuuki_limit_trigger(self, data):
|
||||
self.switch_data["LimitInfo"][data["path"]][data["data"]] -= 1
|
||||
return {"status": 200}
|
||||
|
||||
def _shutdown(self, data):
|
||||
if data:
|
||||
pass
|
||||
self.server.stop()
|
||||
yield True
|
||||
self.async_lock.stop()
|
||||
self.async_lock.close()
|
||||
|
||||
def mds_listen(self, code):
|
||||
global auth_code
|
||||
auth_code = code
|
||||
self.server.listen(self.port)
|
||||
self.async_lock.start()
|
111
libs/poll.py
111
libs/poll.py
|
@ -1,111 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import socket
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from yuuki_core.ttypes import Operation
|
||||
|
||||
from .tools import StaticTools, DynamicTools
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .yuuki import Yuuki
|
||||
|
||||
|
||||
class Poll:
|
||||
Power = True
|
||||
|
||||
NoWork = 0
|
||||
NoWorkLimit = 5
|
||||
|
||||
fetchNum = 50
|
||||
cacheOperations = []
|
||||
|
||||
def __init__(self, handler: Yuuki):
|
||||
self.Yuuki = handler
|
||||
self.DynamicTools = DynamicTools(handler)
|
||||
|
||||
def _action(self):
|
||||
operation = Operation()
|
||||
|
||||
if time.localtime().tm_hour != self.Yuuki.data.get_data(["Global", "LastResetLimitTime"]):
|
||||
self.DynamicTools.reset_limit()
|
||||
self.Yuuki.data.update_data(["Global", "LastResetLimitTime"], time.localtime().tm_hour)
|
||||
|
||||
if self.NoWork >= self.NoWorkLimit:
|
||||
self.NoWork = 0
|
||||
for operation in self.cacheOperations:
|
||||
if operation.reqSeq != -1 and operation.revision > self.Yuuki.revision:
|
||||
self.Yuuki.revision = operation.revision
|
||||
break
|
||||
if operation.revision != self.Yuuki.revision:
|
||||
self.Yuuki.revision = self.Yuuki.client.getLastOpRevision()
|
||||
|
||||
try:
|
||||
self.cacheOperations = self.Yuuki.listen.fetchOperations(self.Yuuki.revision, self.fetchNum)
|
||||
except socket.timeout:
|
||||
self.NoWork += 1
|
||||
|
||||
if self.cacheOperations:
|
||||
self.NoWork = 0
|
||||
self.Yuuki.thread_append(self.Yuuki.task_executor, (self.cacheOperations,))
|
||||
if len(self.cacheOperations) > 1:
|
||||
self.Yuuki.revision = max(self.cacheOperations[-1].revision, self.cacheOperations[-2].revision)
|
||||
|
||||
def _exception(self):
|
||||
(err1, err2, err3, error_info) = StaticTools.report_error()
|
||||
|
||||
operation = Operation()
|
||||
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
for operation in self.cacheOperations:
|
||||
if operation.reqSeq != -1 and operation.revision > self.Yuuki.revision:
|
||||
self.Yuuki.revision = operation.revision
|
||||
break
|
||||
if operation.revision != self.Yuuki.revision:
|
||||
self.Yuuki.revision = self.Yuuki.client.getLastOpRevision()
|
||||
for Root in self.Yuuki.Admin:
|
||||
self.DynamicTools.send_text(
|
||||
Root,
|
||||
"Star Yuuki BOT - Something was wrong...\nError:\n%s\n%s\n%s\n\n%s" %
|
||||
(err1, err2, err3, error_info)
|
||||
)
|
||||
except:
|
||||
print("Star Yuuki BOT - Damage!\nError:\n%s\n%s\n%s\n\n%s" % (err1, err2, err3, error_info))
|
||||
self.Yuuki.exit()
|
||||
|
||||
def init(self):
|
||||
self.Yuuki.data.update_data(["Global", "Power"], self.Power)
|
||||
|
||||
if "LastResetLimitTime" not in self.Yuuki.data.get_data(["Global"]):
|
||||
self.Yuuki.data.update_data(["Global", "LastResetLimitTime"], None)
|
||||
|
||||
while self.Power:
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
self._action()
|
||||
try:
|
||||
self.Power = self.Yuuki.data.sync_data()
|
||||
except ConnectionRefusedError:
|
||||
self.Power = False
|
||||
|
||||
except SystemExit:
|
||||
self.Power = False
|
||||
|
||||
except KeyboardInterrupt:
|
||||
self.Yuuki.exit()
|
||||
|
||||
except EOFError:
|
||||
pass
|
||||
|
||||
except:
|
||||
self._exception()
|
|
@ -1,39 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
import multiprocessing
|
||||
import threading
|
||||
|
||||
|
||||
class Thread:
|
||||
def __init__(self):
|
||||
self.lock = threading.Lock()
|
||||
|
||||
@staticmethod
|
||||
def add(function, args=()):
|
||||
added_thread = threading.Thread(name=function.__name__, target=function, args=args)
|
||||
added_thread.start()
|
||||
|
||||
@staticmethod
|
||||
def get_thread_info():
|
||||
print(threading.active_count())
|
||||
print(threading.enumerate())
|
||||
print(f"{threading.current_thread()} add Threading\n")
|
||||
|
||||
|
||||
class Multiprocess:
|
||||
multiprocess_list = {}
|
||||
|
||||
def add(self, function, args=()):
|
||||
added_multiprocess = multiprocessing.Process(name=function.__name__, target=function, args=args)
|
||||
self.multiprocess_list[function.__name__] = added_multiprocess
|
||||
added_multiprocess.start()
|
||||
|
||||
def stop(self, function_name):
|
||||
assert function_name in self.multiprocess_list
|
||||
self.multiprocess_list[function_name].terminate()
|
363
libs/tools.py
363
libs/tools.py
|
@ -1,363 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import ntpath
|
||||
import os
|
||||
import random
|
||||
import sys
|
||||
import traceback
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import requests
|
||||
from yuuki_core.ttypes import OpType, MIDType, ContentType, Group, Message
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .yuuki import Yuuki
|
||||
|
||||
|
||||
class StaticTools:
|
||||
@staticmethod
|
||||
def get_group_creator(group):
|
||||
"""
|
||||
Get the LINE Group Creator
|
||||
:param group: LINE Group
|
||||
:return: LINE Contact
|
||||
"""
|
||||
if group.creator is None:
|
||||
contact = group.members[0]
|
||||
else:
|
||||
contact = group.creator
|
||||
return contact
|
||||
|
||||
@staticmethod
|
||||
def read_commands(list_):
|
||||
"""
|
||||
Read string list as a command line
|
||||
:param list_: List of strings
|
||||
:return: string
|
||||
"""
|
||||
reply_msg = " ".join(list_).lstrip()
|
||||
return reply_msg
|
||||
|
||||
@staticmethod
|
||||
def report_error():
|
||||
"""
|
||||
reporting_message errors as tuple
|
||||
:return: tuple
|
||||
"""
|
||||
err1, err2, err3 = sys.exc_info()
|
||||
traceback.print_tb(err3)
|
||||
tb_info = traceback.extract_tb(err3)
|
||||
filename, line, func, text = tb_info[-1]
|
||||
error_info = f"occurred in\n{filename}\n\non line {line}\nin statement {text}"
|
||||
return err1, err2, err3, error_info
|
||||
|
||||
@staticmethod
|
||||
def security_for_where(operation):
|
||||
"""
|
||||
Return arguments for security tasks
|
||||
:param operation: Operation
|
||||
:return: tuple
|
||||
"""
|
||||
if operation.type == OpType.NOTIFIED_UPDATE_GROUP or \
|
||||
operation.type == OpType.NOTIFIED_INVITE_INTO_GROUP or \
|
||||
operation.type == OpType.NOTIFIED_ACCEPT_GROUP_INVITATION or \
|
||||
operation.type == OpType.NOTIFIED_KICKOUT_FROM_GROUP:
|
||||
return {
|
||||
"GroupID": operation.param1,
|
||||
"Action": operation.param2,
|
||||
"Another": operation.param3,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def dict_shuffle(dict_object, requirement=None):
|
||||
"""
|
||||
Shuffle dicts
|
||||
:param dict_object: dict
|
||||
:param requirement: list
|
||||
:return: dict
|
||||
"""
|
||||
dict_key = [key for key in dict_object]
|
||||
random.shuffle(dict_key)
|
||||
result = {}
|
||||
for key in dict_key:
|
||||
if requirement is None:
|
||||
result[key] = dict_object[key]
|
||||
else:
|
||||
if key in requirement:
|
||||
result[key] = dict_object[key]
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def send_to_who(operation):
|
||||
"""
|
||||
Get who to send with Operation
|
||||
:param operation: Operation
|
||||
:return: string
|
||||
"""
|
||||
if operation.message.toType == MIDType.USER:
|
||||
return operation.message.from_
|
||||
elif operation.message.toType == MIDType.ROOM:
|
||||
return operation.message.to
|
||||
elif operation.message.toType == MIDType.GROUP:
|
||||
return operation.message.to
|
||||
|
||||
|
||||
class DynamicTools:
|
||||
def __init__(self, handler: Yuuki):
|
||||
self.Yuuki = handler
|
||||
|
||||
def get_client(self, user_id):
|
||||
"""
|
||||
Get client by account user_id
|
||||
:param user_id: string
|
||||
:return: TalkServiceClient
|
||||
"""
|
||||
if user_id == self.Yuuki.MyMID:
|
||||
return self.Yuuki.client
|
||||
return self.Yuuki.Connect.helper[user_id].get("client")
|
||||
|
||||
def check_invitation(self, operation, user_id=None):
|
||||
"""
|
||||
Check If user_id in Invitation List
|
||||
:param operation: Operation
|
||||
:param user_id: string
|
||||
:return: boolean
|
||||
"""
|
||||
if user_id is None:
|
||||
user_id = self.Yuuki.MyMID
|
||||
if operation.param3 == user_id:
|
||||
return True
|
||||
elif "\x1e" in operation.param3:
|
||||
if user_id in operation.param3.split("\x1e"):
|
||||
return True
|
||||
return False
|
||||
|
||||
def switch_group_url_status(self, group, status, handler_id=None):
|
||||
"""
|
||||
Change LINE Group URL Status
|
||||
:param group: Line Group
|
||||
:param status: boolean
|
||||
:param handler_id: string
|
||||
:return: None
|
||||
"""
|
||||
result = Group()
|
||||
for key in group.__dict__:
|
||||
if key != "members" or key != "invitee":
|
||||
result.__dict__[key] = group.__dict__[key]
|
||||
result.preventJoinByTicket = not status
|
||||
handler = self.Yuuki.MyMID if handler_id is None else handler_id
|
||||
self.get_client(handler).updateGroup(self.Yuuki.Seq, result)
|
||||
|
||||
def config_security_status(self, group_id, status):
|
||||
"""
|
||||
Configure LINE Group Security Status for Yuuki
|
||||
:param group_id: string
|
||||
:param status: boolean
|
||||
:return: None
|
||||
"""
|
||||
group_status = self.Yuuki.data.SEGrouptype
|
||||
if 0 in status:
|
||||
group_status[OpType.NOTIFIED_UPDATE_GROUP] = True
|
||||
if 1 in status:
|
||||
group_status[OpType.NOTIFIED_INVITE_INTO_GROUP] = True
|
||||
if 2 in status:
|
||||
group_status[OpType.NOTIFIED_ACCEPT_GROUP_INVITATION] = True
|
||||
if 3 in status:
|
||||
group_status[OpType.NOTIFIED_KICKOUT_FROM_GROUP] = True
|
||||
|
||||
self.Yuuki.data.update_data(["Group", group_id, "SEGroup"], group_status)
|
||||
|
||||
def clean_my_group_invitations(self):
|
||||
"""
|
||||
Clean personal group invitations for LINE account
|
||||
:return: None
|
||||
"""
|
||||
for client in [self.get_client(user_id) for user_id in self.Yuuki.MyMID + self.Yuuki.Connect.helper.keys()]:
|
||||
for cleanInvitations in client.getGroupIdsInvited():
|
||||
client.acceptGroupInvitation(self.Yuuki.Seq, cleanInvitations)
|
||||
client.leave_group(self.Yuuki.Seq, cleanInvitations)
|
||||
|
||||
def leave_group(self, group):
|
||||
"""
|
||||
Leave a group by its group information object
|
||||
:param group: Line Group
|
||||
:return: None
|
||||
"""
|
||||
self.send_text(group.id, self.Yuuki.get_text("Bye Bye"))
|
||||
self.get_client(self.Yuuki.MyMID).leave_group(self.Yuuki.Seq, group.id)
|
||||
for user_id in self.Yuuki.Connect.helper:
|
||||
if user_id in [member.mid for member in group.members]:
|
||||
self.get_client(user_id).leave_group(self.Yuuki.Seq, group.id)
|
||||
group_list = self.Yuuki.data.get_data(["Global", "GroupJoined"])
|
||||
new_group_list = group_list.copy()
|
||||
new_group_list.remove(group.id)
|
||||
self.Yuuki.data.update_data(["Global", "GroupJoined"], new_group_list)
|
||||
|
||||
def get_contact(self, user_id):
|
||||
"""
|
||||
Get LINE Contact information with user_id
|
||||
:param user_id: string
|
||||
:return: LINE Contact
|
||||
"""
|
||||
if len(user_id) == len(self.Yuuki.MyMID) and user_id.startswith("u"):
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
return self.get_client(self.Yuuki.MyMID).getContact(user_id)
|
||||
except:
|
||||
return False
|
||||
return False
|
||||
|
||||
def get_group_ticket(self, group_id, user_id, renew=False):
|
||||
"""
|
||||
Get LINE Group Ticket with group_id and user_id
|
||||
:param group_id: string
|
||||
:param user_id: string
|
||||
:param renew: boolean
|
||||
:return: string
|
||||
"""
|
||||
group_ticket = ""
|
||||
group = self.Yuuki.data.get_group(group_id)
|
||||
if "GroupTicket" in group:
|
||||
if group["GroupTicket"].get(user_id) is not None:
|
||||
group_ticket = group["GroupTicket"].get(user_id)
|
||||
else:
|
||||
assert "Error JSON data type - GroupTicket"
|
||||
if group_ticket == "" or renew:
|
||||
group_ticket = self.get_client(user_id).reissuegroup_ticket(group_id)
|
||||
self.Yuuki.data.update_data(["Group", group_id, "GroupTicket", user_id], group_ticket)
|
||||
return group_ticket
|
||||
|
||||
def reset_limit(self):
|
||||
"""
|
||||
Reset Yuuki modify LINE Group Member List limits
|
||||
:return: None
|
||||
"""
|
||||
for user_id in self.Yuuki.AllAccountIds:
|
||||
self.Yuuki.data.update_data(
|
||||
["LimitInfo", "KickLimit", user_id], self.Yuuki.KickLimit)
|
||||
self.Yuuki.data.update_data(
|
||||
["LimitInfo", "CancelLimit", user_id], self.Yuuki.CancelLimit)
|
||||
|
||||
def modify_group_members(self, action, group, user_id, except_user_id=None):
|
||||
"""
|
||||
Modify LINE Group Member List
|
||||
:param action: integer (1->kick 2->cancel)
|
||||
:param group: LINE Group
|
||||
:param user_id: string
|
||||
:param except_user_id: List of user_id
|
||||
:return: string
|
||||
"""
|
||||
actions = {
|
||||
1: {
|
||||
"command": "KickLimit",
|
||||
"message": "Kick Limit.",
|
||||
"function": lambda handler_id: self.get_client(handler_id).kickoutFromGroup
|
||||
},
|
||||
2: {
|
||||
"command": "CancelLimit",
|
||||
"message": "Cancel Limit.",
|
||||
"function": lambda handler_id: self.get_client(handler_id).cancelGroupInvitation
|
||||
}
|
||||
}
|
||||
assert action in actions, "Invalid action code"
|
||||
if len(self.Yuuki.Connect.helper) >= 1:
|
||||
members = [member.mid for member in group.members if member.mid in self.Yuuki.AllAccountIds]
|
||||
accounts = StaticTools.dict_shuffle(
|
||||
self.Yuuki.data.get_data(["LimitInfo", actions[action].get("command")]), members)
|
||||
if len(accounts) == 0:
|
||||
return "None"
|
||||
if except_user_id:
|
||||
accounts[except_user_id] = -1
|
||||
helper = max(accounts, key=accounts.get)
|
||||
else:
|
||||
if except_user_id == self.Yuuki.MyMID:
|
||||
return "None"
|
||||
helper = self.Yuuki.MyMID
|
||||
limit = self.Yuuki.data.get_data(["LimitInfo", actions[action].get("command"), helper])
|
||||
if limit > 0:
|
||||
actions[action].get("function")(helper)(self.Yuuki.Seq, group.id, [user_id])
|
||||
self.Yuuki.data.trigger_limit(actions[action].get("command"), helper)
|
||||
else:
|
||||
self.send_text(group.id, self.Yuuki.get_text(actions[action].get("message")), helper)
|
||||
return helper
|
||||
|
||||
def send_text(self, send_to, msg, sender_id=None):
|
||||
"""
|
||||
Send text to LINE Chat
|
||||
:param send_to: The target to received
|
||||
:param msg: The message hope to send
|
||||
:param sender_id: The client specified to send the message
|
||||
:return: None
|
||||
"""
|
||||
message = Message(to=send_to, text=msg)
|
||||
sender = self.Yuuki.MyMID if sender_id is None else sender_id
|
||||
self.get_client(sender).sendMessage(self.Yuuki.Seq, message)
|
||||
|
||||
def send_user(self, send_to, user_id):
|
||||
"""
|
||||
Send LINE contact to LINE Chat
|
||||
:param send_to: string
|
||||
:param user_id: string
|
||||
:return: None
|
||||
"""
|
||||
message = Message(
|
||||
contentType=ContentType.CONTACT,
|
||||
text='',
|
||||
contentMetadata={
|
||||
'mid': user_id,
|
||||
'displayName': 'LINE User',
|
||||
},
|
||||
to=send_to
|
||||
)
|
||||
self.get_client(self.Yuuki.MyMID).sendMessage(self.Yuuki.Seq, message)
|
||||
|
||||
def send_media(self, send_to, send_type, path):
|
||||
"""
|
||||
Send media file to LINE Chat
|
||||
:param send_to: string
|
||||
:param send_type: string
|
||||
:param path: string
|
||||
:return: None
|
||||
"""
|
||||
if os.path.exists(path):
|
||||
file_name = ntpath.basename(path)
|
||||
file_size = len(open(path, 'rb').read())
|
||||
message = Message(to=send_to, text=None)
|
||||
message.contentType = send_type
|
||||
message.contentPreview = None
|
||||
message.contentMetadata = {
|
||||
'FILE_NAME': str(file_name),
|
||||
'FILE_SIZE': str(file_size),
|
||||
}
|
||||
if send_type == ContentType.FILE:
|
||||
media_name = file_name
|
||||
else:
|
||||
media_name = 'media'
|
||||
message_id = self.get_client(self.Yuuki.MyMID).sendMessage(
|
||||
self.Yuuki.Seq, message).id
|
||||
files = {
|
||||
'file': open(path, 'rb'),
|
||||
}
|
||||
params = {
|
||||
'name': media_name,
|
||||
'oid': message_id,
|
||||
'size': file_size,
|
||||
'type': ContentType._VALUES_TO_NAMES[send_type].lower(),
|
||||
'ver': '1.0',
|
||||
}
|
||||
data = {
|
||||
'params': json.dumps(params)
|
||||
}
|
||||
url = self.Yuuki.LINE_Media_server + '/talk/m/upload.nhn'
|
||||
r = requests.post(url, headers=self.Yuuki.Connect.con_header, data=data, files=files)
|
||||
if r.status_code != 201:
|
||||
self.send_text(send_to, "Error!")
|
|
@ -1,9 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
__all__ = ["reader", "server"]
|
|
@ -1,25 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from ..data import Data
|
||||
|
||||
|
||||
class WebDataReader:
|
||||
def __init__(self, yuuki_data: Data):
|
||||
self.handle = yuuki_data
|
||||
|
||||
def get_logs(self, name):
|
||||
if name not in self.handle.LogType:
|
||||
return {"status": 404}
|
||||
with open(f"{self.handle.LogPath}{self.handle.LogName.format(name)}") as file:
|
||||
html_doc = file.read()
|
||||
parser = BeautifulSoup(html_doc, 'html.parser')
|
||||
events = parser.find_all('li')
|
||||
return [result.string for result in events]
|
|
@ -1,258 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
import base64
|
||||
import hashlib
|
||||
import os
|
||||
import random
|
||||
import time
|
||||
from functools import wraps
|
||||
|
||||
from flask import Flask, render_template, request, redirect, jsonify
|
||||
from flask_bootstrap import Bootstrap
|
||||
from gevent.pywsgi import WSGIServer
|
||||
|
||||
from .reader import YuukiWebDataReader
|
||||
from ..tools import DynamicTools
|
||||
|
||||
wa_app = Flask(__name__)
|
||||
|
||||
Yuuki_Handle = None
|
||||
Yuuki_Handle_Data = None
|
||||
Yuuki_APIHandle_Data = None
|
||||
|
||||
passports = []
|
||||
password = str(hash(random.random()))
|
||||
|
||||
|
||||
def authorized_response(function):
|
||||
@wraps(function)
|
||||
def wrapper(*args, **kwargs):
|
||||
if "yuuki_admin" in request.cookies \
|
||||
and request.cookies["yuuki_admin"] in passports:
|
||||
return jsonify(function(*args, **kwargs))
|
||||
response = jsonify({"status": 403})
|
||||
response.set_cookie(
|
||||
key='yuuki_admin',
|
||||
value='',
|
||||
expires=0
|
||||
)
|
||||
return response
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
class WebAdmin:
|
||||
Bootstrap(wa_app)
|
||||
http_server = None
|
||||
|
||||
def __init__(self, Yuuki, port):
|
||||
global Yuuki_Handle, Yuuki_Handle_Data, Yuuki_APIHandle_Data
|
||||
Yuuki_Handle = Yuuki
|
||||
Yuuki_Handle_Data = Yuuki.data
|
||||
Yuuki_APIHandle_Data = YuukiWebDataReader(Yuuki_Handle_Data)
|
||||
self.port = port
|
||||
|
||||
@staticmethod
|
||||
def set_password(code):
|
||||
global password
|
||||
password = code
|
||||
|
||||
def wa_listen(self):
|
||||
self.http_server = WSGIServer(('', self.port), wa_app)
|
||||
self.http_server.serve_forever()
|
||||
|
||||
# HTML Server
|
||||
@staticmethod
|
||||
@wa_app.route("/")
|
||||
def index():
|
||||
status = False
|
||||
if "yuuki_admin" in request.cookies \
|
||||
and request.cookies["yuuki_admin"] in passports:
|
||||
status = True
|
||||
return render_template(
|
||||
'/index.html',
|
||||
name=Yuuki_Handle.configs["name"],
|
||||
authorized=status
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
@wa_app.route("/logout")
|
||||
def logout():
|
||||
response = redirect("/")
|
||||
if "yuuki_admin" in request.cookies:
|
||||
if request.cookies.get("yuuki_admin") in passports:
|
||||
passports.remove(request.cookies.get("yuuki_admin"))
|
||||
response.set_cookie(
|
||||
key='yuuki_admin',
|
||||
value='',
|
||||
expires=0
|
||||
)
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
@wa_app.route("/logo")
|
||||
def logo():
|
||||
default_logo_path = os.path.abspath(os.path.join(__file__, "../../../logo.png"))
|
||||
online_logo_path = "https://raw.githubusercontent.com/star-inc/star_yuuki_bot/master/logo.png"
|
||||
if os.path.isfile(default_logo_path):
|
||||
with open(default_logo_path, "rb") as f:
|
||||
return f"data:image/png;base64, {base64.b64encode(f.read()).decode('utf-8')}"
|
||||
return online_logo_path
|
||||
|
||||
# API Points
|
||||
@staticmethod
|
||||
@wa_app.route("/api/verify", methods=['POST'])
|
||||
def verify():
|
||||
if "code" in request.values:
|
||||
if request.values["code"] == password:
|
||||
seed = hash(random.random() + time.time())
|
||||
seed = str(seed).encode('utf-8')
|
||||
session_key = hashlib.sha256(seed).hexdigest()
|
||||
passports.append(session_key)
|
||||
result = jsonify({"status": 200, "session": session_key})
|
||||
result.set_cookie(
|
||||
key='yuuki_admin',
|
||||
value=session_key
|
||||
)
|
||||
return result
|
||||
return jsonify({"status": 401})
|
||||
return jsonify({"status": 403})
|
||||
|
||||
@staticmethod
|
||||
@wa_app.route("/api/profile", methods=["GET", "PUT"])
|
||||
@authorized_response
|
||||
def profile():
|
||||
if request.method == "GET":
|
||||
return {
|
||||
"id": Yuuki_Handle.profile.mid,
|
||||
"version": Yuuki_Handle.configs["version"],
|
||||
"name": Yuuki_Handle.profile.displayName,
|
||||
"status": Yuuki_Handle.profile.statusMessage,
|
||||
"picture": f"{Yuuki_Handle.LINE_Media_server}/{Yuuki_Handle.profile.pictureStatus}"
|
||||
if Yuuki_Handle.profile.pictureStatus is not None else None,
|
||||
}
|
||||
if request.method == "PUT" and "name" in request.values and "status" in request.values:
|
||||
Yuuki_Handle.profile.displayName = request.values["name"]
|
||||
Yuuki_Handle.profile.statusMessage = request.values["status"]
|
||||
DynamicTools(Yuuki_Handle).get_client(Yuuki_Handle.MyMID).updateProfile(
|
||||
Yuuki_Handle.Seq, Yuuki_Handle.profile
|
||||
)
|
||||
return {"status": 200}
|
||||
return {"status": 400}
|
||||
|
||||
@staticmethod
|
||||
@wa_app.route("/api/groups", methods=["GET", "POST", "DELETE"])
|
||||
@authorized_response
|
||||
def groups():
|
||||
if request.method == "GET":
|
||||
return Yuuki_Handle_Data.get_data(["Global", "GroupJoined"])
|
||||
if request.method == "POST" and "id" in request.values:
|
||||
return DynamicTools(Yuuki_Handle).get_client(Yuuki_Handle.MyMID).acceptGroupInvitation(
|
||||
Yuuki_Handle.Seq, request.values["id"]
|
||||
)
|
||||
if request.method == "DELETE" and "id" in request.values:
|
||||
group_information = DynamicTools(Yuuki_Handle).get_client(
|
||||
Yuuki_Handle.MyMID
|
||||
).getGroup(request.values["id"])
|
||||
return DynamicTools(Yuuki_Handle).leave_group(group_information)
|
||||
return {"status": 400}
|
||||
|
||||
@staticmethod
|
||||
@wa_app.route("/api/group/ticket", methods=["POST"])
|
||||
@authorized_response
|
||||
def group_ticket():
|
||||
if "id" in request.values and "ticket" in request.values:
|
||||
return DynamicTools(Yuuki_Handle).get_client(Yuuki_Handle.MyMID).acceptGroupInvitationByTicket(
|
||||
Yuuki_Handle.Seq, request.values["id"], request.values["ticket"]
|
||||
)
|
||||
return {"status": 400}
|
||||
|
||||
@staticmethod
|
||||
@wa_app.route("/api/groups/<id_list>")
|
||||
@authorized_response
|
||||
def groups_information(id_list=None):
|
||||
read_id_list = id_list.split(",")
|
||||
if isinstance(read_id_list, list) and len(read_id_list) > 0:
|
||||
def type_handle(obj):
|
||||
for key in obj.__dict__:
|
||||
if key == "pictureStatus" and obj.__dict__[key] is not None:
|
||||
obj.__dict__[
|
||||
key] = f"{Yuuki_Handle.LINE_Media_server}/{obj.__dict__[key]}"
|
||||
if isinstance(obj.__dict__[key], list):
|
||||
obj.__dict__[key] = list(
|
||||
map(type_handle, obj.__dict__[key]))
|
||||
if hasattr(obj.__dict__[key], '__dict__'):
|
||||
obj.__dict__[key] = obj.__dict__[key].__dict__
|
||||
return obj.__dict__
|
||||
|
||||
return [
|
||||
type_handle(obj)
|
||||
for obj in DynamicTools(Yuuki_Handle).get_client(Yuuki_Handle.MyMID).getGroups(read_id_list)
|
||||
]
|
||||
return {"status": 400}
|
||||
|
||||
@staticmethod
|
||||
@wa_app.route("/api/helpers")
|
||||
@authorized_response
|
||||
def helpers():
|
||||
def info_handle(profile):
|
||||
return {
|
||||
"id": profile.mid,
|
||||
"name": profile.displayName,
|
||||
"status": profile.statusMessage,
|
||||
"picture": f"{Yuuki_Handle.LINE_Media_server}/{profile.pictureStatus}"
|
||||
if profile.pictureStatus is not None else None,
|
||||
}
|
||||
|
||||
return [
|
||||
info_handle(Yuuki_Handle.Connect.helper[user_id].get("profile"))
|
||||
for user_id in Yuuki_Handle.Connect.helper
|
||||
]
|
||||
|
||||
@staticmethod
|
||||
@wa_app.route("/api/settings")
|
||||
@authorized_response
|
||||
def settings():
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
@wa_app.route("/api/events/<doctype>")
|
||||
@authorized_response
|
||||
def get_logs(doctype):
|
||||
return Yuuki_APIHandle_Data.get_logs(doctype)
|
||||
|
||||
@staticmethod
|
||||
@wa_app.route("/api/broadcast", methods=["POST"])
|
||||
@authorized_response
|
||||
def broadcast():
|
||||
if "message" in request.values and "audience" in request.values and request.values["message"]:
|
||||
audience_ids = {
|
||||
"groups": lambda: Yuuki_Handle_Data.get_data(
|
||||
["Global", "GroupJoined"]
|
||||
)
|
||||
}
|
||||
if request.values["audience"] not in audience_ids:
|
||||
return {"status": "404"}
|
||||
return [
|
||||
DynamicTools(Yuuki_Handle).send_text(target_id, request.values["message"])
|
||||
for target_id in audience_ids[request.values["audience"]]()
|
||||
]
|
||||
return {"status": 400}
|
||||
|
||||
@staticmethod
|
||||
@wa_app.route("/api/shutdown")
|
||||
@authorized_response
|
||||
def shutdown():
|
||||
LINE_ACCOUNT_SECURITY_NOTIFY_ID = "u085311ecd9e3e3d74ae4c9f5437cbcb5"
|
||||
# The ID belongs to an official account, which is controlled by SysOp of LINE.
|
||||
DynamicTools(Yuuki_Handle).send_text(
|
||||
LINE_ACCOUNT_SECURITY_NOTIFY_ID,
|
||||
"[Yuuki] Remote Shutdown"
|
||||
)
|
||||
return {"status": 200}
|
|
@ -1,137 +0,0 @@
|
|||
body {
|
||||
padding-top: 5rem;
|
||||
}
|
||||
|
||||
.starter-template {
|
||||
padding: 3rem 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.damage {
|
||||
color: #ff0000;
|
||||
}
|
||||
|
||||
.mini-logo {
|
||||
max-width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.password_box {
|
||||
margin: 0;
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
margin-right: -50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.router-link-exact-active {
|
||||
font-weight: 500;
|
||||
color: #343a40;
|
||||
}
|
||||
|
||||
.bd-placeholder-img {
|
||||
font-size: 1.125rem;
|
||||
text-anchor: middle;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
.bd-placeholder-img-lg {
|
||||
font-size: 3.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
html,
|
||||
body {
|
||||
overflow-x: hidden;
|
||||
/* Prevent scroll on narrow devices */
|
||||
}
|
||||
|
||||
body {
|
||||
padding-top: 56px;
|
||||
}
|
||||
|
||||
@media (max-width: 991.98px) {
|
||||
.offcanvas-collapse {
|
||||
position: fixed;
|
||||
top: 56px;
|
||||
/* Height of navbar */
|
||||
bottom: 0;
|
||||
left: 100%;
|
||||
width: 100%;
|
||||
padding-right: 1rem;
|
||||
padding-left: 1rem;
|
||||
overflow-y: auto;
|
||||
visibility: hidden;
|
||||
background-color: #343a40;
|
||||
transition: visibility .3s ease-in-out, -webkit-transform .3s ease-in-out;
|
||||
transition: transform .3s ease-in-out, visibility .3s ease-in-out;
|
||||
transition: transform .3s ease-in-out, visibility .3s ease-in-out, -webkit-transform .3s ease-in-out;
|
||||
}
|
||||
|
||||
.offcanvas-collapse.open {
|
||||
visibility: visible;
|
||||
-webkit-transform: translateX(-100%);
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
}
|
||||
|
||||
.nav-scroller {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
height: 2.75rem;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.nav-scroller .nav {
|
||||
display: -ms-flexbox;
|
||||
display: flex;
|
||||
-ms-flex-wrap: nowrap;
|
||||
flex-wrap: nowrap;
|
||||
padding-bottom: 1rem;
|
||||
margin-top: -1px;
|
||||
overflow-x: auto;
|
||||
color: rgba(255, 255, 255, .75);
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
|
||||
.nav-underline .nav-link {
|
||||
padding-top: .75rem;
|
||||
padding-bottom: .75rem;
|
||||
font-size: .875rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.nav-underline .nav-link:hover {
|
||||
color: #007bff;
|
||||
}
|
||||
|
||||
.nav-underline .active {
|
||||
font-weight: 500;
|
||||
color: #343a40;
|
||||
}
|
||||
|
||||
.text-white-50 {
|
||||
color: rgba(255, 255, 255, .5);
|
||||
}
|
||||
|
||||
.bg-purple {
|
||||
background-color: #000000;
|
||||
}
|
||||
|
||||
.lh-100 {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.lh-125 {
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.lh-150 {
|
||||
line-height: 1.5;
|
||||
}
|
|
@ -1,137 +0,0 @@
|
|||
/*
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
import Cookies from 'https://cdn.jsdelivr.net/npm/js-cookie@rc/dist/js.cookie.min.mjs';
|
||||
|
||||
import Login from "./views/Login.js";
|
||||
import Dashboard from "./views/Dashboard.js";
|
||||
import Groups from "./views/Groups.js";
|
||||
import Helpers from "./views/Helpers.js";
|
||||
import Settings from "./views/Settings.js";
|
||||
import Profile from "./views/Profile.js";
|
||||
import Events from "./views/Events.js";
|
||||
import About from "./views/About.js";
|
||||
import NotFound from "./views/NotFound.js";
|
||||
|
||||
const router = new VueRouter({
|
||||
routes: [{
|
||||
path: '/',
|
||||
component: Login
|
||||
},
|
||||
{
|
||||
path: '/dashboard',
|
||||
component: Dashboard
|
||||
},
|
||||
{
|
||||
path: '/groups',
|
||||
component: Groups
|
||||
},
|
||||
{
|
||||
path: '/helpers',
|
||||
component: Helpers
|
||||
},
|
||||
{
|
||||
path: '/settings',
|
||||
component: Settings,
|
||||
},
|
||||
{
|
||||
path: '/profile',
|
||||
component: Profile,
|
||||
},
|
||||
{
|
||||
path: '/events/:doctype',
|
||||
component: Events,
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
component: About,
|
||||
props: true
|
||||
},
|
||||
{
|
||||
path: '*',
|
||||
component: NotFound
|
||||
},
|
||||
]
|
||||
})
|
||||
|
||||
new Vue({
|
||||
template: `
|
||||
<div>
|
||||
<div v-if="accessStatus" class="nav-scroller bg-white shadow-sm pt-1 pb-1">
|
||||
<nav class="nav nav-underline">
|
||||
<router-link
|
||||
v-for="(item, target, itemId) in pageList"
|
||||
:key="itemId"
|
||||
class="nav-link"
|
||||
data-transition-enter="slideleft"
|
||||
:to="target">{{item}}</router-link>
|
||||
</nav>
|
||||
</div>
|
||||
<main id="app" role="main" class="container">
|
||||
<router-view></router-view>
|
||||
</main>
|
||||
<div class="text-center m-5">
|
||||
<a title="About SYB" @click.prevent="about" href="#">
|
||||
<svg width="25px" height="25px" viewBox="0 0 16 16" class="bi bi-info-circle" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M8 15A7 7 0 1 0 8 1a7 7 0 0 0 0 14zm0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16z"/>
|
||||
<path d="M8.93 6.588l-2.29.287-.082.38.45.083c.294.07.352.176.288.469l-.738 3.468c-.194.897.105 1.319.808 1.319.545 0 1.178-.252 1.465-.598l.088-.416c-.2.176-.492.246-.686.246-.275 0-.375-.193-.304-.533L8.93 6.588z"/>
|
||||
<circle cx="8" cy="4.5" r="1"/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
router,
|
||||
methods: {
|
||||
about() {
|
||||
if (this.$router.currentRoute.path === "/about") {
|
||||
this.$router.push({path: "/"})
|
||||
} else {
|
||||
this.$router.push({path: "/about"})
|
||||
}
|
||||
},
|
||||
verifyAccess() {
|
||||
if (this.$router.currentRoute.path === "/about") return;
|
||||
if (Cookies.get('yuuki_admin')) {
|
||||
if (this.$router.currentRoute.path === "/") {
|
||||
this.$router.push({
|
||||
path: "/dashboard"
|
||||
});
|
||||
}
|
||||
this.accessStatus = true;
|
||||
} else {
|
||||
if (this.$router.currentRoute.path !== "/") {
|
||||
this.$router.push({
|
||||
path: "/"
|
||||
});
|
||||
}
|
||||
this.accessStatus = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
$route() {
|
||||
this.verifyAccess();
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.verifyAccess();
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
accessStatus: false,
|
||||
pageList: {
|
||||
"/dashboard": "Dashboard",
|
||||
"/groups": "Groups",
|
||||
"/helpers": "Helpers",
|
||||
"/settings": "Settings"
|
||||
}
|
||||
}
|
||||
}
|
||||
}).$mount('#app')
|
|
@ -1,50 +0,0 @@
|
|||
/*
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
export default {
|
||||
template: `
|
||||
<div class="text-center my-3 p-3 bg-white rounded shadow-sm">
|
||||
<h2 class="border-bottom border-gray pb-2 mb-0">Star Yuuki BOT</h2>
|
||||
<p class="mt-3">The perfectest LINE Group Protective BOT.</p>
|
||||
<img class="rounded w-50 mt-5 mb-5" v-show="logo" :src="logo" alt="Logo" />
|
||||
<div class="mx-auto mt-3 w-75">
|
||||
<p class="bg-dark text-light rounded p-3">
|
||||
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.<br />
|
||||
If a copy of the MPL was not distributed with this file, You can obtain one at<br />
|
||||
<a class="text-light" href="http://mozilla.org/MPL/2.0/" target="_blank" rel="noreferrer noopener">http://mozilla.org/MPL/2.0/</a>.
|
||||
</p>
|
||||
<p>
|
||||
<a class="mr-1" title="Homepage" href="https://line.starinc.xyz/star-yuuki-bot/" target="_blank" rel="noreferrer noopener">
|
||||
<svg width="30px" height="30px" viewBox="0 0 16 16" class="bi bi-house-door-fill" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M6.5 10.995V14.5a.5.5 0 0 1-.5.5H2a.5.5 0 0 1-.5-.5v-7a.5.5 0 0 1 .146-.354l6-6a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 .146.354v7a.5.5 0 0 1-.5.5h-4a.5.5 0 0 1-.5-.5V11c0-.25-.25-.5-.5-.5H7c-.25 0-.5.25-.5.495z"/>
|
||||
<path fill-rule="evenodd" d="M13 2.5V6l-2-2V2.5a.5.5 0 0 1 .5-.5h1a.5.5 0 0 1 .5.5z"/>
|
||||
</svg>
|
||||
</a>
|
||||
<a class="ml-1" title="Repository" href="https://github.com/star-inc/star_yuuki_bot" target="_blank" rel="noreferrer noopener">
|
||||
<svg width="30px" height="30px" viewBox="0 0 16 16" class="bi bi-code-slash" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M4.854 4.146a.5.5 0 0 1 0 .708L1.707 8l3.147 3.146a.5.5 0 0 1-.708.708l-3.5-3.5a.5.5 0 0 1 0-.708l3.5-3.5a.5.5 0 0 1 .708 0zm6.292 0a.5.5 0 0 0 0 .708L14.293 8l-3.147 3.146a.5.5 0 0 0 .708.708l3.5-3.5a.5.5 0 0 0 0-.708l-3.5-3.5a.5.5 0 0 0-.708 0zm-.999-3.124a.5.5 0 0 1 .33.625l-4 13a.5.5 0 0 1-.955-.294l4-13a.5.5 0 0 1 .625-.33z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
<p>© 2020 <a class="text-dark" href="https://starinc.xyz" target="_blank" rel="noreferrer noopener">Star Inc.</a></p>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
logo: ""
|
||||
}
|
||||
},
|
||||
created() {
|
||||
fetch("/logo", {
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then((body) => body.text())
|
||||
.then((text) => this.logo = text)
|
||||
},
|
||||
};
|
|
@ -1,148 +0,0 @@
|
|||
/*
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
export default {
|
||||
template: `
|
||||
<div>
|
||||
<div :title="profileId" class="d-flex align-items-center p-3 my-3 text-white-50 bg-purple rounded shadow-sm">
|
||||
<img v-if="profilePicture" width="48" height="48" class="mr-3" :src="profilePicture">
|
||||
<svg v-else width="48" height="48" viewBox="0 0 16 16" class="mr-3" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
||||
</svg>
|
||||
<div class="lh-100">
|
||||
<h6 class="mb-0 text-white lh-100">{{ profileName }}</h6>
|
||||
<small>{{ version }}</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="my-3 p-3 bg-white rounded shadow-sm" method="POST">
|
||||
<h6 class="border-bottom border-gray pb-2 mb-0">Broadcast</h6>
|
||||
<textarea v-model="broadcastText" class="form-control" cols="50" rows="5" placeholder="Type any text message for announcing..." :disabled="broadcastStatus"></textarea>
|
||||
<div class="mt-1">
|
||||
<label>Audience: </label>
|
||||
<label class="checkbox-inline">
|
||||
<input v-model="broadcastAudience.contacts" type="checkbox" id="inlineCheckbox1" value="option1" disabled> Contacts
|
||||
</label>
|
||||
<label class="checkbox-inline">
|
||||
<input v-model="broadcastAudience.groups" type="checkbox" id="inlineCheckbox2" value="option2" :disabled="broadcastStatus" > Groups
|
||||
</label>
|
||||
</div>
|
||||
<input @click.prevent="broadcast" class="form-control text-light bg-primary mt-1" type="submit" value="Send" :disabled="broadcastStatus" />
|
||||
</form>
|
||||
|
||||
<div class="my-3 p-3 bg-white rounded shadow-sm">
|
||||
<h6 class="border-bottom border-gray pb-2 mb-0">Recent updates</h6>
|
||||
<router-link
|
||||
v-for="(event, eventKey, eventIndex) in events"
|
||||
:key="eventIndex"
|
||||
class="media text-muted pt-3"
|
||||
:to="event.href">
|
||||
<svg class="bd-placeholder-img mr-2 rounded" width="32" height="32" xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="xMidYMid slice" focusable="false" role="img" aria-label="Placeholder: 32x32">
|
||||
<title>{{event.title}}</title>
|
||||
<rect width="100%" height="100%" :fill="event.color"/>
|
||||
<text x="50%" y="50%" :fill="event.color" dy=".3em">32x32</text>
|
||||
</svg>
|
||||
<p class="media-body pb-3 mb-0 small lh-125 border-bottom border-gray">
|
||||
<strong class="d-block text-gray-dark">[{{event.title}}]</strong>
|
||||
<span>{{getPreview(event)}}</span>
|
||||
</p>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
methods: {
|
||||
getPreview(event) {
|
||||
return "preview" in event ? event.preview : "Loading...";
|
||||
},
|
||||
async broadcast() {
|
||||
if (!this.broadcastText) return alert("Empty message");
|
||||
const checkpoint = confirm("The message will be broadcast, are you sure?");
|
||||
if (!checkpoint) return;
|
||||
this.broadcastStatus = true;
|
||||
let body = new FormData();
|
||||
body.set("message", this.broadcastText);
|
||||
await Promise.all(Object.keys(this.broadcastAudience).map((target) => {
|
||||
if (this.broadcastAudience[target]) {
|
||||
body.set("audience", target);
|
||||
return fetch("/api/broadcast", {
|
||||
method: "POST",
|
||||
body
|
||||
});
|
||||
}
|
||||
}));
|
||||
this.broadcastText = "";
|
||||
this.broadcastStatus = false;
|
||||
},
|
||||
async fetchEventPreview(eventKey) {
|
||||
const result = await this.getEventInfo(eventKey);
|
||||
if (result.length > 0) {
|
||||
this.$set(this.events[eventKey], "preview", result[result.length - 1]);
|
||||
} else {
|
||||
this.$set(this.events[eventKey], "preview", "(empty)");
|
||||
}
|
||||
},
|
||||
async getEventInfo(eventKey) {
|
||||
return await new Promise((resolve, reject) => fetch(`/api/events/${eventKey}`, {
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then((body) => body.json())
|
||||
.catch(reject)
|
||||
.then(resolve));
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
version: "",
|
||||
profileId: "",
|
||||
profileName: "",
|
||||
profilePicture: "",
|
||||
broadcastStatus: false,
|
||||
broadcastText: "",
|
||||
broadcastAudience: {
|
||||
contacts: false,
|
||||
groups: false,
|
||||
},
|
||||
events: {
|
||||
JoinGroup: {
|
||||
title: "Join Event",
|
||||
color: "#6f42c1",
|
||||
href: "/events/JoinGroup"
|
||||
},
|
||||
BlackList: {
|
||||
title: "Block Event",
|
||||
color: "#007bff",
|
||||
href: "/events/BlackList"
|
||||
},
|
||||
KickEvent: {
|
||||
title: "Kick Event",
|
||||
color: "#e83e8c",
|
||||
href: "/events/KickEvent"
|
||||
},
|
||||
CancelEvent: {
|
||||
title: "Cancel Event",
|
||||
color: "#00ff00",
|
||||
href: "/events/CancelEvent"
|
||||
},
|
||||
},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
fetch("/api/profile", {
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then((body) => body.json())
|
||||
.then((profile) => {
|
||||
this.profileId = profile.id;
|
||||
this.version = profile.version;
|
||||
this.profileName = profile.name;
|
||||
this.profilePicture = profile.picture;
|
||||
});
|
||||
Object.keys(this.events).forEach(this.fetchEventPreview)
|
||||
},
|
||||
};
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
export default {
|
||||
template: `
|
||||
<div class="my-3 p-3 bg-white rounded shadow-sm">
|
||||
<h6 class="border-bottom border-gray pb-2 mb-0">Event: {{doctype}}</h6>
|
||||
<div id="events">
|
||||
<div
|
||||
v-for="(event, eventIndex) in events"
|
||||
:key="eventIndex"
|
||||
class="media pt-3">
|
||||
<p class="media-body pb-3 mb-0 small lh-125 border-bottom border-gray">
|
||||
<strong class="d-block text-gray-dark">{{event.title}}</strong>
|
||||
{{event.content}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
props: ["doctype"],
|
||||
data() {
|
||||
return {
|
||||
events: [{title: "Loading..."}]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
fetch(`/api/events/${this.doctype}`, {
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then((body) => body.json())
|
||||
.then((events) => {
|
||||
if (events.length) {
|
||||
this.events = events.map((event) => {
|
||||
if (!event) return {title: "(unknown)"};
|
||||
return {
|
||||
title: event.substring(0, 24),
|
||||
content: event.substring(26, event.length)
|
||||
};
|
||||
});
|
||||
} else {
|
||||
this.events = [{title: "(empty)"}];
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
|
@ -1,100 +0,0 @@
|
|||
/*
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
export default {
|
||||
template: `
|
||||
<div class="my-3 p-3 bg-white rounded shadow-sm">
|
||||
<h6 class="border-bottom border-gray pb-2 mb-0">Groups</h6>
|
||||
<div id="groups">
|
||||
<div
|
||||
v-for="(group, groupIndex) in groupList"
|
||||
:key="groupIndex"
|
||||
:title="group.id"
|
||||
class="media pt-3">
|
||||
<img v-if="group.pictureStatus" class="mini-logo mr-2 rounded" :src="group.pictureStatus">
|
||||
<svg v-else viewBox="0 0 16 16" class="mini-logo mr-2 rounded" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M2.678 11.894a1 1 0 0 1 .287.801 10.97 10.97 0 0 1-.398 2c1.395-.323 2.247-.697 2.634-.893a1 1 0 0 1 .71-.074A8.06 8.06 0 0 0 8 14c3.996 0 7-2.807 7-6 0-3.192-3.004-6-7-6S1 4.808 1 8c0 1.468.617 2.83 1.678 3.894zm-.493 3.905a21.682 21.682 0 0 1-.713.129c-.2.032-.352-.176-.273-.362a9.68 9.68 0 0 0 .244-.637l.003-.01c.248-.72.45-1.548.524-2.319C.743 11.37 0 9.76 0 8c0-3.866 3.582-7 8-7s8 3.134 8 7-3.582 7-8 7a9.06 9.06 0 0 1-2.347-.306c-.52.263-1.639.742-3.468 1.105z"/>
|
||||
</svg>
|
||||
<p class="media-body pb-3 mb-0 small lh-125 border-bottom border-gray">
|
||||
<strong class="d-block text-gray-dark">{{group.name}}</strong>
|
||||
{{getGroupStatus(group)}}
|
||||
</p>
|
||||
<p v-if="!checkSystemMessage(group)">
|
||||
<a href="#" class="text-danger" title="Leave" @click.prevent="leave_group(group.id)">
|
||||
<svg width="30px" height="30px" viewBox="0 0 16 16" class="bi bi-door-open" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M1 15.5a.5.5 0 0 1 .5-.5h13a.5.5 0 0 1 0 1h-13a.5.5 0 0 1-.5-.5zM11.5 2H11V1h.5A1.5 1.5 0 0 1 13 2.5V15h-1V2.5a.5.5 0 0 0-.5-.5z"/>
|
||||
<path fill-rule="evenodd" d="M10.828.122A.5.5 0 0 1 11 .5V15h-1V1.077l-6 .857V15H3V1.5a.5.5 0 0 1 .43-.495l7-1a.5.5 0 0 1 .398.117z"/>
|
||||
<path d="M8 9c0 .552.224 1 .5 1s.5-.448.5-1-.224-1-.5-1-.5.448-.5 1z"/>
|
||||
</svg>
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
methods: {
|
||||
checkSystemMessage(group) {
|
||||
return group.hasOwnProperty("systemMessage") && group.systemMessage
|
||||
},
|
||||
getGroupStatus(group) {
|
||||
if (this.checkSystemMessage(group)) {
|
||||
return "";
|
||||
}
|
||||
const membersCount = group.members ? group.members.length : 0
|
||||
const inviteeCount = group.invitee ? group.invitee.length : 0
|
||||
return `Members:${membersCount} Invites:${inviteeCount}`;
|
||||
},
|
||||
leave_group(group_id) {
|
||||
const checkpoint = confirm("The group will be removed from the BOT, are you sure?");
|
||||
if (!checkpoint) return;
|
||||
let body = new FormData();
|
||||
body.append("id", group_id);
|
||||
fetch("/api/groups", {
|
||||
method: "DELETE",
|
||||
body
|
||||
});
|
||||
const deleteIndex = this.groupList.findIndex(group => group.id === group_id);
|
||||
this.groupList.splice(deleteIndex, 1);
|
||||
},
|
||||
async fetchGroupsJoined() {
|
||||
return await new Promise((resolve, reject) => fetch("/api/groups", {
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then((body) => body.json())
|
||||
.catch(reject)
|
||||
.then(resolve));
|
||||
},
|
||||
async fetchGroupsInfo(group_ids) {
|
||||
return await new Promise((resolve, reject) => fetch(`/api/groups/${group_ids.join(',')}`, {
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then((body) => body.json())
|
||||
.catch(reject)
|
||||
.then(resolve));
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
groupList: [{
|
||||
name: "Loading...",
|
||||
systemMessage: true
|
||||
}]
|
||||
}
|
||||
},
|
||||
async created() {
|
||||
const groupJoined = await this.fetchGroupsJoined();
|
||||
if (groupJoined.length) {
|
||||
this.groupList = await this.fetchGroupsInfo(groupJoined);
|
||||
} else {
|
||||
this.groupList = [{
|
||||
name: "(empty)",
|
||||
systemMessage: true
|
||||
}];
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,45 +0,0 @@
|
|||
/*
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
export default {
|
||||
template: `
|
||||
<div class="my-3 p-3 bg-white rounded shadow-sm">
|
||||
<h6 class="border-bottom border-gray pb-2 mb-0">Helper</h6>
|
||||
<div id="helpers">
|
||||
<div
|
||||
v-for="(helper, helperIndex) in helperList"
|
||||
:key="helperIndex"
|
||||
:title="helper.id"
|
||||
class="media pt-3">
|
||||
<img v-if="helper.picture" class="mini-logo mr-2 rounded" :src="helper.picture">
|
||||
<svg v-else viewBox="0 0 16 16" class="mini-logo mr-2 rounded" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
||||
</svg>
|
||||
<p class="media-body pb-3 mb-0 small lh-125 border-bottom border-gray">
|
||||
<strong class="d-block text-gray-dark">{{helper.name}}</strong>
|
||||
{{helper.status}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
helperList: [{name: "Loading..."}]
|
||||
}
|
||||
},
|
||||
created() {
|
||||
fetch("/api/helpers", {
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then((body) => body.json())
|
||||
.then((helper_list) => {
|
||||
this.helperList = helper_list.length ? helper_list : [{name: "(empty)"}];
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
export default {
|
||||
template: `
|
||||
<div>
|
||||
<div class="starter-template">
|
||||
<h1>Star Yuuki BOT - WebAdmin</h1>
|
||||
<p class="lead">Perfect LINE BOT for Groups Protection.</p>
|
||||
<p>The administrator control center of LINE BOT</p>
|
||||
<div class="status damage"></div>
|
||||
</div>
|
||||
|
||||
<div class="password_box">
|
||||
<form class="form-inline mt-2 mt-md-0 login-box" method="post">
|
||||
<input v-model="password" class="form-control mr-sm-2" type="password" placeholder="Type your admin password" aria-label="Login" name="code" />
|
||||
<button @click.prevent="authorize" class="btn btn-outline-success my-2 my-sm-0" type="submit">Login</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
data() {
|
||||
return {
|
||||
password: ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
authorize() {
|
||||
let body = new FormData();
|
||||
body.append("code", this.password);
|
||||
fetch("/api/verify", {
|
||||
method: "POST",
|
||||
body
|
||||
})
|
||||
.then(body => body.json())
|
||||
.then(data => {
|
||||
if (data.status == 200) {
|
||||
location.reload();
|
||||
} else {
|
||||
$(".status").text("Wrong password");
|
||||
$(".status").fadeIn();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
/*
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
export default {
|
||||
template: `
|
||||
<div class="my-3 p-3 bg-white rounded shadow-sm">
|
||||
<h2 class="border-bottom border-gray pb-2 mb-0">Not Found</h2>
|
||||
<p class="mt-3">The path requested is not existed.</p>
|
||||
</div>
|
||||
`,
|
||||
};
|
|
@ -1,84 +0,0 @@
|
|||
/*
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
export default {
|
||||
template: `
|
||||
<div class="text-center my-3 card">
|
||||
<div class="mx-auto w-100 p-5 card-header" >
|
||||
<img v-if="profilePicture" width="128" height="128" class="mr-2 rounded" :src="profilePicture">
|
||||
<svg v-else width="128" height="128" viewBox="0 0 16 16" class="mr-2 rounded" fill="currentColor" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1H3zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<div v-if="!modify">
|
||||
<div class="mx-auto mt-3 card-body">
|
||||
<h6 class="text-dark lh-100">{{ profileName }}</h6>
|
||||
<p class="mt-3 mb-3 text-secondary" v-html="statusMessage"></p>
|
||||
<button class="btn btn-primary" @click="switchButton">Modify</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="mx-auto mt-3 card-body">
|
||||
<input class="form-control text-dark lh-100" v-model="profileName" maxlength="20" />
|
||||
<textarea class="form-control mt-3 mb-3 text-secondary" v-model="profileStatus" maxlength="1000"></textarea>
|
||||
<button class="btn btn-primary" @click="switchButton">Save</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
methods: {
|
||||
switchButton() {
|
||||
if (this.modify) {
|
||||
let body = new FormData();
|
||||
body.append("name", this.profileName);
|
||||
body.append("status", this.profileStatus);
|
||||
fetch("/api/profile", {
|
||||
method: "PUT",
|
||||
body
|
||||
});
|
||||
}
|
||||
this.modify = !this.modify;
|
||||
},
|
||||
escapeHtml(text) {
|
||||
let map = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
};
|
||||
return text.replace(/[&<>"']/g, function (m) {
|
||||
return map[m];
|
||||
});
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
statusMessage() {
|
||||
return this.escapeHtml(this.profileStatus).replace(/\n/g, "<br />");
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
profileName: "",
|
||||
profileStatus: "",
|
||||
profilePicture: "",
|
||||
modify: false
|
||||
}
|
||||
},
|
||||
created() {
|
||||
fetch("/api/profile", {
|
||||
credentials: "same-origin"
|
||||
})
|
||||
.then((body) => body.json())
|
||||
.then((profile) => {
|
||||
this.profileName = profile.name;
|
||||
this.profileStatus = profile.status;
|
||||
this.profilePicture = profile.picture;
|
||||
});
|
||||
}
|
||||
};
|
|
@ -1,68 +0,0 @@
|
|||
/*
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
export default {
|
||||
template: `
|
||||
<div class="my-3 p-3 bg-white rounded shadow-sm">
|
||||
<p v-if="shutdownStatus" class="my-3 p-3 bg-white">Disconnected.</p>
|
||||
<div v-else>
|
||||
<h6 class="border-bottom border-gray pb-2 mb-0">Settings</h6>
|
||||
<a
|
||||
v-for="(setting, settingKey, settingIndex) in settings"
|
||||
:key="settingIndex"
|
||||
class="media text-muted pt-3"
|
||||
href="#"
|
||||
@click.prevent="setting.action">
|
||||
<svg class="bd-placeholder-img mr-2 rounded" width="32" height="32" xmlns="http://www.w3.org/2000/svg"
|
||||
preserveAspectRatio="xMidYMid slice" focusable="false" role="img" aria-label="Placeholder: 32x32">
|
||||
<title>{{settingKey}}</title>
|
||||
<rect width="100%" height="100%" :fill="setting.color"/>
|
||||
<text x="50%" y="50%" :fill="setting.color" dy=".3em">32x32</text>
|
||||
</svg>
|
||||
<p class="media-body pb-3 mb-0 small lh-125 border-bottom border-gray">
|
||||
<strong class="d-block text-gray-dark">{{settingKey}}</strong>
|
||||
<span id="Join_setting">{{setting.preview}}</span>
|
||||
</p>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
methods: {
|
||||
shutdown() {
|
||||
this.shutdownStatus = true;
|
||||
fetch(`/api/shutdown`, {
|
||||
credentials: "same-origin"
|
||||
});
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
shutdownStatus: false,
|
||||
settings: {
|
||||
Profile: {
|
||||
color: "#007bff",
|
||||
icon: "#6f42c1",
|
||||
preview: "Edit LINE profile of the console BOT.",
|
||||
action: () => this.$router.push({path: "/profile"})
|
||||
},
|
||||
"Yuuki Configure": {
|
||||
color: "#e83e8c",
|
||||
icon: "#6f42c1",
|
||||
preview: "Settings for the BOT works.",
|
||||
action: () => alert("Unavailable")
|
||||
},
|
||||
Shutdown: {
|
||||
color: "#6f42c1",
|
||||
icon: "#6f42c1",
|
||||
preview: "Turn off the BOT.",
|
||||
action: this.shutdown
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
|
@ -1,47 +0,0 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<!-- Required meta tags -->
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
|
||||
<!-- Bootstrap CSS -->
|
||||
{{ bootstrap.load_css() }}
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='css/main.css') }}"/>
|
||||
|
||||
<title>Star Yuuki BOT - WebAdmin</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
{% from 'bootstrap/nav.html' import render_nav_item %}
|
||||
|
||||
<nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
|
||||
<a class="navbar-brand" href="/">{{ name }} - WebAdmin</a>
|
||||
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse"
|
||||
aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
{{ render_nav_item('index') }}
|
||||
</ul>
|
||||
{% if authorized %}
|
||||
<a class="navbar-brand" href="/logout">
|
||||
<button class="btn btn-outline-success my-2 my-sm-0" type="submit">Logout</button>
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main id="app"></main>
|
||||
|
||||
<!-- Optional JavaScript -->
|
||||
{{ bootstrap.load_js() }}
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue-router/dist/vue-router.min.js"></script>
|
||||
<script type="module" src="{{ url_for('static', filename='js/main.js') }}"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
188
libs/yuuki.py
188
libs/yuuki.py
|
@ -1,188 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
Yuuki_Libs
|
||||
(c) 2020 Star Inc.
|
||||
This Source Code Form is subject to the terms of the Mozilla Public
|
||||
License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import platform
|
||||
import random
|
||||
import sys
|
||||
import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from git import Repo
|
||||
from yuuki_core.ttypes import OpType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .config import Config
|
||||
from .connection import Connect
|
||||
from .data import Data
|
||||
from .events import YuukiCommand, YuukiJoinGroup, YuukiSecurity, YuukiCallback
|
||||
from .i18n import LangSetting
|
||||
from .poll import Poll
|
||||
from .thread import Multiprocess
|
||||
from .webadmin.server import WebAdmin
|
||||
|
||||
|
||||
class Yuuki:
|
||||
def __init__(self, yuuki_config: Config):
|
||||
|
||||
self.Connect = Connect(yuuki_config)
|
||||
self.configs = yuuki_config.system_config
|
||||
|
||||
# Static_Variable
|
||||
self.Thread_Control = Multiprocess()
|
||||
|
||||
self.Seq = self.configs["Seq"]
|
||||
self.Admin = self.configs["Admin"]
|
||||
self.Threading = self.configs["Advanced"]
|
||||
self.KickLimit = self.configs["Hour_KickLimit"]
|
||||
self.CancelLimit = self.configs["Hour_CancelLimit"]
|
||||
self.i18n = LangSetting(self.configs["Default_Language"])
|
||||
|
||||
self.LINE_Media_server = "https://obs.line-apps.com"
|
||||
|
||||
self._version_check()
|
||||
|
||||
def _version_check(self):
|
||||
git_result = "Unknown"
|
||||
origin_url = "https://github.com/star-inc/star_yuuki_bot.git"
|
||||
|
||||
if self.configs["version_check"]:
|
||||
git_remote = Repo('.').remote()
|
||||
update_status = git_remote.fetch()[0]
|
||||
if update_status.flags == 64:
|
||||
git_result = "New version found."
|
||||
elif update_status.flags == 4:
|
||||
git_result = "This is the latest version."
|
||||
origin_url = "\n".join(git_remote.urls)
|
||||
|
||||
return self._announce_dog(git_result, origin_url)
|
||||
|
||||
def _announce_dog(self, git_result, origin_url):
|
||||
print(
|
||||
"\n{} {}\n"
|
||||
"\t===\n\n"
|
||||
"<*> {}\n\n"
|
||||
"Update Origin:\n"
|
||||
"\t{}\n\n\t\t\t\t\t"
|
||||
"{}\n\t{}\n".format(
|
||||
self.configs["name"],
|
||||
self.configs["version"],
|
||||
git_result,
|
||||
origin_url,
|
||||
self.configs["copyright"],
|
||||
"\t==" * 5
|
||||
)
|
||||
)
|
||||
self._login_line()
|
||||
|
||||
def _login_line(self):
|
||||
(self.client, self.listen) = self.Connect.connect()
|
||||
|
||||
if self.configs.get("helper_LINE_ACCESS_KEYs"):
|
||||
for access in self.configs["helper_LINE_ACCESS_KEYs"]:
|
||||
self.Connect.helper_connect(access)
|
||||
|
||||
# Dynamic Variable
|
||||
self.get_text = self.i18n.gettext
|
||||
|
||||
self.JoinGroup = YuukiJoinGroup(self).action
|
||||
self.Command = YuukiCommand(self).action
|
||||
self.Security = YuukiSecurity(self).action
|
||||
self.Callback = YuukiCallback(self).action
|
||||
|
||||
mds_port = self.configs["MDS_Port"]
|
||||
self.data = Data(self.Threading, mds_port)
|
||||
|
||||
self.data.update_data(["Global", "GroupJoined"], self.client.getGroupIdsJoined())
|
||||
self.data.update_data(["Global", "SecurityService"], self.configs["SecurityService"])
|
||||
self._initialize()
|
||||
|
||||
def _initialize(self):
|
||||
self.profile = self.client.getProfile()
|
||||
self.MyMID = self.profile.mid
|
||||
self.revision = self.client.getLastOpRevision()
|
||||
|
||||
self.AllAccountIds = [self.MyMID]
|
||||
|
||||
for user_id in self.Connect.helper:
|
||||
self.AllAccountIds.append(user_id)
|
||||
|
||||
if len(self.data.get_data(["LimitInfo"])) != 2:
|
||||
self.data.update_data(["LimitInfo"], self.data.LimitType)
|
||||
self._setup_web_admin()
|
||||
|
||||
def _setup_web_admin(self):
|
||||
if self.Threading and self.configs.get("WebAdmin"):
|
||||
wa_port = self.configs["WebAdmin_Port"]
|
||||
password = str(hash(random.random()))
|
||||
self.web_admin = WebAdmin(self, wa_port)
|
||||
self.web_admin.set_password(password)
|
||||
self.Thread_Control.add(self.web_admin.wa_listen)
|
||||
print(
|
||||
"<*> Yuuki WebAdmin - Enable\n"
|
||||
"<*> http://localhost:{}\n"
|
||||
"<*> Password: {}\n".format(wa_port, password)
|
||||
)
|
||||
else:
|
||||
print("<*> Yuuki WebAdmin - Disable\n")
|
||||
|
||||
def exit(self, restart=False):
|
||||
print("System Exit")
|
||||
while self.data.sync_data():
|
||||
self.data.update_data(["Global", "Power"], False)
|
||||
if self.Threading:
|
||||
self.data.mds_shake("EXT", None, None)
|
||||
time.sleep(1)
|
||||
self.data.MdsThreadControl.stop("mds_listen")
|
||||
if self.configs.get("WebAdmin"):
|
||||
self.data.MdsThreadControl.stop("wa_listen")
|
||||
if restart:
|
||||
self.__restart()
|
||||
sys.exit(0)
|
||||
|
||||
@staticmethod
|
||||
def __restart():
|
||||
if platform.system() == "Windows":
|
||||
with open("cache.bat", "w") as c:
|
||||
c.write(sys.executable + " ./main.py")
|
||||
os.system("cache.bat")
|
||||
os.system("del cache.bat")
|
||||
elif platform.system() == "Linux":
|
||||
with open(".cache.sh", "w") as c:
|
||||
c.write(sys.executable + " ./main.py")
|
||||
os.system("sh .cache.sh")
|
||||
os.system("rm .cache.sh")
|
||||
else:
|
||||
print("Star Yuuki BOT - Restart Error\n\nUnknown Platform")
|
||||
|
||||
def thread_append(self, function, args):
|
||||
if self.Threading:
|
||||
self.Thread_Control.add(function, args)
|
||||
else:
|
||||
function(*args)
|
||||
|
||||
def task_executor(self, operations):
|
||||
for operation in operations:
|
||||
if operation.type == OpType.NOTIFIED_INVITE_INTO_GROUP:
|
||||
self.thread_append(self.JoinGroup, (operation,))
|
||||
elif operation.type == OpType.NOTIFIED_KICKOUT_FROM_GROUP:
|
||||
self.thread_append(self.Security, (operation,))
|
||||
elif operation.type == OpType.NOTIFIED_ACCEPT_GROUP_INVITATION:
|
||||
self.thread_append(self.Security, (operation,))
|
||||
elif operation.type == OpType.NOTIFIED_UPDATE_GROUP:
|
||||
self.thread_append(self.Security, (operation,))
|
||||
elif operation.type == OpType.RECEIVE_MESSAGE:
|
||||
self.thread_append(self.Command, (operation,))
|
||||
elif operation.type == OpType.SEND_MESSAGE:
|
||||
self.thread_append(self.Callback, (operation,))
|
||||
|
||||
def main(self):
|
||||
handle = Poll(self)
|
||||
handle.init()
|
10
main.py
10
main.py
|
@ -6,17 +6,11 @@
|
|||
This is a main program in SYB.
|
||||
It`s belong to Star Yuuki(pYthon) Bot Project of Star Neptune Bot
|
||||
|
||||
Version: v6.5.3
|
||||
Version: v6.6.0
|
||||
|
||||
Copyright(c) 2020 Star Inc. All Rights Reserved.
|
||||
Copyright(c) 2021 Star Inc. All Rights Reserved.
|
||||
The software licensed under Mozilla Public License Version 2.0
|
||||
"""
|
||||
|
||||
from libs import Yuuki, Config
|
||||
|
||||
config = Config()
|
||||
Console = Yuuki(config)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("Star Yuuki BOT - Start Successful!")
|
||||
Console.main()
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
beautifulsoup4==4.9.3
|
||||
Bootstrap-Flask==1.5
|
||||
certifi==2020.6.20
|
||||
chardet==3.0.4
|
||||
click==7.1.2
|
||||
Flask==1.1.2
|
||||
gevent==20.9.0
|
||||
gitdb2==4.0.2
|
||||
GitPython==3.1.9
|
||||
greenlet==0.4.17
|
||||
idna==2.10
|
||||
itsdangerous==1.1.0
|
||||
PyYAML==5.3.1
|
||||
requests==2.24.0
|
||||
six==1.15.0
|
||||
smmap2==3.0.1
|
||||
soupsieve==2.0.1
|
||||
thrift==0.13.0
|
||||
tornado==6.0.4
|
||||
urllib3==1.25.10
|
||||
Werkzeug==1.0.1
|
||||
yuuki-core==6.5.2
|
||||
zope.event==4.5.0
|
||||
zope.interface==5.1.2
|
Loading…
Reference in a new issue