// ==UserScript==
// @name wsmud_api
// @namespace com.wsmud
// @version 0.0.7
// @description 使用于 Tampermonkey 的武神传说脚本的前置 API 库
// @author sq
// @date 2020/08/24
// @modified 2020/08/28
// @match http://*.wsmud.com/*
// @exclude http://*.wsmud.com/news/*
// @exclude http://*.wsmud.com/pay.html
// @homepage https://greasyfork.org/zh-CN/scripts/409901
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js
// @run-at document-start
// @grant unsafeWindow
// @grant GM_addStyle
// ==/UserScript==
(function() {
'use strict'
if (!unsafeWindow.WebSocket) return
// GM_info.script.version
unsafeWindow.console.green = function(log) {
console.log(`%c${log}`, 'color:green')
}
unsafeWindow.console.orange = function(log) {
console.log(`%c${log}`, 'color:orange')
}
unsafeWindow.console.red = function(log) {
console.log(`%c${log}`, 'color:red')
}
console.green = unsafeWindow.console.green
console.orange = unsafeWindow.console.orange
console.red = unsafeWindow.console.red
class Api {
constructor() {
this.roles = this.getValue('roles')
this.id = String()
this.name = String()
this.state = String()
this.performs = Array()
this.room = Object()
this.monitors = Object()
this.commandQueue = Array()
this.commandState = false
}
set roles(value) {
if (value instanceof Array) {
value.sort((a, b) => a.sort - b.sort)
value.forEach((item, index) => {
if (item.server) item.sort = index + 1
else item.sort = 9999
})
this._roles = value
} else {
this._roles = []
}
this.setValue('roles', this._roles)
}
get roles() {
return this._roles
}
ondata(data) {
const type = data.type === 'dialog' ? data.dialog : data.type
if (this.monitors[type]) {
Object.keys(this.monitors[type]).forEach(name => {
const callback = this.monitors[type][name]
callback(data)
})
}
const data2event = function(data) {
if (!data.type) return
if (data.type === 'text') return { data: data.text }
else return { data: JSON.stringify(data) }
}
const event = data2event(data)
if (event) this._onmessage(event)
}
send(...args) {
// 分割含有英文逗号的字符串
args.forEach((item, index) => {
if (typeof item === 'string' && /,/.test(item) && /^(?!setting)/.test(item)) args[index] = item.split(',')
})
// 加入指令队列
this.commandQueue.push(...args.flat(Infinity))
// 开启消息队列
if (!this.commandState) {
this.commandState = true
this.sendloop(0)
}
}
sendloop(delay = 256) {
if (!this._websocket || !this.commandState || this.commandQueue.length === 0) {
this.commandState = false
return
}
// 取出首位元素
const cmd = this.commandQueue.splice(0, 1)[0]
// 判断是否数字
const number = Number(cmd)
if (!isNaN(number)) {
console.orange(`CommandQueue: Wait for ${number}ms.`)
this.sendloop(number)
return
}
// 判断是否字符串
if (typeof cmd === 'string') {
setTimeout(() => {
console.orange(cmd)
this._websocket.send(cmd)
this.sendloop()
}, delay)
return
}
// 判断是否函数
if (typeof cmd === 'function') {
setTimeout(() => {
cmd()
this.sendloop()
}, delay)
return
}
}
clearCommandQueue() {
this.commandQueue.splice(0)
}
addMonitor(type, name, callback) {
if (!type || !name || typeof callback !== 'function') return
if (!this.monitors[type]) this.monitors[type] = {}
this.monitors[type][name] = callback.bind(this)
console.green(`AddMonitor: [${type}]${name}`)
}
removeMonitor(type, name) {
if (!type || !name) return
delete this.monitors[type][name]
console.red(`RemoveMonitor: type = ${type}; name = ${name};`)
api.roles = new Object(api.roles)
}
addStyle(css) {
GM_addStyle(css)
}
cookie() {
const cookies = document.cookie.split(';').reduce((accumulator, currentValue) => {
const i = currentValue.indexOf('=')
const name = currentValue.substr(0, i).trim()
const value = currentValue.substr(i + 1)
accumulator[name] = value
return accumulator
}, {})
const setCookie = (name, value) => document.cookie = name + '=' + value
const deleteCookie = name => document.cookie = name + '=; expires=Thu, 01 Jan 1970 00:00:01 GMT;'
return new Proxy(cookies, {
set: (target, name, value) => {
setCookie(name, value)
return Reflect.set(target, name, value)
},
deleteProperty: (target, name) => {
deleteCookie(name)
return Reflect.deleteProperty(target, name)
},
})
}
setValue(key, value) {
localStorage.setItem(key, JSON.stringify(value))
}
getValue(key) {
return JSON.parse(localStorage.getItem(key))
}
deleteValue(key) {
localStorage.removeItem(key)
}
}
unsafeWindow.Vue = Vue
unsafeWindow.api = Vue.observable(new Api())
// const api = unsafeWindow.api
unsafeWindow.WebSocket = function (uri) {
api._websocket = new WebSocket(uri)
}
unsafeWindow.WebSocket.prototype = {
set onopen(fn) {
api._websocket.onopen = fn
},
set onclose(fn) {
api._websocket.onclose = fn
},
set onerror(fn) {
api._websocket.onerror = fn
},
set onmessage(fn) {
const event2data = function(event) {
const data = event.data
if (typeof data === 'string' && data[0] === '{') {
try {
return new Function('return ' + data)()
} catch (error) {
console.red(error)
console.red(data)
}
}
return { 'type': 'text', 'text': data }
}
api._onmessage = fn
api._websocket.onmessage = function(event) {
const data = event2data(event)
api.ondata(data)
}
},
get readyState() {
return api._websocket.readyState
},
send: function(command) {
api.send(command)
},
}
api.addMonitor('roles', 'RoleList', function(data) {
if (!(data.roles instanceof Array)) return
data.roles.forEach(item => {
const { id, name, title } = item
const index = this.roles.findIndex(role => role.id === id)
if (index === -1) {
this.roles.push({ id, name, title, sort: 9999 })
} else {
this.roles[index].name = name
this.roles[index].title = title
}
})
this.roles = this.roles.slice(0)
})
api.addMonitor('login', 'Login', function(data) {
const id = data.id
if (this.id || !id) return
this.id = id
const index = this.roles.findIndex(role => role.id === id)
this.name = this.roles[index].name
const cookie = this.cookie()
this.roles[index].u = cookie.u
this.roles[index].p = cookie.p
this.roles[index].s = cookie.s
this.roles[index].server = ['一区', '二区', '三区', '四区', '测试'][cookie.s]
this.roles = this.roles.slice(0)
setTimeout(() => {
this.send('pack,score2,score')
setTimeout(() => $('[command=skills]').click(), 1000)
setTimeout(() => $('[command=tasks]').click(), 2000)
setTimeout(() => $('.dialog-close').click(), 3000)
if (!$('.right-bar').width()) {
setTimeout(() => $('[command=showtool]').click(), 500)
}
if (!$('.content-bottom').height()) {
setTimeout(() => $('[command=showcombat]').click(), 1000)
}
}, 2000)
// greet master 请安师傅
})
api.addMonitor('state', 'State', function(data) {
if (typeof data.state !== 'string') {
this.state = ''
return
}
const list = [
{ regexp: /^你正在挖矿中$/, value: '挖矿' },
{ regexp: /^你正在练习技能$/, value: '练习' },
{ regexp: /^你正在学习(\S+)$/, value: '学习' },
{ regexp: /^你正在打坐运功$/, value: '打坐' },
{ regexp: /^你正在疗伤$/, value: '疗伤' },
{ regexp: /^你正在闭关$/, value: '闭关' },
{ regexp: /^你正在炼药$/, value: '炼药' },
{ regexp: /^你正在读书$/, value: '读书' },
{ regexp: /^你正在领悟技能$/, value: '武道领悟' },
]
const result = list.find(item => item.regexp.test(data.state))
if (result) {
data.state = result.value + (RegExp.$1 || '')
} else {
console.red(data.state)
}
this.state = data.state
delete data.desc
})
api.addMonitor('combat', 'Fight', function(data) {
if (data.start === 1) {
this.state = '战斗'
// if (this.fightTimer) clearInterval(this.fightTimer)
// this.fightTimer = setInterval(() => {
// const now = Date.now()
// this.performs.find(pfm => {
// if (pfm.auto && (pfm.time < now)) {
// this.send(`perform ${pfm.id}`)
// return true
// }
// })
// }, 256)
} else if (data.end === 1) {
this.state = ''
// clearInterval(this.fightTimer)
}
})
api.addMonitor('die', 'Die', function(data) {
if (data.relive) {
this.state = ''
} else {
this.state = '死亡'
// // 自动武庙复活
// if (this.CanReliveWuMiao && !this.roomName.includes('副本')) {
// this.send('relive')
}
})
class Perform {
constructor(item) {
this.id = item.id
this.name = item.name
this.distime = item.distime
this.timestamp = 0
this.auto = api.getValue(this.key('auto')) || false
this.sort = api.getValue(this.key('sort')) || 999
}
key(value) {
return `${api.id}-${this.id}-${value}`
}
set auto(value) {
this._auto = Boolean(value)
api.setValue(this.key('auto'), this._auto)
}
get auto() {
return this._auto
}
set sort(value) {
this._sort = Number(value)
api.setValue(this.key('sort'), this._auto)
}
get sort() {
return this._sort
}
}
api.addMonitor('perform', 'PerformList', function(data) {
if (!(data.skills instanceof Array)) return
data.skills.forEach((item, index) => data.skills[index] = new Perform(item))
data.skills.forEach((a, b) => a.sort - b.sort)
data.skills.forEach((item, index) => item.sort = index + 1)
api.performs = data.skills
})
api.addMonitor('dispfm', 'PerformDistime', function(data) {
if (data.hasOwnProperty('id') && data.hasOwnProperty('distime')) {
api.performs.forEach(pfm => {
if (pfm.id === data.id) {
pfm.distime = data.distime
pfm.timestamp = Date.now() + data.distime
}
if (data.hasOwnProperty('rtime')) {
pfm.time = Math.max(Date.now() + data.rtime, pfm.timestamp)
}
})
}
})
class Room {
constructor(item) {
this.name = item.name
this.path = item.path
this.commands = item.commands || Array()
this.desc = item.desc
this.exits = Object()
this.items = Array()
}
get x() {
if (/^(\S+)-(\S+)$/.test(this.name)) {
return RegExp.$1
}
}
get y() {
if (/^(\S+)-(\S+)\(副本区域\)$/.test(this.name)) {
return RegExp.$2
} else if (/^(\S+)-(\S+)$/.test(this.name)) {
return RegExp.$2
}
}
set desc(value) {
if (/cmd/.test(value)) {
// 统一使用双引号
value = value.replace(/'/g, '"')
// 去除英文括号和里面的英文单词
value = value.replace(/\([A-Za-z]+?\)/g, '')
// 新手教程中的椅子
value = value.replace(/<hig>椅子<\/hig>/, '椅子')
// 兵营副本中的门
value = value.replace(/<CMD cmd="look men">门<CMD>/, '<cmd cmd="look men">门</cmd>');
// 古墓副本中的画和古琴
value = value.replace(/span/ig, 'cmd')
const htmls = value.match(/<cmd cmd="[^"]+?">[^<]+?<\/cmd>/g)
htmls.forEach(html => {
if (/<cmd cmd="([^"]+?)">([^<]+?)<\/cmd>/.test(html)) {
this.commands.unshift({ cmd: RegExp.$1, name: `<hic>${RegExp.$2}</hic>` })
}
})
}
this._desc = value
}
get desc() {
return this._desc
}
get npcs() {
return this.items.filter(item => item.isNpc)
}
}
api.addMonitor('room', 'RoomInfo', function(data) {
api.room = new Room(data)
data.desc = api.room.desc
data.commands = api.room.commands
})
api.addMonitor('exits', 'RoomExitInfo', function(data) {
if (typeof data.items !== 'object') return
api.room.exits = {}
Object.keys(data.items).forEach(key => api.room.exits[`go ${key}`] = data.items[key])
})
class Role {
constructor(data) {
this.id = data.id
this.name = data.name
if (data.hasOwnProperty('hp')) this.hp = data.hp
if (data.hasOwnProperty('mp')) this.mp = data.mp
if (data.hasOwnProperty('max_hp')) this.max_hp = data.max_hp
if (data.hasOwnProperty('max_mp')) this.max_mp = data.max_mp
if (data.hasOwnProperty('status')) this.status = data.status
if (data.hasOwnProperty('p')) this.p = data.p // 玩家肯定有 p === 1
}
get isNpc() {
return !this.hasOwnProperty('p') && this.hasOwnProperty('hp')
}
get sort() {
// 自身
if (this.id === api.id) return 1
// NPC
if (this.isNpc) return 10
// 玩家
if (this.hasOwnProperty('hp')) {
const list = ['武神 ', '武帝 ', '武圣 ', '宗师 ', '武师 ', '武士 ', '普通百姓 ', '']
const index = list.findIndex(item => this.name.includes(item))
return 100 * index * (this.name.includes('<red><断线中></red>') ? 100 : 1)
}
// 其他物品或尸体
return 1000
}
}
api.addMonitor('items', 'RoomItemList', function(data) {
if (!data.items instanceof Array) return
data.items.splice(-1, 1) // 删除最后一个 0
data.items.forEach((item, index) => data.items[index] = new Role(item))
data.items.sort((a, b) => a.sort - b.sort)
api.room.items = data.items
})
api.addMonitor('itemadd', 'RoomAddItem', function(data) {
const item = new Role(data)
const index = api.room.items.findIndex(item => item.id === data.id)
if (index === -1) api.room.items.push(item)
else api.room.items.splice(index, 1, item)
})
api.addMonitor('itemremove', 'RoomRemoveItem', function(data) {
const index = api.room.items.findIndex(item => item.id === data.id)
if (index !== -1) api.room.items.splice(index, 1)
})
api.addMonitor('', '', function(data) {})
api.addMonitor('', '', function(data) {})
class Skill {
constructor(item) {
this.id = item.id
this.name = item.name
this.level = item.level
this.target = api.getValue(this.key('target'))
}
key(value) {
return `${api.id}-${this.id}-${value}`
}
set target(value) {
const number = Number(value)
this._target = isNaN(number) ? 0 : number
api.setValue(this.key('target'), this._target)
}
get target() {
return this._target
}
}
new Vue({
computed: {
title() {
return `${api.name} ${api.state}`
},
},
watch: {
title(value) {
document.title = value
},
},
})
document.addEventListener('DOMContentLoaded', () => api.addStyle(`
`), false)
})()