Beispiel #1
0
 def exRecInfo(self, rec):
     """从rec api中提取信息"""
     mod = mods.getMod(rec['enabled_mods'])
     if 'NONE' in mod:
         mod.remove('NONE')
     ret = {
         "mods": ''.join(mod),
         "acc": mods.get_acc(rec['count300'], rec['count100'], rec['count50'], rec['countmiss']),
         "cb": rec['maxcombo'],
         "miss": rec['countmiss']
     }
     return ret
Beispiel #2
0
 def bbpOutFormat(self, bp5, ousname):
     """bbp输出格式化
     """
     s_msg = "%s's bp!!\n" % ousname
     for i,r in enumerate(bp5[0:5]):
         msg = 'bp{x},{pp}pp,{acc}%,{rank},+{mod}'
         c50 = float(r['count50'])
         c100 = float(r['count100'])
         c300 = float(r['count300'])
         cmiss = float(r['countmiss'])
         acc = round((c50*50+c100*100+c300*300)/(c50+c100+c300+cmiss)/300*100, 2)
         msg = msg.format(
             x=i+1,
             pp=round(float(r['pp'])),
             acc=acc,rank=r['rank'],
             mod=','.join(mods.getMod(int(r['enabled_mods'])))
         )
         s_msg = s_msg + msg + '\n'
     return s_msg[:-1]
Beispiel #3
0
def get_rankinfo(uid, groupid, bid, hid=1, mod=-1):
    logging.info('查询用户[%s]'%uid)
    ret = score.hid_ranks(bid, groupid, hid, mod)
    o = osu_user.Osuer()
    if not ret:
        return '榜上一个成绩都没有!!! https://osu.ppy.sh/b/%s' % bid
    ret = ret[0]
    outstr = '%s(%s)-%s[%s]\n' % (ret['source'], ret['artist'], ret['title'], ret['version'])
    uids = []
    for i,r in enumerate(json.loads(ret['rankjson'])[:10]):
        for k,v in r.items():
            uid = k
            uids.append(uid)
            sco = v
        m = ','.join(mods.getMod(int(sco[5])))
        outstr += '#%s {%s} %s分 [%sx] %s%% %s +%s\n' % (i+1, uid, sco[0], sco[1], sco[3], sco[4], m)
    res = o.get_usernames_by_uid(uids)
    for r in res:
        restr = '{%s}' % r['osuid']
        outstr = outstr.replace(restr, r['osuname'])
    return outstr[:-1]
Beispiel #4
0
    def osuV2stat(self, qq, groupid):
        rs = ""
        status, ret = self.getV2osuInfo(qq, groupid)
        if status < 0:
            return ret

        uid = ret["id"]
        grade_counts = ret['statistics']['grade_counts']
        pp = ret['statistics']['pp']
        pc = ret['statistics']['play_count']
        acc = ret['statistics']['hit_accuracy']
        username = ret["username"]
        join_time = ret['join_date'].split('T')[0]

        total_hits = ret["statistics"]["total_hits"]
        tth_pc_incr = int(total_hits / pc)
        total_hits_str = self.numberFormat2w(total_hits)
        tth_pc_incr_str = self.numberFormat2w(tth_pc_incr)

        play_time = ret["statistics"]["play_time"]
        play_days = play_time / 84600
        play_hours = play_time % 84600 / 3600

        if not ret['last_visit']:
            last_visit_str = '?'
        else:
            last_visit_f, last_visit_s = ret['last_visit'].split('T')
            last_visit_utc = '%s %s' % (last_visit_f,
                                        last_visit_s.split('+')[0])
            last_visit = datetime.strptime(
                last_visit_utc, '%Y-%m-%d %H:%M:%S') + timedelta(hours=8)
            last_visit_str = datetime.strftime(last_visit, '%Y-%m-%d %H:%M')

        follower_count = ret["follower_count"]
        avatar = ret["avatar_url"]
        join_days = (datetime.now() -
                     datetime.strptime(join_time, '%Y-%m-%d')).days
        pp_days_incr = round(float(pp) / join_days, 2)
        pc_days_incr = round(float(pc) / join_days, 2)

        # bp
        botIns = botHandler.botHandler()
        bpinfo = botIns.getRecBp(uid, "5")
        bp1 = bpinfo[0]
        c50, c100, c300, cmiss = int(bp1['count50']), int(
            bp1['count100']), int(bp1['count300']), int(bp1['countmiss'])
        bp1_acc = round((c50 * 50 + c100 * 100 + c300 * 300) /
                        (c50 + c100 + c300 + cmiss) / 300 * 100, 2)
        bp1_mod = ','.join(mods.getMod(int(bp1['enabled_mods'])))
        bp1_pp = round(float(bp1['pp']))
        bp1_rank = bp1['rank']
        bp1_data = bp1['date']
        bp1_bid = bp1['beatmap_id']
        bp1_days = (datetime.now() -
                    datetime.strptime(bp1_data, '%Y-%m-%d %H:%M:%S')).days
        if bp1_days > 365:
            bp1_days_str = '%s年前' % int(bp1_days / 365)
        elif bp1_days > 182:
            bp1_days_str = '半年前'
        elif bp1_days > 30:
            bp1_days_str = '%s个月前' % int(bp1_days / 30)
        else:
            bp1_days_str = '%s天前' % bp1_days
        # map
        bp1_mapInfo = botIns.getOsuBeatMapInfo(bp1_bid)
        bp1_stars = bp1_mapInfo["difficultyrating"][:3]

        rs += '{username}\n'.format(username=username)
        rs += '[CQ:image,cache=0,file={avatar}]\n'.format(avatar=avatar)
        rs += '{pp}pp ({pp_days_incr}/day)\n'.format(pp=int(pp),
                                                     pp_days_incr=pp_days_incr)
        rs += '{pc}pc ({pc_days_incr}/day)\n'.format(pc=pc,
                                                     pc_days_incr=pc_days_incr)
        rs += '{tth}tth ({tth_pc_incr}/pc)\n'.format(
            tth=total_hits_str, tth_pc_incr=tth_pc_incr_str)
        rs += '--------------------\n'
        rs += 'SS+({ssh}) | SS({ss}) | S+({sh}) | S({s}) | A({a})\n'.format(
            **grade_counts)
        rs += 'bp1: {pp}pp,{stars}*,{acc}%,+{mod}({d})\n'.format(
            pp=bp1_pp,
            acc=bp1_acc,
            mod=bp1_mod,
            d=bp1_days_str,
            stars=bp1_stars)
        rs += '--------------------\n'
        rs += '粉丝数: {follower_count}\n'.format(follower_count=follower_count)
        rs += '爆肝时长: {play_days}天{play_hours}小时\n'.format(
            play_days=int(play_days), play_hours=int(play_hours))
        rs += '最后登录: {last_visit_str}\n'.format(last_visit_str=last_visit_str)
        rs += '注册时间: {join_time} ({join_days}天)'.format(join_time=join_time,
                                                        join_days=join_days)
        return rs
Beispiel #5
0
def drawR(mapjson, rankjson, userjson):
    # skin
    bg_e = draw_data.check_bg(mapjson['beatmap_id'], mapjson['beatmapset_id'])
    bg = '%s.jpg' % mapjson['beatmap_id'] if bg_e else 'newgame_background.png'
    back_icon = 'menu-back-0.png'
    mod_icon = 'selection-mode.png'
    mods_icon = 'selection-mods.png'
    random_icon = 'selection-random.png'
    options_icon = 'selection-options.png'
    rank_x = 'ranking-%s-small.png'

    # ous ui
    songselecttop = 'songselect-top.png'
    uptips = 'selection-update.png'
    osu_icon = 'menu-osu.png'
    songselect_bottom = 'songselect-bottom.png'
    level_bar = 'levelbar.png'
    level_bar_bg = 'levelbar-bg.png'
    selection_approved = 'selection-approved.png'

    # 用户信息
    uname = userjson.get('username', '')
    pp = f"{float(userjson.get('pp_raw', 0)):,.0f}"
    acc = round(float(userjson.get('accuracy', 0)), 2)
    lv = float(userjson.get('level', 0))
    level = int(lv)
    lv_left = lv - level  # 小数位,经验条
    rank = userjson.get('pp_rank', 0)
    umod = 'mode-osu-small.png'

    #曲子信息
    title = mapjson.get('title_unicode', '')
    source = mapjson.get('source', '')
    artist = mapjson.get('artist_unicode', '')
    version = mapjson.get('version', '')
    creator = mapjson.get('creator', '')
    bpm = mapjson.get('bpm', '')
    max_combo = mapjson.get('max_combo', '')
    difficultyrating = round(float(mapjson.get('difficultyrating', '')),
                             2)  #stars
    diff_size = mapjson.get('diff_size', '')  #CS
    diff_approach = mapjson.get('diff_approach', '')  #AR
    diff_overall = mapjson.get('diff_overall', '')  #OD
    diff_drain = mapjson.get('diff_drain', '')  #HP
    count_normal = int(mapjson.get('count_normal', 0))
    count_slider = int(mapjson.get('count_slider', 0))
    count_spinner = int(mapjson.get('count_spinner', 0))

    m, s = divmod(int(mapjson.get('total_length')), 60)
    h, m = divmod(m, 60)
    if h != 0:
        hit_length = "%02d:%02d:%02d" % (h, m, s)  #sec
    else:
        hit_length = "%02d:%02d" % (m, s)  #sec

    # 头像download
    uids = [list(r.keys())[0] for r in rankjson]
    check_uids = uids[:12]
    me = userjson.get('user_id', '')
    if me not in uids:
        check_uids.append(me)
        me_idx = -1
    else:
        me_idx = uids.index(me)
    draw_data.check_img(check_uids)

    d = DrawRec()

    # 第一层bg
    # d.add_items(isresize=True, path='image/bg/default.jpg')
    d.add_items(isresize=True, path='image/bg/%s' % bg)
    # title黑层
    d.add_items2(songselecttop)
    # 更新提示
    # d.add_items2(uptips, 20, 200)
    # 低下大黑条
    d.add_items2(songselect_bottom,
                 0,
                 648,
                 isresize=True,
                 width=1366,
                 height=120)
    # 大粉饼
    d.add_items2(osu_icon, 1130, 550, isresize=True, width=300, height=300)
    # 返回
    d.add_items(back_icon, 10, 615)

    # mode
    d.add_items(mod_icon, 250, 678)
    # mods选择
    d.add_items(mods_icon, 340, 678)
    # random
    d.add_items(random_icon, 430, 678)
    # options
    d.add_items(options_icon, 520, 678)

    # 头像
    d.add_items(x=690,
                y=675,
                path='image/userimg/%s.jpg' % me,
                isresize=True,
                width=90,
                height=90)
    # 用户信息
    d.add_items2(umod, 998, 670, factor=0.5)
    d.add_text(968, 710, '# %s' % rank, font_size=16, ttype='en')
    d.add_text(788, 670, uname, font_size=24, ttype='en')
    d.add_text(788, 700, 'Performance:%spp' % pp, font_size=16, ttype='en')
    d.add_text(788, 720, 'Accuracy:%s%%' % acc, font_size=16, ttype='en')
    d.add_text(788, 740, 'Lv:%s' % level, font_size=16, ttype='en')
    if lv_left > 0.1:
        d.add_items2(level_bar,
                     840,
                     745,
                     isresize=True,
                     width=int(lv_left * 200),
                     height=14)
    d.add_items2(level_bar_bg, 840, 745)

    # 曲子信息
    bid = mapjson['beatmap_id']
    d.add_text(35,
               0,
               '%s (%s) - %s [%s]' % (source, artist, title, version),
               font_size=25,
               ttype='cn')
    d.add_items2(selection_approved, 7, 3)
    d.add_text(40,
               30,
               '作者: %s   [bid: %s]' % (creator, bid),
               font_size=16,
               ttype='cn')
    d.add_text(5,
               50,
               '长度: %s  BPM: %s  物件数: %s' %
               (hit_length, bpm, count_normal + count_slider + count_spinner),
               font_size=18,
               ttype='cn')
    d.add_text(5,
               75,
               '圈数: %s 滑条数: %s 转盘数: %s' %
               (count_normal, count_slider, count_spinner),
               font_size=16,
               ttype='cn')
    d.add_text(
        5,
        100,
        'CS:%s AR:%s OD:%s HP:%s Star:%s★' %
        (diff_size, diff_approach, diff_overall, diff_drain, difficultyrating),
        font_size=16,
        ttype='en')

    bid = mapjson['beatmap_id']
    d.add_text(1180, 30, f"bid: {bid}", font_size=25, ttype='en')

    # 榜区域
    nums = len(uids)
    o = botHandler.botHandler()
    res = o.get_usernames_by_uid(uids)
    udict = {r['osuid']: r['osuname'] for r in res}
    if nums > 6:
        r1 = 6
        r2 = nums - 6
    else:
        r1 = nums
        r2 = 0
    offset1 = 65
    for i in range(r1):
        r = rankjson[i]
        u = uids[i]
        mds = int(r[u][5])
        mds_l = mods.getMod(mds)
        m = ','.join(mds_l) if mds > 0 else ''
        rank = 'D' if r[u][4] == 'F' else r[u][4]
        d.draw_rectangle(x=20,
                         y=160 + i * offset1,
                         width=460,
                         height=60,
                         fill=(0, 0, 0, 50))
        d.add_items(x=20,
                    y=160 + i * offset1,
                    path='image/userimg/%s.jpg' % u,
                    isresize=True,
                    width=60,
                    height=60)
        d.add_items(rank_x % rank, 80, 170 + i * offset1)
        d.add_text(120,
                   160 + i * offset1,
                   '%s' % (udict.get(u, 'None')),
                   font_size=25,
                   ttype='en')
        d.add_text(120,
                   190 + i * offset1,
                   '得分: %s' % (format(int(r[u][0]), ',')),
                   font_size=20,
                   ttype='cn')
        d.add_text(300,
                   190 + i * offset1,
                   '(%sx)' % (format(int(r[u][1]), ',')),
                   font_size=20,
                   ttype='en')
        d.add_text(450 - 20 * len(mds_l),
                   165 + i * offset1,
                   '%s' % (m),
                   font_size=20,
                   ttype='en')
        d.add_text(400,
                   190 + i * offset1,
                   '%s%%' % (r[u][3]),
                   font_size=18,
                   ttype='en')

    d.add_text(150, 550, '个人最佳成绩', font_size=24, ttype='cn')
    d.draw_rectangle(x=20, y=590, width=460, height=60, fill=(0, 0, 0, 50))
    if me_idx != -1:
        r = rankjson[me_idx]
        u = uids[me_idx]
        mds = int(r[u][5])
        mds_l = mods.getMod(mds)
        m = ','.join(mds_l) if mds > 0 else ''
        rank = 'D' if r[u][4] == 'F' else r[u][4]
        d.add_items(x=20,
                    y=590,
                    path='image/userimg/%s.jpg' % me,
                    isresize=True,
                    width=60,
                    height=60)
        d.add_items(rank_x % rank, 80, 595)
        d.add_text(120,
                   590,
                   '%s' % (udict.get(u, 'None')),
                   font_size=25,
                   ttype='en')
        d.add_text(120,
                   620,
                   '得分: %s' % (format(int(r[u][0]), ',')),
                   font_size=20,
                   ttype='cn')
        d.add_text(300,
                   620,
                   '(%sx)' % (format(int(r[u][1]), ',')),
                   font_size=20,
                   ttype='en')
        d.add_text(450 - 20 * len(mds_l),
                   600,
                   '%s' % (m),
                   font_size=20,
                   ttype='en')
        d.add_text(410, 620, '%s%%' % (r[u][3]), font_size=18, ttype='en')
    else:
        d.add_text(130, 600, '你倒是快刚榜啊', font_size=25, ttype='cn')

    # copy
    for i in range(r2):
        if i == 6:
            break
        oi = i + 6
        r = rankjson[oi]
        u = uids[oi]
        mds = int(r[u][5])
        mds_l = mods.getMod(mds)
        m = ','.join(mds_l) if mds > 0 else ''
        rank = 'D' if r[u][4] == 'F' else r[u][4]
        d.draw_rectangle(x=620,
                         y=160 + i * offset1,
                         width=460,
                         height=60,
                         fill=(0, 0, 0, 50))
        d.add_items(x=620,
                    y=160 + i * offset1,
                    path='image/userimg/%s.jpg' % u,
                    isresize=True,
                    width=60,
                    height=60)
        d.add_items(rank_x % rank, 680, 170 + i * offset1)
        d.add_text(720,
                   160 + i * offset1,
                   '%s' % (udict.get(u, 'None')),
                   font_size=25,
                   ttype='en')
        d.add_text(720,
                   190 + i * offset1,
                   '得分: %s' % (format(int(r[u][0]), ',')),
                   font_size=20,
                   ttype='cn')
        d.add_text(900,
                   190 + i * offset1,
                   '(%sx)' % (format(int(r[u][1]), ',')),
                   font_size=20,
                   ttype='en')
        d.add_text(1050 - 20 * len(mds_l),
                   165 + i * offset1,
                   '%s' % (m),
                   font_size=20,
                   ttype='en')
        d.add_text(1010,
                   190 + i * offset1,
                   '%s%%' % (r[u][3]),
                   font_size=18,
                   ttype='en')

    n = random.randint(0, 100)
    p = 'rank%s.png' % n
    pfs = 'rank%s-fs8.png' % n
    f = '/static/interbot/image/%s' % p
    d.save(f)
    # 压缩
    os.system('pngquant -f %s' % f)
    logging.info('[%s]榜单生成成功!' % pfs)
    return pfs
Beispiel #6
0
def drawRec(mapjson, recinfo, bestinfo, userjson, debug=0, **kw):
    """
    kwargs:
        pp, fcpp, acpp

    """

    # skin
    bg_e = draw_data.check_bg(mapjson['beatmap_id'], mapjson['beatmapset_id'])
    bg = '%s.jpg'%mapjson['beatmap_id'] if bg_e else 'newgame_background.png' 
    back_icon = 'menu-back-0.png'
    mod_icon = 'selection-mode.png'
    mods_icon = 'selection-mods.png'
    random_icon = 'selection-random.png'
    options_icon = 'selection-options.png'
    rank_x = 'ranking-%s-small.png'
    maxcb_icon = 'ranking-maxcombo.png'
    acc_icon = 'ranking-accuracy.png'
    ranking_icon = 'ranking-title.png'
    rank_icon = 'ranking-%s.png'
    replay_icon = 'ranking-replay.png'
    score_icon = 'score-%s.png'
    score_x_icon = 'score-x.png'
    score_pp_icon = 'score-p.png' # 自制p字母
    score_dot_icon = 'score-dot.png'
    score_p_icon = 'score-percent.png'
    hit0_icon = 'hit0.png'
    hit50_icon = 'hit50.png'
    hit50k_icon = 'hit50k.png'
    hit100_icon = 'hit100.png'
    hit100k_icon = 'hit100k.png'
    
    mod_icon_map={
        'NF': 'selection-mod-nofail.png',
        'EZ': 'selection-mod-easy.png',
        'NV': 'selection-mod-random.png', # 这个未知
        'HD': 'selection-mod-hidden.png',
        'HR': 'selection-mod-hardrock.png',
        'SD': 'selection-mod-suddendeath.png',
        'DT': 'selection-mod-doubletime.png',
        'Relax': 'selection-mod-relax.png',
        'HT': 'selection-mod-halftime.png',
        'NC': 'selection-mod-nightcore.png',
        'FL': 'selection-mod-flashlight.png',
        'AT': 'selection-mod-autoplay.png',
        'SO': 'selection-mod-spunout.png',
        'AP': 'selection-mod-relax2.png',
        'PF': 'selection-mod-perfect.png',
    }

    # ous ui
    songselecttop = 'songselect-top.png'
    uptips = 'selection-update.png'
    osu_icon = 'menu-osu.png'
    songselect_bottom = 'songselect-bottom.png'
    level_bar = 'levelbar.png'
    level_bar_bg = 'levelbar-bg.png'
    selection_approved = 'selection-approved.png'

    # 用户信息
    uname = userjson.get('username', '')
    pp = f"{float(userjson.get('pp_raw', 0)):,.0f}"
    acc = round(float(userjson.get('accuracy', 0)), 2)
    lv = float(userjson.get('level', 0))
    level = int(lv)
    lv_left = lv - level # 小数位,经验条
    rank = userjson.get('pp_rank', 0)
    umod = 'mode-osu-small.png'

    #曲子信息
    title = mapjson.get('title_unicode','')
    source = mapjson.get('source','')
    artist = mapjson.get('artist_unicode','')
    version = mapjson.get('version','')
    creator = mapjson.get('creator','')
    bpm = kw['bpm'] if kw.get("bpm") else mapjson.get('bpm','')
    max_combo = mapjson.get('max_combo','')
    difficultyrating = kw['star'] if kw.get("star") else round(float(mapjson.get('difficultyrating','')),2)  #star
    diff_size = kw['cs'] if kw.get("cs") else mapjson.get('diff_size','') #CS
    diff_approach = kw['ar'] if kw.get("ar") else mapjson.get('diff_approach','') #AR
    diff_overall = kw['od'] if kw.get("od") else mapjson.get('diff_overall','') #OD
    diff_drain = kw['hp'] if kw.get("hp") else mapjson.get('diff_drain','') #HP
    count_normal = int(mapjson.get('count_normal', 0))
    count_slider = int(mapjson.get('count_slider', 0))
    count_spinner = int(mapjson.get('count_spinner', 0))


    m, s = divmod(int(mapjson.get('total_length')), 60)
    h, m = divmod(m, 60)
    if h != 0:
        hit_length = "%02d:%02d:%02d" % (h, m, s) #sec
    else:
        hit_length = "%02d:%02d" % (m, s) #sec

    # 头像download
    me = userjson.get('user_id', '')
    draw_data.check_img([me], isup=1)
    
    skin_idx = random.randint(1, len(skin_map))
    conf = skin_map[skin_idx]
    d = drawRank.DrawRec(skin=conf["path"])

    # 第一层bg
    d.add_items(isresize=True, path='image/bg/%s'%bg, factor=0.95)
    # title黑层
    d.add_items2(songselecttop)
    # 低下大黑条
    d.add_items2(songselect_bottom, 0, 648, isresize=True, width=1366, height=120)
    # 大粉饼
    d.add_items2(osu_icon, 1130, 550, isresize=True, width=300, height=300)
    # 返回
    d.add_items(back_icon, 10, 615)
    
    # 设置项
    d.add_items(mod_icon, 250, 678) # mode
    d.add_items(mods_icon, 340, 678) # mods选择
    d.add_items(random_icon, 430, 678) # random
    d.add_items(options_icon, 520, 678) # options

    # 头像
    d.add_items(x=690, y=675, path='image/userimg/%s.jpg'%me, isresize=True, width=90, height=90)
    # 用户信息
    d.add_items2(umod, 998, 670, factor=0.5)
    d.add_text(968, 710, '# %s'%rank, font_size=16, ttype='en')
    d.add_text(788, 670, uname, font_size=24, ttype='en')
    d.add_text(788, 700, 'Performance:%spp'%pp, font_size=16, ttype='en')
    d.add_text(788, 720, 'Accuracy:%s%%'%acc, font_size=16, ttype='en')
    d.add_text(788, 740, 'Lv%s'%level, font_size=16, ttype='en')
    if lv_left > 0.1:
        d.add_items2(level_bar, 840, 745, isresize=True, width=int(lv_left*200), height=14)
    d.add_items2(level_bar_bg, 840, 745)

    # 曲子信息
    bid = mapjson['beatmap_id']
    d.add_text(35, 0, '%s (%s) - %s [%s]'%(source,artist,title,version), font_size=25, ttype='cn')
    d.add_items2(selection_approved, 7, 3)
    d.add_text(40, 30, '作者: %s   [bid: %s]'%(creator, bid), font_size=16, ttype='cn')
    d.add_text(5, 50, '长度: %s  BPM: %s  物件数: %s'%(hit_length,bpm,count_normal+count_slider+count_spinner), font_size=18, ttype='cn')
    d.add_text(5, 75, '圈数: %s 滑条数: %s 转盘数: %s'%(count_normal,count_slider,count_spinner), font_size=16, ttype='cn')
    d.add_text(5, 100, 'CS:%s AR:%s OD:%s HP:%s Star:%s★'%(diff_size,diff_approach,diff_overall,diff_drain,difficultyrating), font_size=16, ttype='en')

    # 个人最佳成绩区域
    d.add_text(150, 550, '个人最佳成绩', font_size=24, ttype='cn')
    d.draw_rectangle(x=20, y=590, width=460, height=60, fill=(0, 0, 0, 90))

    mds = int(bestinfo['enabled_mods'])
    mds_l = mods.getMod(mds)
    if 'NONE' in mds_l:
        mds_l.remove('NONE')
    m_str = ','.join(mds_l) if mds > 0 else ''
    rank = 'D' if bestinfo['rank'] == 'F' else bestinfo['rank']
    d.add_items(x=20, y=590, path='image/userimg/%s.jpg'%me, isresize=True, width=60, height=60)
    d.add_items(rank_x%rank, 80, 595)
    d.add_text(120, 590, f"{uname}  #{round(float(bestinfo.get('pp', kw['pp'])))}pp", font_size=25, ttype='en')
    d.add_text(120, 620, f"得分: {int(bestinfo['score']):,}    ({int(bestinfo['maxcombo']):,}x)", font_size=20, ttype='cn')
    d.add_text(450-20*len(mds_l), 597, '%s'%(m_str), font_size=20, ttype='en')
    acc = mods.get_acc(bestinfo['count300'], bestinfo['count100'], bestinfo['count50'], bestinfo['countmiss'])
    d.add_text(410, 620, f"{acc:.2f}%", font_size=18, ttype='en')

    # 当前成绩区域
    d.draw_rectangle(x=20, y=160, width=600, height=60, fill=(0, 0, 0, 90))
    d.draw_rectangle(x=20, y=240, width=600, height=300, fill=(0, 0, 0, 90))

    # 分数icon
    d.add_items(hit100_icon, 40, **conf["hit100_icon"])
    d.add_items(hit50_icon, 40, **conf["hit50_icon"])
    d.add_items(hit100k_icon, 350, **conf["hit100k_icon"])
    d.add_items(hit0_icon, **conf["hit0_icon"])
    d.add_items(maxcb_icon, 40, 440)
    d.add_items(acc_icon, 330, 440)

    # score
    for idx, s in enumerate(recinfo['score'][::-1]):
        d.add_items(score_icon%s, 550-50*idx, 170)

    for idx, s in enumerate(recinfo['count300']+'x'):
        d.add_items(score_icon%s, 180+idx*35, 260)

    for idx, s in enumerate(recinfo['count100']+'x'):
        d.add_items(score_icon%s, 180+idx*35, 320)

    for idx, s in enumerate(recinfo['count50']+'x'):
        d.add_items(score_icon%s, 180+idx*35, 380)

    for idx, s in enumerate(recinfo['countgeki']+'x'):
        d.add_items(score_icon%s, 490+idx*35, 260)

    for idx, s in enumerate(recinfo['countkatu']+'x'):
        d.add_items(score_icon%s, 490+idx*35, 320)
        
    for idx, s in enumerate(recinfo['countmiss']+'x'):
        d.add_items(score_icon%s, 490+idx*35, 380)

    # cb
    for idx, s in enumerate(recinfo['maxcombo']+'x'):
        d.add_items(score_icon%s, 60+idx*35, 480)
    # acc
    acc2 = mods.get_acc(recinfo['count300'], recinfo['count100'], recinfo['count50'], recinfo['countmiss'])
    acc_str = str(round(acc2, 2))
    acc_start_x = 360
    for idx, s in enumerate(acc_str):
        if s == '.':
            d.add_items(score_dot_icon, acc_start_x+idx*35, 480)
            acc_start_x -= 10 # 去掉间隙
        else:
            d.add_items(score_icon%s, acc_start_x+idx*35, 480)

        if idx + 1 == len(acc_str):
            d.add_items(score_p_icon, acc_start_x+idx*35+35, 480)

    mds2 = int(recinfo['enabled_mods'])
    mds_l2 = mods.getMod(mds2)
    if 'NONE' in mds_l2:
        mds_l2.remove('NONE')
    rank2 = 'D' if recinfo['rank'] == 'F' else recinfo['rank']

    # d.add_text(1180, 30, f"bid: {bid}", font_size=25, ttype='en')
    d.add_items(ranking_icon, 1000, 10) # 右上角 ranking
    d.add_items(replay_icon, 920, 470, factor=0.8) # replay
    d.add_items(rank_icon%rank2, 920, 100) # 评分
    # mod
    for idx, mod in enumerate(mds_l2[::-1]):
        m_icon = mod_icon_map.get(mod)
        if not m_icon:
            continue
        d.add_items(m_icon, 1190-70*idx, 390)

    # pp区域
    d.draw_rectangle(x=640, y=590, width=460, height=60, fill=(0, 0, 0, 90))
    d.add_items(score_pp_icon, 650, 600)
    for idx, s in enumerate(str(round(kw["pp"]))):
        d.add_items(score_icon%s, 710+idx*30, 600)

    for idx, s in enumerate(str(round(kw["fcpp"]))):
        d.add_items(score_icon%s, 840+idx*30, 600)

    for idx, s in enumerate(str(round(kw["acpp"]))):
        d.add_items(score_icon%s, 980+idx*30, 600)

    if debug:
        d.RecImg.show()
        return ""

    else:
        uid = userjson.get('user_id', '')
        p = 'rctpp-%s.png' % uid
        pfs = 'rctpp-%s-fs8.png' % uid
        f = '/static/interbot/image/tmp/%s' % p
        d.save(f)
        # 压缩
        os.system('pngquant -f %s' % f)
        logging.info('[%s]成绩生成成功!' % pfs)
        return pfs