mirror of
https://github.com/studio-neptune/yuuki.git
synced 2024-09-20 08:25:56 +08:00
Update WA and resolved several bugs
This commit is contained in:
parent
2d3b094b3b
commit
b9cd353f7f
|
@ -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):
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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: "/"
|
||||
});
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
};
|
||||
});
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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)"];
|
||||
|
|
|
@ -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) {
|
||||
|
|
81
libs/webadmin/static/js/views/Profile.js
Normal file
81
libs/webadmin/static/js/views/Profile.js
Normal 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 = {
|
||||
"&": "&",
|
||||
"<": "<",
|
||||
">": ">",
|
||||
'"': """,
|
||||
"'": "'",
|
||||
};
|
||||
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;
|
||||
});
|
||||
}
|
||||
};
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
Loading…
Reference in a new issue