Update WA and resolved several bugs

This commit is contained in:
SuperSonic 2020-10-18 01:25:06 +08:00
parent 2d3b094b3b
commit b9cd353f7f
No known key found for this signature in database
GPG key ID: E511B80256C9F20D
11 changed files with 184 additions and 83 deletions

View file

@ -130,15 +130,11 @@ class Yuuki_DynamicTools:
if userId is None:
userId = self.Yuuki.MyMID
if ncMessage.param3 == userId:
inList = True
return True
elif "\x1e" in ncMessage.param3:
if userId in ncMessage.param3.split("\x1e"):
inList = True
else:
inList = False
else:
inList = False
return inList
return True
return False
def changeGroupUrlStatus(self, groupInfo, status, userId=None):
"""
@ -244,12 +240,21 @@ class Yuuki_DynamicTools:
:param exceptUserId: List of userId
:return: string
"""
actions = {1: "KickLimit", 2: "CancelLimit"}
actions = {
1: {
"command": "KickLimit",
"message": "Kick Limit."
},
2: {
"command": "CancelLimit",
"message": "Cancel Limit."
}
}
assert action in actions, "Invalid action code"
if len(self.Yuuki.Connect.helper) >= 1:
members = [member.mid for member in groupInfo.members if member.mid in self.Yuuki.AllAccountIds]
accounts = Yuuki_StaticTools.dictShuffle(
self.Yuuki.data.getData(["LimitInfo", actions[action]]), members)
self.Yuuki.data.getData(["LimitInfo", actions[action].get("command")]), members)
if len(accounts) == 0:
return "None"
if exceptUserId:
@ -264,12 +269,12 @@ class Yuuki_DynamicTools:
1: self.getClient(helper).kickoutFromGroup,
2: self.getClient(helper).cancelGroupInvitation
}
Limit = self.Yuuki.data.getData(["LimitInfo", actions[action], helper])
Limit = self.Yuuki.data.getData(["LimitInfo", actions[action].get("command"), helper])
if Limit > 0:
actions_func[action](self.Yuuki.Seq, groupInfo.id, [userId])
self.Yuuki.data.limitDecrease(actions[action], helper)
self.Yuuki.data.limitDecrease(actions[action].get("command"), helper)
else:
self.sendText(groupInfo.id, self.Yuuki.get_text("Cancel Limit."))
self.sendText(groupInfo.id, self.Yuuki.get_text(actions[action].get("message")))
return helper
def sendText(self, send_to, msg):

View file

@ -26,7 +26,6 @@ Yuuki_APIHandle_Data = None
passports = []
password = str(hash(random.random()))
shutdown_password = str(hash(random.random()))
def authorized_response(function):
@ -60,19 +59,10 @@ class Yuuki_WebAdmin:
global password
password = code
@staticmethod
def set_shutdown_password(code):
global shutdown_password
shutdown_password = code
def wa_listen(self):
self.http_server = WSGIServer(('', 2020), wa_app)
self.http_server.serve_forever()
def wa_shutdown(self):
self.http_server.stop()
self.http_server.close()
# HTML Server
@staticmethod
@wa_app.route("/")
@ -121,14 +111,24 @@ class Yuuki_WebAdmin:
return jsonify({"status": 403})
@staticmethod
@wa_app.route("/api/profile")
@wa_app.route("/api/profile", methods=["GET", "PUT"])
@authorized_response
def profile():
return {
"version": Yuuki_Handle.YuukiConfigs["version"],
"name": Yuuki_Handle.profile.displayName,
"picture": f"{Yuuki_Handle.LINE_Media_server}/{Yuuki_Handle.profile.pictureStatus}"
}
if request.method == "GET":
return {
"version": Yuuki_Handle.YuukiConfigs["version"],
"name": Yuuki_Handle.profile.displayName,
"status": Yuuki_Handle.profile.statusMessage,
"picture": f"{Yuuki_Handle.LINE_Media_server}/{Yuuki_Handle.profile.pictureStatus}"
}
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"]
Yuuki_DynamicTools(Yuuki_Handle).getClient(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"])
@ -195,9 +195,9 @@ class Yuuki_WebAdmin:
return Yuuki_APIHandle_Data.get_logs(doctype)
@staticmethod
@wa_app.route("/api/boardcast", methods=["POST"])
@wa_app.route("/api/broadcast", methods=["POST"])
@authorized_response
def boardcast():
def broadcast():
if "message" in request.values and "audience" in request.values and request.values["message"]:
audience_ids = {
"groups": lambda: Yuuki_Handle_Data.getData(

View file

@ -13,14 +13,15 @@ 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 NotFound from "./views/NotFound.js";
const router = new VueRouter({
routes: [{
path: '/',
component: Login
},
path: '/',
component: Login
},
{
path: '/dashboard',
component: Dashboard
@ -35,7 +36,11 @@ const router = new VueRouter({
},
{
path: '/settings',
component: Settings
component: Settings,
},
{
path: '/profile',
component: Profile,
},
{
path: '/events/:doctype',
@ -49,7 +54,7 @@ const router = new VueRouter({
]
})
const app = new Vue({
new Vue({
template: `
<div>
<div v-if="accessStatus" class="nav-scroller bg-white shadow-sm pt-1 pb-1">
@ -78,7 +83,7 @@ const app = new Vue({
}
this.accessStatus = true;
} else {
if (this.$router.currentRoute.path != "/") {
if (this.$router.currentRoute.path !== "/") {
this.$router.push({
path: "/"
});

View file

@ -18,18 +18,18 @@ export default {
</div>
<form class="my-3 p-3 bg-white rounded shadow-sm" method="POST">
<h6 class="border-bottom border-gray pb-2 mb-0">Boardcast</h6>
<textarea v-model="boardcastText" class="form-control" cols="50" rows="5" placeholder="Type any text message for announcing..."></textarea>
<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 for="name">Audience: </label>
<label class="checkbox-inline">
<input v-model="boardcastAudience.contacts" type="checkbox" id="inlineCheckbox1" value="option1" disabled> Contacts
<input v-model="broadcastAudience.contacts" type="checkbox" id="inlineCheckbox1" value="option1" disabled> Contacts
</label>
<label class="checkbox-inline">
<input v-model="boardcastAudience.groups" type="checkbox" id="inlineCheckbox2" value="option2"> Groups
<input v-model="broadcastAudience.groups" type="checkbox" id="inlineCheckbox2" value="option2" :disabled="broadcastStatus" > Groups
</label>
</div>
<input @click.prevent="boardcast" class="form-control text-light bg-primary mt-1" type="submit" value="Send" />
<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">
@ -41,12 +41,12 @@ export default {
: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>{{eventKey}}</title>
<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">[{{eventKey}}]</strong>
<strong class="d-block text-gray-dark">[{{event.title}}]</strong>
<span>{{getPreview(event)}}</span>
</p>
</router-link>
@ -57,20 +57,23 @@ export default {
getPreview(event) {
return "preview" in event ? event.preview : "Loading...";
},
boardcast() {
async broadcast() {
let checkpoint = confirm("The message will broadcast, are you sure?");
if (!this.boardcastText && checkpoint) return;
if (!this.broadcastText && checkpoint) return;
this.broadcastStatus = true;
let body = new FormData();
body.set("message", this.boardcastText);
for (let target in this.boardcastAudience) {
if (this.boardcastAudience[target]) {
body.set("message", this.broadcastText);
await Promise.all(Object.keys(this.broadcastAudience).map((target) => {
if (this.broadcastAudience[target]) {
body.set("audience", target);
fetch("/api/boardcast", {
return fetch("/api/broadcast", {
method: "POST",
body
});
}
}
}));
this.broadcastText = "";
this.broadcastStatus = false;
},
async fetchEventPreview(eventKey) {
const result = await this.getEventInfo(eventKey);
@ -82,8 +85,8 @@ export default {
},
async getEventInfo(eventKey) {
return await new Promise((resolve, reject) => fetch(`/api/events/${eventKey}`, {
credentials: "same-origin"
})
credentials: "same-origin"
})
.then((body) => body.json())
.catch(reject)
.then(resolve));
@ -93,26 +96,31 @@ export default {
return {
version: "",
profileName: "",
boardcastText: "",
boardcastAudience: {
profilePicture: "",
broadcastStatus: false,
broadcastText: "",
broadcastAudience: {
contacts: false,
groups: false,
},
profilePicture: "",
events: {
"Join Event": {
JoinGroup: {
title: "Join Event",
color: "#6f42c1",
href: "/events/JoinGroup"
},
"Block Event": {
BlackList: {
title: "Block Event",
color: "#007bff",
href: "/events/BlackList"
},
"Kick Event": {
KickEvent: {
title: "Kick Event",
color: "#e83e8c",
href: "/events/KickEvent"
},
"Cancel Event": {
CancelEvent: {
title: "Cancel Event",
color: "#00ff00",
href: "/events/CancelEvent"
},
@ -121,8 +129,8 @@ export default {
},
async created() {
fetch("/api/profile", {
credentials: "same-origin"
})
credentials: "same-origin"
})
.then((body) => body.json())
.then((profile) => {
this.version = profile.version;

View file

@ -11,10 +11,13 @@ export default {
<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 class="media pt-3">
<div
v-for="(event, eventIndex) in data"
: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">{{title}}</strong>
{{content}}
<strong class="d-block text-gray-dark">{{event.title}}</strong>
{{event.content}}
</p>
</div>
</div>
@ -23,20 +26,21 @@ export default {
props: ["doctype"],
data() {
return {
data: []
data: [{ title: "Loading..." }]
}
},
created() {
fetch(`/api/events/${this.doctype}`, {
credentials: "same-origin"
})
credentials: "same-origin"
})
.then((body) => body.json())
.then((events) => {
this.data = events.map((event) => {
if(!event) return { title: "(empty)" };
return {
title: event.substring(0, 24),
content: event.substring(26, element.length)
}
content: event.substring(26, event.length)
};
});
});
},

View file

@ -27,16 +27,16 @@ export default {
methods: {
async fetchGroupsJoined() {
return await new Promise((resolve, reject) => fetch("/api/groups", {
credentials: "same-origin"
})
credentials: "same-origin"
})
.then((body) => body.json())
.catch(reject)
.then(resolve));
},
async fetchGroupsInfo(groupIds) {
return await new Promise((resolve, reject) => fetch(`/api/groups/${groupIds.join(',')}`, {
credentials: "same-origin"
})
credentials: "same-origin"
})
.then((body) => body.json())
.catch(reject)
.then(resolve));

View file

@ -30,8 +30,8 @@ export default {
},
created() {
fetch("/api/helpers", {
credentials: "same-origin"
})
credentials: "same-origin"
})
.then((body) => body.json())
.then((helper_list) => {
this.helperList = helper_list.length ? helper_list : ["(empty)"];

View file

@ -18,7 +18,7 @@ export default {
<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">
<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>
@ -34,9 +34,9 @@ export default {
let body = new FormData();
body.append("code", this.password);
fetch("/api/verify", {
method: "POST",
body
})
method: "POST",
body
})
.then(body => body.json())
.then(data => {
if (data.status == 200) {

View file

@ -0,0 +1,81 @@
/*
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 class="rounded" :src="profilePicture" alt="Profile Picture" width="128" height="128">
</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

@ -48,13 +48,13 @@ export default {
color: "#007bff",
icon: "#6f42c1",
preview: "Edit LINE profile of the console BOT.",
action: () => {}
action: () => this.$router.push({path: "/profile"})
},
"Yuuki Configure": {
color: "#e83e8c",
icon: "#6f42c1",
preview: "Settings for the BOT works.",
action: () => {}
action: () => alert("Unavailable")
},
Shutdown: {
color: "#6f42c1",

View file

@ -116,10 +116,8 @@ class Yuuki:
def _Setup_WebAdmin(self):
if self.Threading and self.YuukiConfigs.get("WebAdmin"):
password = str(hash(random.random()))
self.shutdown_password = str(hash(random.random()))
self.web_admin = Yuuki_WebAdmin(self)
self.web_admin.set_password(password)
self.web_admin.set_shutdown_password(self.shutdown_password)
self.Thread_Control.add(self.web_admin.wa_listen)
print(
"<*> Yuuki WebAdmin - Enable\n"