一些已知问题修复,增加钟离的面板彩蛋图

This commit is contained in:
yoimiya-kokomi 2022-09-09 04:34:32 +08:00
parent 6c8952c885
commit a0b51d6083
28 changed files with 129 additions and 110 deletions

View File

@ -116,7 +116,8 @@ class Puppeteer {
const page = await this.browser.newPage() const page = await this.browser.newPage()
await page.goto(`file://${_path}${lodash.trim(savePath, '.')}`) await page.goto(`file://${_path}${lodash.trim(savePath, '.')}`)
let body = await page.$('#container') || await page.$('body') let body = await page.$('#container') || await page.$('body')
await page.waitForSelector('#container')
await page.waitForTimeout(100)
let randData = { let randData = {
// encoding: 'base64', // encoding: 'base64',
type: data.imgType || 'jpeg', type: data.imgType || 'jpeg',

View File

@ -48,16 +48,23 @@ class Mys {
return false return false
} }
let e = this.e let e = this.e
// 防止错误信息刷屏 // 暂时先在plugin侧阻止错误防止刷屏
e._original_reply = e._original_reply || e.reply e._original_reply = e._original_reply || e.reply
e._reqCount = e._reqCount || 0
e.reply = function (msg) { e.reply = function (msg) {
if (!e._isReplyed) { if (!e._isReplyed) {
e._isReplyed = true e._isReplyed = true
return e._original_reply(msg) return e._original_reply(msg)
} else {
// console.log('请求错误')
} }
} }
e._reqCount++
let ret = await MysInfo.get(this.e, api, data) let ret = await MysInfo.get(this.e, api, data)
e._reqCount--
if (e._reqCount === 0) {
e.reply = e._original_reply e.reply = e._original_reply
}
if (!ret) { if (!ret) {
return false return false
} }

View File

@ -68,7 +68,7 @@ export async function wiki (e) {
return await renderWiki({ e, char }) return await renderWiki({ e, char })
} }
return await Common.render('wiki/character-talent', { return await Common.render('wiki/character-talent', {
saveId: `${mode}-${char.id}-${char.elem}`, // saveId: `${mode}-${char.id}-${char.elem}`,
...char.getData(), ...char.getData(),
detail: char.getDetail(), detail: char.getDetail(),
imgs: char.getImgs(), imgs: char.getImgs(),
@ -87,7 +87,7 @@ async function renderWiki ({ e, char }) {
let artis = await CharWiki.getArtis(char.id) let artis = await CharWiki.getArtis(char.id)
return await Common.render('wiki/character-wiki', { return await Common.render('wiki/character-wiki', {
saveId: `info-${char.id}`, // saveId: `info-${char.id}`,
data, data,
attr: char.getAttrList(), attr: char.getAttrList(),
detail: char.getDetail(), detail: char.getDetail(),

View File

@ -189,7 +189,7 @@ let EnkaData = {
if (ret._fix) { if (ret._fix) {
return ret return ret
} }
let { attr, id } = ret let { attr, id, weapon } = ret
id = id * 1 id = id * 1
switch (id) { switch (id) {
case 10000052: case 10000052:
@ -201,6 +201,15 @@ let EnkaData = {
attr.dmg = Math.max(0, attr.dmg - attr.recharge * 0.2) attr.dmg = Math.max(0, attr.dmg - attr.recharge * 0.2)
break break
} }
let wDmg = {
息灾: 12,
波乱月白经津: 12,
雾切之回光: 12,
猎人之径: 12
}
let { name, affix } = weapon
// 修正武器的加伤
attr.dmg = Math.max(0, attr.dmg - wDmg[name] - wDmg[name] * (affix - 1) / 4)
ret._fix = true ret._fix = true
return ret return ret
} }

View File

@ -16,7 +16,7 @@ export const customCharacters = {
10000026: ['魈', '风夜叉'], 10000026: ['魈', '风夜叉'],
// 自定义角色角色id请以小写英文定义 // 自定义角色角色id请以小写英文定义
sb: ['散兵', '国崩'] sanbing: ['散兵', '国崩']
} }
/* /*

View File

@ -37,9 +37,10 @@ export const enkaApi = {
export const requestInterval = 5 export const requestInterval = 5
/* /*
* MiaoApi面板更新地址暂时支持B服角色 * MiaoApi面板更新服务需要具备Token
* 默认使用Enka服务进行更新
* */ * */
export const miaoApi = { export const miaoApi = {
url: 'http://49.232.91.210/profile', url: 'http://miaoapi.cn/profile',
token: '请求Token' token: '请求Token'
} }

View File

@ -1,15 +1,6 @@
/* /*
* 请不要直接修改此或删除此文件防止后续更新冲突 * 请不要直接修改此或删除此文件防止后续更新冲突
* 如需新增自定义角色可复制此文件改名为character.js * 如需新增自定义角色可复制config/character_default.js改名为config/character.js
* 复制的character.js中可按格式及自己需求进行配置
* 最终character.js character_default.js两份配置会叠加生效
*
* 暂未做热更新修改完毕请重启yunzai
* */
/*
* 角色列表别名的第一个是标准名字后面的为别名
* 实装的角色需要以数字roleid为key自定义的角色及非实装角色请以英文为key
* */ * */
export const characters = { export const characters = {
10000003: ['琴', 'Jean', '团长', '代理团长', '琴团长', '蒲公英骑士'], 10000003: ['琴', 'Jean', '团长', '代理团长', '琴团长', '蒲公英骑士'],
@ -76,7 +67,7 @@ export const characters = {
10000071: ['赛诺', 'cyno', '塞诺', '胡狼'], 10000071: ['赛诺', 'cyno', '塞诺', '胡狼'],
10000072: ['坎蒂丝', 'candace', '坎迪斯'], 10000072: ['坎蒂丝', 'candace', '坎迪斯'],
// 以下为Miao新增自定义角色 // 自定义角色
paimon: ['派蒙', '应急食物', '应急食品', '吉祥物', '宠物', '外置器官', '会说话的动物', '矮堇瓜', '飞行矮堇瓜', '最好的伙伴'], paimon: ['派蒙', '应急食物', '应急食品', '吉祥物', '宠物', '外置器官', '会说话的动物', '矮堇瓜', '飞行矮堇瓜', '最好的伙伴'],
sanbing: ['散兵', '国崩', '雷电国崩', '大炮', '雷电大炮', '雷大炮', '伞兵', '斯卡拉姆齐'], sanbing: ['散兵', '国崩', '雷电国崩', '大炮', '雷电大炮', '雷大炮', '伞兵', '斯卡拉姆齐'],
nvshi: ['女士', '炽热的炎之魔女', '炎之魔女', '罗莎琳'], nvshi: ['女士', '炽热的炎之魔女', '炎之魔女', '罗莎琳'],
@ -97,24 +88,13 @@ export const characters = {
alhaitham: ['艾尔海森', '海哥', '埃尔海森'] alhaitham: ['艾尔海森', '海哥', '埃尔海森']
} }
/*
* 追加设置每个关系的可选角色会与yunzai的设置同时起作用
* 一个角色可以在多个关系中
* */
export const wifeData = { export const wifeData = {
// 老婆&女朋友:成女、少女
girlfriend: `琴, 丽莎, 荧, 芭芭拉, 安柏, 香菱, 北斗, 凝光, 菲谢尔, 诺艾尔, 甘雨, 莫娜, 刻晴, 砂糖, 辛焱, 罗莎莉亚, 胡桃, girlfriend: `琴, 丽莎, 荧, 芭芭拉, 安柏, 香菱, 北斗, 凝光, 菲谢尔, 诺艾尔, 甘雨, 莫娜, 刻晴, 砂糖, 辛焱, 罗莎莉亚, 胡桃,
烟绯, 优菈, 神里绫华, 宵宫, 雷电将军, 珊瑚宫心海, 九条裟罗, 八重神子, 埃洛伊, 申鹤, 云堇, 夜兰, 久岐忍, 柯莱, 多莉, 伐难, 烟绯, 优菈, 神里绫华, 宵宫, 雷电将军, 珊瑚宫心海, 九条裟罗, 八重神子, 埃洛伊, 申鹤, 云堇, 夜兰, 久岐忍, 柯莱, 多莉, 伐难,
女士, 萍姥姥, 柯莱, 多莉, 仆人, 少女, 妮露`, 女士, 萍姥姥, 柯莱, 多莉, 仆人, 少女, 妮露,坎蒂丝`,
// 老公&男朋友:成男、少男
boyfriend: `空, 凯亚, 迪卢克, 雷泽, 温迪, 行秋, 魈, 钟离, 班尼特, 达达利亚, 重云, 阿贝多, 枫原万叶, 托马, 五郎, 荒泷一斗, boyfriend: `空, 凯亚, 迪卢克, 雷泽, 温迪, 行秋, 魈, 钟离, 班尼特, 达达利亚, 重云, 阿贝多, 枫原万叶, 托马, 五郎, 荒泷一斗,
鹿野院平藏, 神里绫人, 提纳里, 散兵, 白术, 提纳里, 富人, 博士, 丑角, 公鸡, 队长`, 鹿野院平藏, 神里绫人, 提纳里, 散兵, 白术, 提纳里, 富人, 博士, 丑角, 公鸡, 队长,赛诺`,
// 女儿:萝莉
daughter: '可莉, 七七, 迪奥娜, 早柚, 派蒙, 瑶瑶, 纳西妲', daughter: '可莉, 七七, 迪奥娜, 早柚, 派蒙, 瑶瑶, 纳西妲',
// 儿子:正太
son: '' son: ''
} }

View File

@ -13,7 +13,7 @@ export const getProfileServ = ({ uid, serv, diyCfg }) => {
} }
export const miaoApi = { export const miaoApi = {
url: 'http://49.232.91.210/profile', url: 'http://miaoapi.cn/profile',
listApi: ({ url, uid, diyCfg }) => { listApi: ({ url, uid, diyCfg }) => {
return `${url}/data?uid=${uid}&token=${diyCfg.token}` return `${url}/data?uid=${uid}&token=${diyCfg.token}`
} }
@ -31,7 +31,6 @@ export const enkaApi = {
} }
} }
/* 请求面板的冷却时间,单位分钟 */
export const requestInterval = 5 export const requestInterval = 5
export const isSys = true export const isSys = true

View File

@ -4,11 +4,6 @@ let cacheMap = {}
let reFn = {} let reFn = {}
export default class Base { export default class Base {
toString () {
return this?.name || ''
}
getData (arrList = '', cfg = {}) { getData (arrList = '', cfg = {}) {
arrList = arrList || this._dataKey || '' arrList = arrList || this._dataKey || ''
return Data.getData(this, arrList, cfg) return Data.getData(this, arrList, cfg)
@ -19,28 +14,65 @@ export default class Base {
return Data.getVal(this, key, defaultValue) return Data.getVal(this, key, defaultValue)
} }
// 获取缓存
_getCache (uuid = '', time = 10 * 60) { _getCache (uuid = '', time = 10 * 60) {
if (uuid && cacheMap[uuid]) { if (uuid && cacheMap[uuid]) {
return cacheMap[uuid]._setCache(time) return cacheMap[uuid]._expire(time)
} }
this._uuid = uuid this._uuid = uuid
} }
_setCache (time = 10 * 60) { // 设置缓存
_cache (time = 10 * 60) {
let id = this._uuid
this._expire(time)
cacheMap[id] = this._proxy()
return cacheMap[id]
}
// 设置超时时间
_expire (time = 10 * 60) {
this._delCache()
if (time > 0) {
let id = this._uuid let id = this._uuid
if (id) { if (id) {
reFn[id] && clearTimeout(reFn[id])
delete reFn[id]
reFn[id] = setTimeout(() => { reFn[id] = setTimeout(() => {
reFn[id] && clearTimeout(reFn[id]) reFn[id] && clearTimeout(reFn[id])
delete reFn[id] delete reFn[id]
delete cacheMap[id] delete cacheMap[id]
}, time * 1000) }, time * 1000)
cacheMap[id] = this
} }
return this return cacheMap[id]
} else {
return this._delCache()
}
} }
}
Base.get = (id, time = 10 * 60) => {
// 删除缓存
_delCache () {
let id = this._uuid
let ret = reFn[id] || false
if (id) {
reFn[id] && clearTimeout(reFn[id])
delete reFn[id]
}
return ret
}
// 返回代理对象
_proxy () {
return new Proxy(this, {
get (self, key) {
if (key in self) {
return self[key]
}
if (self._get) {
if (key in self) {
return self._get(key)
}
}
return (self._meta || self._data || self.meta || {})[key]
}
})
}
} }

View File

@ -30,7 +30,7 @@ class Character extends Base {
} else { } else {
this.meta = {} this.meta = {}
} }
return this._setCache() return this._cache()
} }
// 默认获取的数据 // 默认获取的数据

View File

@ -3,7 +3,6 @@ import Base from './Base.js'
import { Data } from '../components/index.js' import { Data } from '../components/index.js'
import MaterialMeta from './material-lib/MaterialMeta.js' import MaterialMeta from './material-lib/MaterialMeta.js'
let data = Data.readJSON('resources/meta/material/data.json') let data = Data.readJSON('resources/meta/material/data.json')
let mMap = {} let mMap = {}
let getItem = (ds) => { let getItem = (ds) => {
@ -41,7 +40,7 @@ class Material extends Base {
this.meta = meta this.meta = meta
this.type = meta.type this.type = meta.type
this.star = meta.star this.star = meta.star
return this._setCache() return this._cache()
} }
get abbr () { get abbr () {

View File

@ -16,6 +16,7 @@ export default class ProfileArtis extends Base {
if (ds) { if (ds) {
this.setArtisSet(ds) this.setArtisSet(ds)
} }
return this._proxy()
} }
setProfile (profile, artis) { setProfile (profile, artis) {
@ -53,28 +54,15 @@ export default class ProfileArtis extends Base {
}) })
} }
get 1 () { _get (key) {
return this.artis[1] let artis = this.artis
switch (key) {
case 'length':
return artis.length
} }
if (artis[key]) {
get 2 () { return artis[key]
return this.artis[2]
} }
get 3 () {
return this.artis[3]
}
get 4 () {
return this.artis[4]
}
get 5 () {
return this.artis[5]
}
get length () {
return lodash.keys(this.artis).length
} }
toJSON () { toJSON () {

View File

@ -94,10 +94,12 @@ export default class ProfileData extends Base {
get costume () { get costume () {
let cMap = { let cMap = {
// 10000033: 900001, // 公子 // 10000033: 900001, // 公子
10000052: 900002 // 雷神 10000052: 900002, // 雷神
10000030: 900003 // 钟离
} }
let talent = this.talent ? lodash.map(this.talent, (ds) => ds.original).join('') : ''
if (cMap[this.id]) { if (cMap[this.id]) {
if (this.cons === 6 || ['ACE', 'ACE²'].includes(this.artis?.markClass)) { if (this.cons === 6 || ['ACE', 'ACE²'].includes(this.artis?.markClass) || talent === '101010') {
return cMap[this.id] return cMap[this.id]
} }
} }
@ -125,7 +127,7 @@ export default class ProfileData extends Base {
// 获取当前profileData的圣遗物评分withDetail=false仅返回简略信息 // 获取当前profileData的圣遗物评分withDetail=false仅返回简略信息
getArtisMark (withDetail = true) { getArtisMark (withDetail = true) {
if(this.hasData) { if (this.hasData) {
return this.artis.getMarkDetail(withDetail) return this.artis.getMarkDetail(withDetail)
} }
return {} return {}

View File

@ -9,7 +9,6 @@ lodash.forEach(data, (ds) => {
wData[ds.name] = ds wData[ds.name] = ds
}) })
class Weapon extends Base { class Weapon extends Base {
constructor (name) { constructor (name) {
super(name) super(name)
@ -25,7 +24,7 @@ class Weapon extends Base {
this.meta = meta this.meta = meta
this.type = meta.type this.type = meta.type
this.star = meta.star this.star = meta.star
return this._setCache() return this._cache()
} }
get abbr () { get abbr () {

View File

@ -1,5 +1,5 @@
{ {
"id": 10000007, "id": 20000000,
"name": "旅行者", "name": "旅行者",
"abbr": "旅行者", "abbr": "旅行者",
"title": "异界的旅人", "title": "异界的旅人",
@ -10,8 +10,8 @@
"birth": "-", "birth": "-",
"astro": "旅人座", "astro": "旅人座",
"desc": "从世界之外漂流而来的旅行者,被神带走血亲,自此踏上寻找七神之路。", "desc": "从世界之外漂流而来的旅行者,被神带走血亲,自此踏上寻找七神之路。",
"cncv": "多多poi", "cncv": "宴宁/鹿喑",
"jpcv": "古贺葵", "jpcv": "悠木碧/堀江瞬",
"costume": false, "costume": false,
"ver": 1, "ver": 1,
"baseAttr": { "baseAttr": {

View File

@ -8,7 +8,7 @@
"allegiance": "蒙德城", "allegiance": "蒙德城",
"weapon": "bow", "weapon": "bow",
"birth": "6-16", "birth": "6-16",
"astro": "歌仙座歌仙座", "astro": "歌仙座",
"desc": "蒙德城诸多吟游诗人中的一位,自由自在地穿行在街头巷尾。", "desc": "蒙德城诸多吟游诗人中的一位,自由自在地穿行在街头巷尾。",
"cncv": "喵酱", "cncv": "喵酱",
"jpcv": "村濑步", "jpcv": "村濑步",

View File

@ -10,8 +10,8 @@
"birth": "-", "birth": "-",
"astro": "旅人座", "astro": "旅人座",
"desc": "从世界之外漂流而来的旅行者,被神带走血亲,自此踏上寻找七神之路。", "desc": "从世界之外漂流而来的旅行者,被神带走血亲,自此踏上寻找七神之路。",
"cncv": "鹿喑", "cncv": "鹿喑&多多poi",
"jpcv": "堀江瞬", "jpcv": "堀江瞬&古贺葵",
"costume": false, "costume": false,
"ver": 1, "ver": 1,
"baseAttr": { "baseAttr": {

View File

@ -10,8 +10,8 @@
"birth": "-", "birth": "-",
"astro": "旅人座", "astro": "旅人座",
"desc": "从世界之外漂流而来的旅行者,被神带走血亲,自此踏上寻找七神之路。", "desc": "从世界之外漂流而来的旅行者,被神带走血亲,自此踏上寻找七神之路。",
"cncv": "多多poi", "cncv": "宴宁&多多poi",
"jpcv": "古贺葵", "jpcv": "悠木碧&古贺葵",
"costume": false, "costume": false,
"ver": 1, "ver": 1,
"baseAttr": { "baseAttr": {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -8,11 +8,13 @@
"allegiance": "璃月港", "allegiance": "璃月港",
"weapon": "polearm", "weapon": "polearm",
"birth": "12-31", "birth": "12-31",
"astro": "岩王帝君座", "astro": "岩王帝君座",
"desc": "被「往生堂」请来的神秘客人,知识渊博,对各种事物都颇有见地。", "desc": "被「往生堂」请来的神秘客人,知识渊博,对各种事物都颇有见地。",
"cncv": "彭博", "cncv": "彭博",
"jpcv": "前野智昭", "jpcv": "前野智昭",
"costume": false, "costume": [
900003
],
"ver": 1, "ver": 1,
"baseAttr": { "baseAttr": {
"hp": 14695, "hp": 14695,

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 506 KiB

View File

@ -8,7 +8,7 @@
"allegiance": "稻妻城", "allegiance": "稻妻城",
"weapon": "polearm", "weapon": "polearm",
"birth": "6-26", "birth": "6-26",
"astro": "天下人座天下人座", "astro": "天下人座",
"desc": "其身为御建鸣神主尊大御所大人,许稻妻人民以亘古不变之「永恒」。", "desc": "其身为御建鸣神主尊大御所大人,许稻妻人民以亘古不变之「永恒」。",
"cncv": "菊花花", "cncv": "菊花花",
"jpcv": "泽城美雪", "jpcv": "泽城美雪",

View File

@ -18,7 +18,7 @@ if (fs.existsSync(_mRoot + 'data.json')) {
const tElems = ['anemo', 'geo', 'electro', 'dendro'] const tElems = ['anemo', 'geo', 'electro', 'dendro']
let getCharData = async function (id, key, name = '') { let getCharData = async function (id, key, name = '', _id = id) {
let idNum = (id < 10 ? '0' : '') + (id < 100 ? '0' : '') + id let idNum = (id < 10 ? '0' : '') + (id < 100 ? '0' : '') + id
let url = `https://genshin.honeyhunterworld.com/${key}_${idNum}/?lang=CHS` let url = `https://genshin.honeyhunterworld.com/${key}_${idNum}/?lang=CHS`
console.log('req: ' + url) console.log('req: ' + url)
@ -53,7 +53,7 @@ let getCharData = async function (id, key, name = '') {
} }
} }
} }
let data = CharData.getBasic($, id, name) let data = CharData.getBasic($, id, name, _id)
name = name || data.name name = name || data.name
let imgs = new ImgDownloader(name) let imgs = new ImgDownloader(name)
$.imgs = imgs $.imgs = imgs
@ -140,14 +140,14 @@ function checkName (name) {
return data.ver * 1 > 1 return data.ver * 1 > 1
} }
async function saveCharData (id, key, name = '', force = false) { async function saveCharData (id, key, name = '', force = false, _id = id) {
if (!id || !key) { if (!id || !key) {
return return
} }
if (name && checkName(name) && !force) { if (name && checkName(name) && !force) {
return return
} }
let { data, details, imgs } = await getCharData(id, key, name) let { data, details, imgs } = await getCharData(id, key, name, _id)
name = name || data.name name = name || data.name
if (!name) { if (!name) {
@ -185,7 +185,7 @@ async function down (name = '', force = false) {
if (!names.includes(id) && !names.includes(ds.key) && !names.includes(ds.name)) { if (!names.includes(id) && !names.includes(ds.key) && !names.includes(ds.name)) {
continue continue
} }
await saveCharData(ds.id || id, ds.key, ds.name, force) await saveCharData(ds.id || id, ds.key, ds.name, force, id)
} }
fs.writeFileSync(`${_mRoot}data.json`, JSON.stringify(mData, '', 2)) fs.writeFileSync(`${_mRoot}data.json`, JSON.stringify(mData, '', 2))
} }
@ -251,4 +251,4 @@ const charData = {
71: { key: 'cyno', name: '赛诺' }, 71: { key: 'cyno', name: '赛诺' },
72: { key: 'candace', name: '坎蒂丝' } 72: { key: 'candace', name: '坎蒂丝' }
} }
await down('妮露,赛诺,坎蒂丝', true) await down('4', true)

View File

@ -9,33 +9,33 @@ let costumes = {
迪卢克: [201601], // 迪卢克 迪卢克: [201601], // 迪卢克
菲谢尔: [203101], // 菲谢尔 菲谢尔: [203101], // 菲谢尔
达达利亚: [900001], 达达利亚: [900001],
雷电将军: [900002] 雷电将军: [900002],
钟离: [900003]
} }
const fixData = { const fixData = {
4: { 4: {
id: 20000000, id: 20000000,
title: '异界的旅人' title: '异界的旅人',
cncv: '宴宁/鹿喑',
jpcv: '悠木碧/堀江瞬'
}, },
5: { 5: {
title: '异界的旅人', title: '异界的旅人'
cncv: '鹿喑',
jpcv: '堀江瞬'
}, },
7: { 7: {
title: '异界的旅人', title: '异界的旅人'
cncv: '多多poi',
jpcv: '古贺葵'
} }
} }
const CharData = { const CharData = {
getBasic ($, id, name = '') { getBasic ($, id, name = '', _id = id) {
let ret = {} let ret = {}
let fix = fixData[id] || {} console.log(id, _id)
let fix = fixData[_id || id] || {}
ret.id = fix.id || 10000000 + id * 1 ret.id = fix.id || 10000000 + id * 1
let basic = $('.genshin_table.main_table') let basic = $('.genshin_table.main_table')
let title = function (title) { let title = function (title) {
return basic.find(`td:contains('${title}')`).next('td').text().trim() return basic.find(`td:contains('${title}'):last`).next('td').text().trim()
} }
ret.name = name || title('Name') ret.name = name || title('Name')
ret.abbr = abbr[ret.name] || ret.name ret.abbr = abbr[ret.name] || ret.name