完善私信相关功能
This commit is contained in:
+49
-10
@@ -9,7 +9,7 @@ const mixinKeyEncTab = [
|
||||
];
|
||||
|
||||
// 对imgKey和subKey进行混淆编码
|
||||
const getMixinKey = (orig: string) =>
|
||||
const getMixinKey = (orig: string) =>
|
||||
mixinKeyEncTab.map(n => orig[n]).join("").slice(0, 32);
|
||||
|
||||
// 账号数据接口
|
||||
@@ -40,6 +40,13 @@ class BilibiliClient {
|
||||
private buvid3: string | null = null;
|
||||
private buvid4: string | null = null;
|
||||
|
||||
// 私信DeviceId,每次登录刷新
|
||||
// From https://github.com/andywang425/BLTH/
|
||||
private dm_deviceid = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (function (name) {
|
||||
let randomInt = 16 * Math.random() | 0;
|
||||
return ("x" === name ? randomInt : 3 & randomInt | 8).toString(16).toUpperCase()
|
||||
}));
|
||||
|
||||
// 获取请求头,用于模拟正常浏览器环境,降低风控概率
|
||||
private getHeaders(): Record<string, string> {
|
||||
return {
|
||||
@@ -100,15 +107,19 @@ class BilibiliClient {
|
||||
}
|
||||
|
||||
// 发送POST请求
|
||||
private async postRequest(url: string, data: string, content_type: string): Promise<any> {
|
||||
private async postRequest(url: string, data: string, content_type: string, custom_headers: any = null): Promise<any> {
|
||||
console.log(`postRequest: ${url}, body: ${data}, contentType: ${content_type}`);
|
||||
let headers = { ...this.getHeaders(), "Content-Type": content_type }
|
||||
if(custom_headers){
|
||||
headers = custom_headers
|
||||
}
|
||||
try {
|
||||
const response = await fetch.fetch({
|
||||
url,
|
||||
responseType: 'json',
|
||||
method: 'POST',
|
||||
data,
|
||||
header: { ...this.getHeaders(), "Content-Type": content_type }
|
||||
header: headers
|
||||
});
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
@@ -117,6 +128,14 @@ class BilibiliClient {
|
||||
}
|
||||
}
|
||||
|
||||
// 发送带Wbi签名的POST请求
|
||||
private async postRequestWbi(url: string, wbiParams: any, data: string, content_type: string, custom_headers: any = null): Promise<any> {
|
||||
const img_key = this.accountInfo.wbi_img.img_url.split('/').pop().split('.')[0];
|
||||
const sub_key = this.accountInfo.wbi_img.sub_url.split('/').pop().split('.')[0];
|
||||
const signedParams = this.encWbi(wbiParams, img_key, sub_key);
|
||||
return this.postRequest(`${url}?${signedParams}`, data, content_type, custom_headers);
|
||||
}
|
||||
|
||||
// 检查澎湃哔哩是否存在更新
|
||||
async checkHyperbilibiliUpdates(): Promise<any> {
|
||||
const latestVerGet = await fetch.fetch({
|
||||
@@ -383,7 +402,7 @@ class BilibiliClient {
|
||||
break;
|
||||
case "video":
|
||||
result_array.data.forEach(vid => {
|
||||
if(vid.bvid){
|
||||
if (vid.bvid) {
|
||||
result.videos.push(vid)
|
||||
}
|
||||
});
|
||||
@@ -398,7 +417,7 @@ class BilibiliClient {
|
||||
}
|
||||
|
||||
// 获取通知信息数量 (例如回复我的、at我的、点赞数量)
|
||||
async getMessageNotifyFeed(){
|
||||
async getMessageNotifyFeed() {
|
||||
const url = "https://api.vc.bilibili.com/x/im/web/msgfeed/unread";
|
||||
const response = await this.getRequest(url);
|
||||
return response.data.data;
|
||||
@@ -406,7 +425,7 @@ class BilibiliClient {
|
||||
|
||||
// 获取私信Session列表
|
||||
// 一次性最多拉取20个,可加end_ts做IFS(但没必要)
|
||||
async getDMSessions(session_type: number, sort_rule: number){
|
||||
async getDMSessions(session_type: number, sort_rule: number) {
|
||||
const url = "https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions"
|
||||
const response = await this.getRequestWbi(url, {
|
||||
session_type,
|
||||
@@ -422,14 +441,14 @@ class BilibiliClient {
|
||||
// 获取私信Session聊天记录
|
||||
// 若要做IFS,则end_seqno应该为最顶上那条信息的序列号
|
||||
// 接口有漏洞,自带防撤回
|
||||
async getDMSessionMessage(session_type: number, talker_id: string, size: number = 10, end_seqno: string){
|
||||
async getDMSessionMessage(session_type: number, talker_id: string, size: number = 10, end_seqno: string) {
|
||||
const url = "https://api.vc.bilibili.com/svr_sync/v1/svr_sync/fetch_session_msgs"
|
||||
var params = {
|
||||
session_type,
|
||||
talker_id,
|
||||
size
|
||||
};
|
||||
if(end_seqno){
|
||||
if (end_seqno) {
|
||||
params["end_seqno"] = end_seqno;
|
||||
}
|
||||
const response = await this.getRequestWbi(url, params);
|
||||
@@ -437,13 +456,33 @@ class BilibiliClient {
|
||||
return response.data.data
|
||||
}
|
||||
|
||||
// 发送私信消息
|
||||
async SendDMSessionMessage(receiver_id: string, msg_type: number, content: string) {
|
||||
const url = "https://api.vc.bilibili.com/web_im/v1/web_im/send_msg";
|
||||
const body = `msg[sender_uid]=${this.accountInfo.mid}&msg[receiver_id]=${receiver_id}&msg[receiver_type]=1&msg[msg_type]=${msg_type}&msg[dev_id]=${this.dm_deviceid}&msg[timestamp]=${Number.parseInt(((new Date()).getTime() / 1000).toString())}&msg[content]=${encodeURIComponent(`{"content": "${content}"}`)}&csrf=${this.biliJct}&csrf_token=${this.biliJct}&msg[msg_status]=0&msg[new_face_version]=0&from_firework=0&build=0&mobi_app=web`;
|
||||
|
||||
var headers = { ...this.getHeaders(), "Content-Type": "application/x-www-form-urlencoded" }
|
||||
headers["Host"] = "api.vc.bilibili.com"
|
||||
headers["Origin"] = "https://message.bilibili.com"
|
||||
headers["Referer"] = "https://message.bilibili.com/"
|
||||
headers["Content-Length"] = body.length
|
||||
|
||||
const response = await this.postRequestWbi(url, {
|
||||
w_sender_uid: this.accountInfo.mid,
|
||||
w_receiver_id: receiver_id,
|
||||
w_dev_id: this.dm_deviceid
|
||||
}, body, "application/x-www-form-urlencoded", headers);
|
||||
|
||||
return response.data
|
||||
}
|
||||
|
||||
// 根据UID批量获取用户信息
|
||||
async getMultiUserInfoByUID(uids: Array<String>){
|
||||
async getMultiUserInfoByUID(uids: Array<String>) {
|
||||
const url = "https://api.bilibili.com/x/polymer/pc-electron/v1/user/cards";
|
||||
let param = "";
|
||||
uids.forEach(uid => {
|
||||
param += uid
|
||||
if(uids.indexOf(uid) != uids.length -1){
|
||||
if (uids.indexOf(uid) != uids.length - 1) {
|
||||
param += ","
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
<template>
|
||||
<div class="sendbox">
|
||||
<div class="textbox">
|
||||
<text class="textbox_text">点此输入文字</text>
|
||||
<div class="textbox" @click="GoInput">
|
||||
<text class="textbox_text">{{this.input_text.content || "点此输入文字"}}</text>
|
||||
</div>
|
||||
<image src="/common/dmpage_sendbtn.png" style="margin-left: 6px"></image>
|
||||
<image @click="GoSend" src="/common/dmpage_sendbtn.png" style="margin-left: 215px; margin-top: 12px"></image>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {}
|
||||
export default {
|
||||
props: ["input_text"],
|
||||
GoInput(){
|
||||
this.$emit("clickinput")
|
||||
},
|
||||
GoSend(){
|
||||
this.$emit("clicksend")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@@ -17,7 +25,6 @@ export default {}
|
||||
height: 73px;
|
||||
border-radius: 48px;
|
||||
background-color: #262626ef;
|
||||
padding: 12px 20px;
|
||||
position: absolute;
|
||||
bottom: 55px
|
||||
}
|
||||
@@ -29,10 +36,19 @@ export default {}
|
||||
background-color: #3D3D3D;
|
||||
align-items: center;
|
||||
padding: 15px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: 12px;
|
||||
left: 20px;
|
||||
}
|
||||
|
||||
.textbox_text {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
text-overflow: ellipsis;
|
||||
width: 165px;
|
||||
max-width: 165px;
|
||||
lines: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<template>
|
||||
<div class="inputpanel">
|
||||
<input-method hide={{keyboardHide}} @complete="InputComplete" @delete="InputDelete" vibratemode="short"></input-method>
|
||||
<input-method style="position: absolute" hide={{keyboardHide}} @complete="InputComplete" @delete="InputDelete" vibratemode="short"></input-method>
|
||||
<div class="inputactions">
|
||||
<div style="margin-left: 35px" @click="ExitInput">
|
||||
<image class="inputaction_img" src="/common/textinput_back.png"></image>
|
||||
|
||||
@@ -39,7 +39,13 @@ export default {
|
||||
|
||||
var session_uids = []
|
||||
this.sessions.forEach((session) => {
|
||||
session_uids.push(session.talker_id)
|
||||
if(session.talker_id){
|
||||
session_uids.push(session.talker_id)
|
||||
}
|
||||
else{
|
||||
// Pass掉非法UID
|
||||
this.sessions = this.sessions.filter(item => item !== session)
|
||||
}
|
||||
})
|
||||
|
||||
this.session_cards = await this.$app.$def.biliclient.getMultiUserInfoByUID(session_uids)
|
||||
|
||||
+166
-29
@@ -1,7 +1,9 @@
|
||||
<import name="sendbox" src="../../components/FloatingSendBox/FloatingSendBox.ux"></import>
|
||||
<import name="full-screen-input" src="../../components/FullScreenInput/FullScreenInput.ux"></import>
|
||||
|
||||
<template>
|
||||
<div class="page">
|
||||
<image style="position: absolute; margin-top: 233px" if="{{show_loading}}" src="{{anims.loading_src.value}}"></image>
|
||||
<scroll class="scroll" scroll-y="true" id="mainscroll">
|
||||
<div for="{{messages}}">
|
||||
<div class="targetmsg-container" if="{{$item.receiver_id == myId}}">
|
||||
@@ -19,8 +21,19 @@
|
||||
<div class="mymsg"
|
||||
style="margin-top: {{MsgBoxStyleCalcMarginTop($item.msg_seqno)}}; margin-bottom: {{MsgBoxStyleCalcMarginBottom($item.msg_seqno)}}">
|
||||
<div style="background-color: #ff7da8" class="msgbox">
|
||||
<text class="msgtext">
|
||||
{{ JSON.parse($item.content).content || "该消息暂不支持显示" }}
|
||||
<!-- 普通文本消息 -->
|
||||
<text class="msgtext" if="{{$item.msg_type == 1}}">
|
||||
{{ JSON.parse($item.content).content}}
|
||||
</text>
|
||||
<!-- 已撤回的文本消息 -->
|
||||
<text class="msgtext" if="{{$item.msg_type == 5}}">
|
||||
[已撤回] {{ JSON.parse($item.content).content}}
|
||||
</text>
|
||||
<!-- 图片消息 -->
|
||||
<image class="imgmsg" if="{{$item.msg_type == 2}}" src="{{JSON.parse($item.content).url}}"></image>
|
||||
<!-- 不支持显示的消息 -->
|
||||
<text class="msgtext" if="{{($item.msg_type != 5) && ($item.msg_type != 2) && ($item.msg_type != 1)}}">
|
||||
该消息暂不支持显示
|
||||
</text>
|
||||
</div>
|
||||
<image style="margin-left: 5px" class="msgprofpic" src="{{myPic}}@75w_75h" alt="/common/fullgray.png"></image>
|
||||
@@ -28,11 +41,17 @@
|
||||
</div>
|
||||
</div>
|
||||
</scroll>
|
||||
<sendbox style="position: absolute"></sendbox>
|
||||
<sendbox input_text="{{input_text}}" @clickinput="OpenInput" @clicksend="SendMsg" style="position: absolute">
|
||||
</sendbox>
|
||||
<full-screen-input style="position: absolute" input_text="{{input_text}}" if="{{inputmode}}" @complete="InputComplete"
|
||||
@delete="InputDelete" @send="FinishInput" @exit="ExitInput"></full-screen-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from "@system.router"
|
||||
import prompt from "@system.prompt"
|
||||
|
||||
export default {
|
||||
public: {
|
||||
myId: 0,
|
||||
@@ -41,43 +60,152 @@ export default {
|
||||
targetPic: ""
|
||||
},
|
||||
private: {
|
||||
messages: []
|
||||
messages: [
|
||||
{
|
||||
msg_seqno: "placeholder",
|
||||
receiver_id: 0,
|
||||
content: `{"content": "占位符"}`
|
||||
}
|
||||
],
|
||||
inputmode: false,
|
||||
show_loading: true,
|
||||
input_text: {
|
||||
content: ""
|
||||
},
|
||||
anims: {
|
||||
loading: null,
|
||||
loading_src: {value: "/common/seqanims/loadingWhite/icons8-loading_颜色反转-1.png"}
|
||||
}
|
||||
},
|
||||
async SendMsg() {
|
||||
// 发送消息
|
||||
try {
|
||||
const result = await this.$app.$def.biliclient.SendDMSessionMessage(
|
||||
this.targetId,
|
||||
1,
|
||||
this.input_text.content
|
||||
)
|
||||
if (result.code !== 0) {
|
||||
prompt.showToast({
|
||||
message: `发送失败!错误代码:${result.code} 原因:${result.message}`
|
||||
})
|
||||
} else {
|
||||
this.UpdateMessages()
|
||||
this.input_text.content = ""
|
||||
}
|
||||
} catch (error) {
|
||||
prompt.showToast({
|
||||
message: `发送失败!${error.message}`
|
||||
})
|
||||
}
|
||||
},
|
||||
ShowMessageDetail(content, type){
|
||||
switch(type){
|
||||
// 文本类消息直接丢给textdetail
|
||||
case 1:
|
||||
router.push({
|
||||
uri: "pages/textdetail",
|
||||
params: {
|
||||
text: content,
|
||||
titletext: "消息详情"
|
||||
}
|
||||
})
|
||||
break
|
||||
default:
|
||||
prompt.showToast({
|
||||
message: "该类型消息暂不支持展示详情"
|
||||
})
|
||||
break
|
||||
}
|
||||
},
|
||||
RefreshMessageList(input_text_set = ""){
|
||||
// 更改数组length以强迫快应用框架重渲染页面
|
||||
this.messages.push({})
|
||||
this.messages.pop()
|
||||
// 同时也刷新自定义组件的ViewModel
|
||||
this.input_text.content = "Loading..."
|
||||
// 将内容设置为默认为空字符串的input_text_set以适配FinishInput
|
||||
this.input_text.content = input_text_set
|
||||
setTimeout(() => {
|
||||
this.ScrollToEnd()
|
||||
}, 300)
|
||||
},
|
||||
FinishInput() {
|
||||
this.inputmode = false
|
||||
this.RefreshMessageList(this.input_text.content)
|
||||
},
|
||||
ExitInput() {
|
||||
this.inputmode = false
|
||||
this.input_text.content = ""
|
||||
this.RefreshMessageList()
|
||||
},
|
||||
InputComplete(evt) {
|
||||
this.input_text.content += evt.detail.content
|
||||
},
|
||||
InputDelete() {
|
||||
if (this.input_text.content.length > 0) {
|
||||
this.input_text.content = this.input_text.content.slice(0, -1)
|
||||
}
|
||||
},
|
||||
OpenInput() {
|
||||
this.inputmode = true
|
||||
},
|
||||
MsgBoxStyleCalcMarginTop(msg_seqno) {
|
||||
if (msg_seqno == this.messages[0].msg_seqno) {
|
||||
return "85px"
|
||||
}
|
||||
return "15px"
|
||||
return msg_seqno === this.messages[0].msg_seqno ? "85px" : "15px"
|
||||
},
|
||||
MsgBoxStyleCalcMarginBottom(msg_seqno) {
|
||||
if (msg_seqno == this.messages[this.messages.length - 1].msg_seqno) {
|
||||
return "160px"
|
||||
}
|
||||
return "0px"
|
||||
return msg_seqno === this.messages[this.messages.length - 1].msg_seqno ? "160px" : "0px"
|
||||
},
|
||||
async UpdateMessages(){
|
||||
var messages = (await this.$app.$def.biliclient.getDMSessionMessage(1, this.targetId, 30)).messages.reverse()
|
||||
if(messages != this.messages){
|
||||
this.messages = messages
|
||||
setTimeout(() => {
|
||||
this.ScrollToEnd()
|
||||
}, 200)
|
||||
async UpdateMessages(initial = false) {
|
||||
// 更新消息
|
||||
try {
|
||||
const response = await this.$app.$def.biliclient.getDMSessionMessage(1, this.targetId, 30)
|
||||
const newMessages = response.messages.reverse()
|
||||
|
||||
if (
|
||||
newMessages.length > 0 &&
|
||||
newMessages[newMessages.length - 1].msg_seqno !==
|
||||
this.messages[this.messages.length - 1].msg_seqno
|
||||
) {
|
||||
this.messages = newMessages
|
||||
setTimeout(this.ScrollToEnd, 200)
|
||||
}
|
||||
|
||||
if (initial && this.show_loading) {
|
||||
this.show_loading = false
|
||||
this.anims.loading.stop()
|
||||
}
|
||||
} catch (error) {
|
||||
prompt.showToast({
|
||||
message: `更新消息失败!${error.message}`
|
||||
})
|
||||
}
|
||||
},
|
||||
ScrollToEnd(){
|
||||
this.$element('mainscroll').getScrollRect({
|
||||
ScrollToEnd() {
|
||||
this.$element("mainscroll").getScrollRect({
|
||||
success: (rect) => {
|
||||
this.$element('mainscroll').scrollTo({
|
||||
top: rect.height
|
||||
})
|
||||
this.$element("mainscroll").scrollTo({top: rect.height})
|
||||
}
|
||||
})
|
||||
},
|
||||
onShow(){
|
||||
this.UpdateMessages()
|
||||
onReady() {
|
||||
global.runGC()
|
||||
this.UpdateMessages(true) // 初始化加载消息
|
||||
this.messageUpdateInterval = setInterval(this.UpdateMessages, 3000) // 每3秒更新一次消息
|
||||
},
|
||||
onInit(){
|
||||
|
||||
onDestroy() {
|
||||
clearInterval(this.messageUpdateInterval)
|
||||
},
|
||||
onInit() {
|
||||
this.anims.loading = new this.$app.$def.animengine.SequenceAnim(
|
||||
"replysPage",
|
||||
this.anims.loading_src,
|
||||
28,
|
||||
"/common/seqanims/loadingWhite/icons8-loading_颜色反转-*.png",
|
||||
1000,
|
||||
true
|
||||
)
|
||||
this.anims.loading.start()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -121,12 +249,21 @@ export default {
|
||||
margin-right: 55px;
|
||||
}
|
||||
|
||||
.imgmsg {
|
||||
object-fit: scale-down;
|
||||
border-radius: 15px;
|
||||
max-width: 156px;
|
||||
max-height: 125px;
|
||||
}
|
||||
|
||||
.msgbox {
|
||||
max-width: 171px;
|
||||
max-height: 140px;
|
||||
border-radius: 19px;
|
||||
border-radius: 20px;
|
||||
padding: 15px;
|
||||
background-color: #242424;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.msgprofpic {
|
||||
|
||||
Reference in New Issue
Block a user