Turn PEP8 branch to v6.6

This commit is contained in:
SuperSonic 2021-06-26 06:48:44 +08:00
parent 6a63836de0
commit 452dcfc566
No known key found for this signature in database
GPG key ID: E511B80256C9F20D
35 changed files with 3 additions and 3543 deletions

View file

@ -79,4 +79,4 @@ Welcome to help us for improving and making `Yuuki` better!
Copyright of `logo.png` belongs to "[©川原 礫ASCII Media WorksSAO Project](https://www.aniplex.co.jp)" and its artists. Copyright of `logo.png` belongs to "[©川原 礫ASCII Media WorksSAO Project](https://www.aniplex.co.jp)" and its artists.
> (c) 2020 Star Inc. > (c) 2021 Star Inc.

View file

@ -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']

View file

@ -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"]

View file

@ -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,))

View file

@ -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

View file

@ -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"]

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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}"

View file

@ -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"
}

View file

@ -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": "已增加"
}

View file

@ -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()

View file

@ -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()

View file

@ -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()

View file

@ -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!")

View file

@ -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"]

View file

@ -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]

View file

@ -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}

View file

@ -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;
}

View file

@ -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')

View file

@ -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>&copy; 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)
},
};

View file

@ -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)
},
};

View file

@ -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)"}];
}
});
},
};

View file

@ -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
}];
}
}
};

View file

@ -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)"}];
});
}
};

View file

@ -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();
}
});
}
}
};

View file

@ -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>
`,
};

View file

@ -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 = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;",
};
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;
});
}
};

View file

@ -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
},
}
}
}
};

View file

@ -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>

View file

@ -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
View file

@ -6,17 +6,11 @@
This is a main program in SYB. This is a main program in SYB.
It`s belong to Star Yuuki(pYthon) Bot Project of Star Neptune Bot 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 The software licensed under Mozilla Public License Version 2.0
""" """
from libs import Yuuki, Config
config = Config()
Console = Yuuki(config)
if __name__ == "__main__": if __name__ == "__main__":
print("Star Yuuki BOT - Start Successful!") print("Star Yuuki BOT - Start Successful!")
Console.main()

View file

@ -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