mirror of
https://github.com/xfgryujk/blivechat.git
synced 2026-08-23 11:53:28 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b73a4e3748 | ||
|
|
102746d0b0 | ||
|
|
2d237af6fd | ||
|
|
4030ff43cd |
11
api/chat.py
11
api/chat.py
@@ -63,7 +63,7 @@ class Room(blivedm.BLiveClient):
|
||||
def __parse_gift(self, command):
|
||||
data = command['data']
|
||||
return self._on_receive_gift(blivedm.GiftMessage(
|
||||
None, None, data['uname'], data['face'], None,
|
||||
data['giftName'], data['num'], data['uname'], data['face'], None,
|
||||
data['uid'], data['timestamp'], None, None,
|
||||
None, None, None, data['coin_type'], data['total_coin']
|
||||
))
|
||||
@@ -192,7 +192,9 @@ class Room(blivedm.BLiveClient):
|
||||
'avatarUrl': avatar_url,
|
||||
'timestamp': gift.timestamp,
|
||||
'authorName': gift.uname,
|
||||
'totalCoin': gift.total_coin
|
||||
'totalCoin': gift.total_coin,
|
||||
'giftName': gift.gift_name,
|
||||
'num': gift.num
|
||||
})
|
||||
|
||||
async def _on_buy_guard(self, message: blivedm.GuardBuyMessage):
|
||||
@@ -419,7 +421,9 @@ class ChatHandler(tornado.websocket.WebSocketHandler):
|
||||
gift_data = {
|
||||
**base_data,
|
||||
'id': uuid.uuid4().hex,
|
||||
'totalCoin': 450000
|
||||
'totalCoin': 450000,
|
||||
'giftName': '摩天大楼',
|
||||
'num': 1
|
||||
}
|
||||
sc_data = {
|
||||
**base_data,
|
||||
@@ -444,6 +448,7 @@ class ChatHandler(tornado.websocket.WebSocketHandler):
|
||||
self.send_message(Command.ADD_GIFT, gift_data)
|
||||
gift_data['id'] = uuid.uuid4().hex
|
||||
gift_data['totalCoin'] = 1245000
|
||||
gift_data['giftName'] = '小电视飞船'
|
||||
self.send_message(Command.ADD_GIFT, gift_data)
|
||||
|
||||
@property
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
'@vue/app'
|
||||
],
|
||||
plugins: [
|
||||
[
|
||||
'component', {
|
||||
'libraryName': 'element-ui',
|
||||
'styleLibraryName': 'theme-chalk'
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
|
||||
15938
frontend/package-lock.json
generated
15938
frontend/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -20,8 +20,9 @@
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "^3.7.0",
|
||||
"@vue/cli-plugin-eslint": "^3.7.0",
|
||||
"@vue/cli-service": "^3.7.0",
|
||||
"@vue/cli-service": "^4.2.2",
|
||||
"babel-eslint": "^10.0.1",
|
||||
"babel-plugin-component": "^1.1.1",
|
||||
"eslint": "^5.16.0",
|
||||
"eslint-plugin-vue": "^5.0.0",
|
||||
"vue-template-compiler": "^2.5.21"
|
||||
|
||||
@@ -2,9 +2,11 @@ import {mergeConfig} from '@/utils'
|
||||
|
||||
export const DEFAULT_CONFIG = {
|
||||
minGiftPrice: 7, // $1
|
||||
mergeSimilarDanmaku: true,
|
||||
showDanmaku: true,
|
||||
showGift: true,
|
||||
showGiftName: false,
|
||||
mergeSimilarDanmaku: true,
|
||||
mergeGift: true,
|
||||
maxNumber: 60,
|
||||
|
||||
blockGiftDanmaku: true,
|
||||
|
||||
@@ -34,18 +34,19 @@
|
||||
<paid-message :key="pinnedMessage.id" v-else
|
||||
class="style-scope yt-live-chat-ticker-renderer"
|
||||
:price="pinnedMessage.price" :avatarUrl="pinnedMessage.avatarUrl" :authorName="pinnedMessage.authorName"
|
||||
:time="pinnedMessage.time" :content="pinnedMessage.content"
|
||||
:time="pinnedMessage.time" :content="showContent"
|
||||
></paid-message>
|
||||
</template>
|
||||
</yt-live-chat-ticker-renderer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as config from '@/api/config'
|
||||
import {formatCurrency} from '@/utils'
|
||||
import ImgShadow from './ImgShadow.vue'
|
||||
import LegacyPaidMessage from './LegacyPaidMessage.vue'
|
||||
import PaidMessage from './PaidMessage.vue'
|
||||
import * as constants from './constants'
|
||||
import {formatCurrency} from '@/utils'
|
||||
|
||||
export default {
|
||||
name: 'Ticker',
|
||||
@@ -55,7 +56,11 @@ export default {
|
||||
PaidMessage
|
||||
},
|
||||
props: {
|
||||
messages: Array
|
||||
messages: Array,
|
||||
showGiftName: {
|
||||
type: Boolean,
|
||||
default: config.DEFAULT_CONFIG.showGiftName
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -65,6 +70,18 @@ export default {
|
||||
pinnedMessage: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
showContent() {
|
||||
if (!this.pinnedMessage) {
|
||||
return ''
|
||||
}
|
||||
if (this.pinnedMessage.type === constants.MESSAGE_TYPE_GIFT) {
|
||||
return constants.getGiftShowContent(this.pinnedMessage, this.showGiftName)
|
||||
} else {
|
||||
return constants.getShowContent(this.pinnedMessage)
|
||||
}
|
||||
}
|
||||
},
|
||||
beforeDestroy() {
|
||||
window.clearInterval(this.updateTimerId)
|
||||
},
|
||||
|
||||
@@ -18,10 +18,11 @@ export const GUARD_LEVEL_TO_TEXT = [
|
||||
]
|
||||
|
||||
export const MESSAGE_TYPE_TEXT = 0
|
||||
export const MESSAGE_TYPE_MEMBER = 1
|
||||
export const MESSAGE_TYPE_SUPER_CHAT = 2
|
||||
export const MESSAGE_TYPE_DEL = 3
|
||||
export const MESSAGE_TYPE_UPDATE = 4
|
||||
export const MESSAGE_TYPE_GIFT = 1
|
||||
export const MESSAGE_TYPE_MEMBER = 2
|
||||
export const MESSAGE_TYPE_SUPER_CHAT = 3
|
||||
export const MESSAGE_TYPE_DEL = 4
|
||||
export const MESSAGE_TYPE_UPDATE = 5
|
||||
|
||||
// 美元 -> 人民币 汇率
|
||||
const EXCHANGE_RATE = 7
|
||||
@@ -120,3 +121,17 @@ export function getPriceConfig (price) {
|
||||
}
|
||||
return PRICE_CONFIGS[PRICE_CONFIGS.length - 1]
|
||||
}
|
||||
|
||||
export function getShowContent(message) {
|
||||
if (message.translation) {
|
||||
return `${message.content}(${message.translation})`
|
||||
}
|
||||
return message.content
|
||||
}
|
||||
|
||||
export function getGiftShowContent(message, showGiftName) {
|
||||
if (!showGiftName) {
|
||||
return ''
|
||||
}
|
||||
return `Sent ${message.giftName}x${message.num}`
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
<yt-live-chat-renderer class="style-scope yt-live-chat-app" style="--scrollbar-width:11px;" hide-timestamps
|
||||
@mousemove="refreshCantScrollStartTime"
|
||||
>
|
||||
<ticker class="style-scope yt-live-chat-renderer" :messages="paidMessages" :hidden="paidMessages.length === 0"></ticker>
|
||||
<ticker class="style-scope yt-live-chat-renderer" :messages="paidMessages" :showGiftName="showGiftName"
|
||||
:hidden="paidMessages.length === 0"
|
||||
></ticker>
|
||||
<yt-live-chat-item-list-renderer class="style-scope yt-live-chat-renderer" allow-scroll>
|
||||
<div ref="scroller" id="item-scroller" class="style-scope yt-live-chat-item-list-renderer animated" @scroll="onScroll">
|
||||
<div ref="itemOffset" id="item-offset" class="style-scope yt-live-chat-item-list-renderer" style="height: 0px;">
|
||||
@@ -16,6 +18,11 @@
|
||||
:authorType="message.authorType" :content="getShowContent(message)" :privilegeType="message.privilegeType"
|
||||
:repeated="message.repeated"
|
||||
></text-message>
|
||||
<paid-message :key="message.id" v-else-if="message.type === MESSAGE_TYPE_GIFT"
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
:price="message.price" :avatarUrl="message.avatarUrl" :authorName="message.authorName"
|
||||
:time="message.time" :content="getGiftShowContent(message)"
|
||||
></paid-message>
|
||||
<legacy-paid-message :key="message.id" v-else-if="message.type === MESSAGE_TYPE_MEMBER"
|
||||
class="style-scope yt-live-chat-item-list-renderer"
|
||||
:avatarUrl="message.avatarUrl" :title="message.title" :content="message.content"
|
||||
@@ -58,6 +65,10 @@ export default {
|
||||
maxNumber: {
|
||||
type: Number,
|
||||
default: config.DEFAULT_CONFIG.maxNumber
|
||||
},
|
||||
showGiftName: {
|
||||
type: Boolean,
|
||||
default: config.DEFAULT_CONFIG.showGiftName
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -65,6 +76,7 @@ export default {
|
||||
document.head.appendChild(styleElement)
|
||||
return {
|
||||
MESSAGE_TYPE_TEXT: constants.MESSAGE_TYPE_TEXT,
|
||||
MESSAGE_TYPE_GIFT: constants.MESSAGE_TYPE_GIFT,
|
||||
MESSAGE_TYPE_MEMBER: constants.MESSAGE_TYPE_MEMBER,
|
||||
MESSAGE_TYPE_SUPER_CHAT: constants.MESSAGE_TYPE_SUPER_CHAT,
|
||||
|
||||
@@ -118,12 +130,10 @@ export default {
|
||||
this.clearMessages()
|
||||
},
|
||||
methods: {
|
||||
getShowContent(message) {
|
||||
if (message.translation) {
|
||||
return `${message.content}(${message.translation})`
|
||||
}
|
||||
return message.content
|
||||
getGiftShowContent(message) {
|
||||
return constants.getGiftShowContent(message, this.showGiftName)
|
||||
},
|
||||
getShowContent: constants.getShowContent,
|
||||
|
||||
addMessage(message) {
|
||||
this.addMessages([message])
|
||||
@@ -159,14 +169,15 @@ export default {
|
||||
})
|
||||
return res
|
||||
},
|
||||
mergeSimilarGift(authorName, price) {
|
||||
mergeSimilarGift(authorName, price, giftName, num) {
|
||||
let res = false
|
||||
this.forEachRecentMessage(5, message => {
|
||||
if (message.type === constants.MESSAGE_TYPE_SUPER_CHAT
|
||||
&& message.content === ''
|
||||
if (message.type === constants.MESSAGE_TYPE_GIFT
|
||||
&& message.authorName === authorName
|
||||
&& message.giftName === giftName
|
||||
) {
|
||||
message.price += price
|
||||
message.num += num
|
||||
res = true
|
||||
return false
|
||||
}
|
||||
@@ -317,6 +328,7 @@ export default {
|
||||
for (let message of messageGroup) {
|
||||
switch (message.type) {
|
||||
case constants.MESSAGE_TYPE_TEXT:
|
||||
case constants.MESSAGE_TYPE_GIFT:
|
||||
case constants.MESSAGE_TYPE_MEMBER:
|
||||
case constants.MESSAGE_TYPE_SUPER_CHAT:
|
||||
this.handleAddMessage(message)
|
||||
|
||||
@@ -14,7 +14,9 @@ export default {
|
||||
roomId: 'Room ID',
|
||||
showDanmaku: 'Show messages',
|
||||
showGift: 'Show Super Chats',
|
||||
showGiftName: 'Show gift name',
|
||||
mergeSimilarDanmaku: 'Merge similar messages',
|
||||
mergeGift: 'Merge gifts',
|
||||
minGiftPrice: 'Min price of Super Chats to show (CNY)',
|
||||
maxNumber: 'Max number of messages',
|
||||
|
||||
|
||||
@@ -14,7 +14,9 @@ export default {
|
||||
roomId: 'ルームID',
|
||||
showDanmaku: 'コメントを表示する',
|
||||
showGift: 'スーパーチャットと新メンバーを表示する',
|
||||
showGiftName: 'ギフト名を表示する',
|
||||
mergeSimilarDanmaku: '同じコメントを合併する',
|
||||
mergeGift: 'ギフトを合併する',
|
||||
minGiftPrice: '最低表示スーパーチャット価格(CNY)',
|
||||
maxNumber: '最大コメント数',
|
||||
|
||||
@@ -28,7 +30,7 @@ export default {
|
||||
blockUsers: 'ブロックユーザー',
|
||||
blockMedalLevel: 'ブロック勲章等級がx未満',
|
||||
|
||||
advanced: 'アドバンス',
|
||||
advanced: 'アドバンスド',
|
||||
autoTranslate: '自動翻訳コメントから日本語へ',
|
||||
|
||||
roomUrl: 'ルームのURL',
|
||||
|
||||
@@ -14,7 +14,9 @@ export default {
|
||||
roomId: '房间ID',
|
||||
showDanmaku: '显示弹幕',
|
||||
showGift: '显示打赏和新舰长',
|
||||
showGiftName: '显示礼物名',
|
||||
mergeSimilarDanmaku: '合并相似弹幕',
|
||||
mergeGift: '合并礼物',
|
||||
minGiftPrice: '最低显示打赏价格(元)',
|
||||
maxNumber: '最大弹幕数',
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</router-link>
|
||||
</div>
|
||||
<div class="version">
|
||||
v1.4.0
|
||||
v1.4.1
|
||||
</div>
|
||||
<sidebar></sidebar>
|
||||
</el-aside>
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import Vue from 'vue'
|
||||
import VueRouter from 'vue-router'
|
||||
import ElementUI from 'element-ui'
|
||||
import 'element-ui/lib/theme-chalk/index.css'
|
||||
import VueI18n from 'vue-i18n'
|
||||
import {
|
||||
Aside, Autocomplete, Badge, Button, Col, ColorPicker, Container, Divider, Form, FormItem, Image,
|
||||
Input, Main, Menu, MenuItem, Message, Row, Scrollbar, Slider, Submenu, Switch, TabPane, Tabs, Tooltip
|
||||
} from 'element-ui'
|
||||
import axios from 'axios'
|
||||
|
||||
import App from './App.vue'
|
||||
@@ -23,8 +25,32 @@ if (process.env.NODE_ENV === 'development') {
|
||||
}
|
||||
|
||||
Vue.use(VueRouter)
|
||||
Vue.use(ElementUI)
|
||||
Vue.use(VueI18n)
|
||||
// 初始化element
|
||||
Vue.use(Aside)
|
||||
Vue.use(Autocomplete)
|
||||
Vue.use(Badge)
|
||||
Vue.use(Button)
|
||||
Vue.use(Col)
|
||||
Vue.use(ColorPicker)
|
||||
Vue.use(Container)
|
||||
Vue.use(Divider)
|
||||
Vue.use(Form)
|
||||
Vue.use(FormItem)
|
||||
Vue.use(Image)
|
||||
Vue.use(Input)
|
||||
Vue.use(Main)
|
||||
Vue.use(Menu)
|
||||
Vue.use(MenuItem)
|
||||
Vue.use(Row)
|
||||
Vue.use(Scrollbar)
|
||||
Vue.use(Slider)
|
||||
Vue.use(Submenu)
|
||||
Vue.use(Switch)
|
||||
Vue.use(TabPane)
|
||||
Vue.use(Tabs)
|
||||
Vue.use(Tooltip)
|
||||
Vue.prototype.$message = Message
|
||||
|
||||
Vue.config.ignoredElements = [
|
||||
/^yt-/
|
||||
|
||||
@@ -16,9 +16,15 @@
|
||||
<el-form-item :label="$t('home.showGift')">
|
||||
<el-switch v-model="form.showGift"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('home.showGiftName')">
|
||||
<el-switch v-model="form.showGiftName"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('home.mergeSimilarDanmaku')">
|
||||
<el-switch v-model="form.mergeSimilarDanmaku"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('home.mergeGift')">
|
||||
<el-switch v-model="form.mergeGift"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item :label="$t('home.minGiftPrice')">
|
||||
<el-input v-model.number="form.minGiftPrice" type="number" min="0"></el-input>
|
||||
</el-form-item>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<chat-renderer ref="renderer" :maxNumber="config.maxNumber"></chat-renderer>
|
||||
<chat-renderer ref="renderer" :maxNumber="config.maxNumber" :showGiftName="config.showGiftName"></chat-renderer>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -43,6 +43,11 @@ export default {
|
||||
created() {
|
||||
this.updateConfig()
|
||||
this.wsConnect()
|
||||
// 提示用户已加载
|
||||
this.$message({
|
||||
message: 'Loaded',
|
||||
duration: '500'
|
||||
})
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.isDestroying = true
|
||||
@@ -60,9 +65,11 @@ export default {
|
||||
cfg = mergeConfig(cfg, config.DEFAULT_CONFIG)
|
||||
|
||||
cfg.minGiftPrice = toInt(cfg.minGiftPrice, config.DEFAULT_CONFIG.minGiftPrice)
|
||||
cfg.mergeSimilarDanmaku = toBool(cfg.mergeSimilarDanmaku)
|
||||
cfg.showDanmaku = toBool(cfg.showDanmaku)
|
||||
cfg.showGift = toBool(cfg.showGift)
|
||||
cfg.showGiftName = toBool(cfg.showGiftName)
|
||||
cfg.mergeSimilarDanmaku = toBool(cfg.mergeSimilarDanmaku)
|
||||
cfg.mergeGift = toBool(cfg.mergeGift)
|
||||
cfg.maxNumber = toInt(cfg.maxNumber, config.DEFAULT_CONFIG.maxNumber)
|
||||
cfg.blockGiftDanmaku = toBool(cfg.blockGiftDanmaku)
|
||||
cfg.blockLevel = toInt(cfg.blockLevel, config.DEFAULT_CONFIG.blockLevel)
|
||||
@@ -153,20 +160,21 @@ export default {
|
||||
break
|
||||
}
|
||||
let price = data.totalCoin / 1000
|
||||
if (price < this.config.minGiftPrice) { // 丢人
|
||||
if (this.mergeSimilarGift(data.authorName, price, data.giftName, data.num)) {
|
||||
break
|
||||
}
|
||||
if (this.mergeSimilarGift(data.authorName, price)) {
|
||||
if (price < this.config.minGiftPrice) { // 丢人
|
||||
break
|
||||
}
|
||||
message = {
|
||||
id: data.id,
|
||||
type: constants.MESSAGE_TYPE_SUPER_CHAT,
|
||||
type: constants.MESSAGE_TYPE_GIFT,
|
||||
avatarUrl: data.avatarUrl,
|
||||
time: new Date(data.timestamp * 1000),
|
||||
authorName: data.authorName,
|
||||
price: price,
|
||||
time: new Date(data.timestamp * 1000),
|
||||
content: '' // 有了SC,礼物不需要内容了
|
||||
giftName: data.giftName,
|
||||
num: data.num
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -257,11 +265,11 @@ export default {
|
||||
}
|
||||
return this.$refs.renderer.mergeSimilarText(content)
|
||||
},
|
||||
mergeSimilarGift(authorName, price) {
|
||||
if (!this.config.mergeSimilarDanmaku) {
|
||||
mergeSimilarGift(authorName, price, giftName, num) {
|
||||
if (!this.config.mergeGift) {
|
||||
return false
|
||||
}
|
||||
return this.$refs.renderer.mergeSimilarGift(authorName, price)
|
||||
return this.$refs.renderer.mergeSimilarGift(authorName, price, giftName, num)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,8 +268,8 @@ const EXAMPLE_MESSAGES = [
|
||||
{
|
||||
...legacyPaidMessageTemplate,
|
||||
id: (nextId++).toString(),
|
||||
authorName: '少年Pi',
|
||||
content: 'Welcome 少年Pi!'
|
||||
authorName: '进击的冰糖',
|
||||
content: 'Welcome 进击的冰糖!'
|
||||
},
|
||||
{
|
||||
...paidMessageTemplate,
|
||||
|
||||
Reference in New Issue
Block a user