def mediaseg( src: Union[str, Path], type_: Optional[Literal['image', 'record', 'video']] = None) -> MessageSegment: """生成图片、语音或短视频的MessageSegment Args: src (Union[str, Path]): 路径,会自动转化绝对路径 type_ (Optional[Literal[, optional): 文件类型,如果不指定的话会依据文件后缀来判断是什么类型. Defaults to None. Returns: MessageSegment: 直接可发送的消息段 """ if type_ is None: if isinstance(src, str): src = Path(src) if src.suffix in ('.jpg', '.png', '.jpeg', '.gif'): return MessageSegment.image('file:///' + str(src.resolve())) elif src.suffix in ('.mp3', '.aac', '.amr', '.wav', '.m4a'): return MessageSegment.record('file:///' + str(src.resolve())) elif src.suffix in ('.mp4', '.flv', '.m4v'): return MessageSegment.video('file:///' + str(src.resolve())) else: logger.error(f'Unkwon or unsupported file format with {src}') else: return MessageSegment(type=type_, data={"file": 'file:///' + str(src.resolve())})
def localize(url: str, filename: str, failed_times: int = 0) -> Optional[str]: """本地化图片存储在语料库图片文件夹 Args: url (str): 要下载的url filename (str): 下载后存储的文件名称 failed_times (int, optional): 初始失败次数. Defaults to 0. Returns: Optional[str]: 成功下载会返回下载后的文件储存路径,否则返回None """ searchfile = CORPUS_IMAGES_PATH.glob(filename.split('.')[0] + ".*") for f in searchfile: fp = f logger.debug(f'File [{filename}] has localized with {fp}') return fp.name fp = CORPUS_IMAGES_PATH / filename try: urlretrieve(url, fp) realpath = fp.with_suffix('.' + what(fp)) # 修复文件为真正的后缀 fp.rename(realpath) logger.info(f'Localize image [{filename}] with path: {realpath.name}') return realpath.name except Exception as err: failed_times += 1 logger.warning( f'Download file [{url}] error {failed_times} times: {err}') if failed_times < 6: return localize(url, filename, failed_times=failed_times) else: logger.error( f'Can not download file [{url}] with filename[{fp}]: {err}') return None
def __exit__(self, exc_type, exc_val, exc_tb): if exc_type is None: if self.q is False: self.commit() self.close() else: logger.error( f'EXCType: {exc_type}; EXCValue: {exc_val}; EXCTraceback: {exc_tb}' )
def ai_chat(query): try: params = { "Query": query } req.from_json_string(json.dumps(params)) resp = client.ChatBot(req) return resp.Reply, resp.Confidence except TencentCloudSDKException as err: logger.error(err)
async def save_img(url: str, filepath: Union[str, Path]): ''' 存储网络图片并将filepath文件名自动更正成正确的后缀 ''' async with httpx.AsyncClient() as client: resp = await client.get(url, timeout=600) if resp.status_code != httpx.codes.OK: logger.error(f'Filed to request url: {url}') # print(f'Filed to request url: {url}') return if not isinstance(filepath, Path): filepath = Path(filepath) filepath.write_bytes(resp.content) real_suffix = f".{what(filepath).replace('jpeg', 'jpg')}" if real_suffix != filepath.suffix.lower(): filepath.rename(filepath.with_suffix(real_suffix)) return filepath
def update_prob(sid: Union[int, Tuple[int, int]], prob: int): """更新出现率 Args: sid (Union[int, Tuple[int, int]]): 对话id,若要更新多个则传入最小id和最id组成的数组 prob (int): 更新后的出现率 """ if prob < 0 or prob > 100: logger.error(f'Probability {prob} out of range!') return with QbotDB() as qb: if isinstance(sid, int): qb.update('UPDATE corpus SET probability=%s WHERE id=%s;', (prob, sid)) else: min_, max_ = sid qb.update( f'UPDATE corpus SET probability=%s WHERE id BETWEEN %s AND %s;', (prob, min_, max_))
def run(self, yaml_name=webConfig.LOGIN_SUCCESS, case_name=None): yaml_path = find_file(WebConfig.yaml_path, yaml_name + '.yaml') yaml_data = YamlReader(yaml_path).data self.scenarios = yaml_data[0].get('scenarios') self.setCase(case_name) requests = self.getRequests() if self.browser is None: self.openDefaultBrowser() img = None msg = '【%s】场景开始执行' % self.getCaseName() print(msg) logger.info(msg) for request in requests: msg = 'label:【%s】' % request.get('label') print(msg) logger.info(msg) for action in request.get('actions'): logger.info('action:%s' % action) if action == 'getScreenshot()': time.sleep(1) self.success_img.append( self.browser.driver.get_screenshot_as_base64()) continue try: msg = self.doAction(action) if yaml_name != webConfig.LOGIN_SUCCESS: print(msg) logger.info(msg) except Exception as e: print(str(e)) logger.error(str(e)) img = self.browser.driver.get_screenshot_as_base64() self.imgs.append(img) return False result = True if img is None else False return result
async def send_others(bot: Bot, event: MessageEvent, state: T_State): # msg = MessageSegment.reply(id_=event.message_id) if event.message_type == 'group' else MessageSegment.text('') if state['img_type'] == 'meizi': call = get_nmb(False) elif state['img_type'] == 'photo': call = get_pw(False) elif state['img_type'] == 'bg': if state['lx'] in ('acg', '小姐姐', '风景', '随机'): lx = state['lx'].replace('acg', 'dongman').replace('小姐姐', 'meizi').replace('风景', 'fengjing').replace('随机', 'suiji') elif state['lx'] is not None: msg = MessageSegment.text( f'没有{state["lx"]}类型的壁纸') await rand_img.finish(reply_header(event, msg)) else: lx = 'suiji' if state['pc'] is not None and state['pc'] == '手机': state['pc'] = 'mobile' call = get_sjbz(state['pc'], lx) elif state['img_type'] == 'acg': call = choice((get_asmdh(), get_nmb(True), get_pw(True))) logger.debug(f'调用杂图API: {call.__name__}') try: result = await call except httpx.HTTPError as e: logger.exception(e) msg = MessageSegment.text('图片丢掉了,要不你再试试?') await rand_img.finish(reply_header(event, msg)) if isinstance(result, str): img = MessageSegment.image(result) elif isinstance(result, int): logger.error(f'{call.__name__} 失效,状态码: {result}') await rand_img.finish(reply_header(event, '这个API可能挂掉了,如果一直不好使就只好停用这个功能了')) else: img = imgseg(result) await rand_img.send(reply_header(event, img)) return 'completed'
async def ofl_rmd(bot: Bot): dc_time = datetime.now().time().strftime("%H:%M:%S") logger.critical(f'Bot {bot.self_id} disconnected') ol_bots = [bt for strid, bt in get_bots().items()] if ol_bots: while ol_bots: notifier: Bot = choice(ol_bots) try: for su in SUPERUSERS: await notifier.send_private_msg( user_id=su, message=f' {bot.self_id}disconnected at {dc_time}') break except BaseException as err: logger.error( f'Bot {notifier.self_id} failed to send offline notification: {err}' ) ol_bots.remove(notifier) else: logger.error(f'All bots failed to send notification!') else: logger.critical('There is no bot can send notification!')
APP_ID = dict(cfg.items('key'))['app_id'] APP_KEY = dict(cfg.items('key'))['app_key'] try: cred = credential.Credential(APP_ID, APP_KEY) httpProfile = HttpProfile() httpProfile.endpoint = "nlp.tencentcloudapi.com" clientProfile = ClientProfile() clientProfile.httpProfile = httpProfile client = nlp_client.NlpClient(cred, "ap-guangzhou", clientProfile) req = models.ChatBotRequest() except TencentCloudSDKException as err: logger.error(err) def ai_chat(query): try: params = { "Query": query } req.from_json_string(json.dumps(params)) resp = client.ChatBot(req) return resp.Reply, resp.Confidence except TencentCloudSDKException as err: logger.error(err)
async def send_lolicon(bot: Bot, event: MessageEvent, state: T_State): gid = event.group_id if event.message_type=='group' else 0 if gid: if str(gid) not in sl_settings: await setu.finish('''先设置本群sl再使用此功能吧 [设置sl 最小sl-最大sl] 例如:设置sl 0-4 ──────────── sl说明: 大概可以解释成本群能接收的工口程度,sl越高的图被人看见越会触发社死事件 ※ r18权限已改为sl, 当最大sl为5时即为开启r18权限,sl0-4级别仅适用于美图,色图会自动忽略 最低sl0:不含任何ero要素,纯陶冶情操,也有一部分风景图 最高sl5: 就是R18了 中间的等级依次过渡''') max_sl = sl_settings[str(gid)]['max_sl'] min_sl = sl_settings[str(gid)]['min_sl'] restricted = True if max_sl < 5 else False # r18是否在本群受限 else: restricted = False # 限制条件优先度:r18,5张最大数,等级限制数量,频率,资金,由于要检测参数只好先把个别参数解析混入条款中了 uid = event.user_id # r18限制条款,顺便解析了r18 r18_call = state["_matched_dict"]['r18_call'] or state["_matched_dict"]['r18_call2'] if r18_call and restricted: await setu.finish(reply_header(event, f'当前群内最大sl为{max_sl},已禁止R18内容')) # 5张最大数量限制条款,顺便解析了num if state["_matched_dict"]['num']: num = cn2an(state["_matched_dict"]['num'].replace('两', '二'), 'smart') elif state["_matched_dict"]['num2']: num = cn2an(state["_matched_dict"]['num2'].replace('两', '二'), 'smart') else: num = 1 if num > 5: await setu.finish(reply_header(event, '一次最多只能要5张')) elif num == 0: await setu.finish(reply_header(event, '你好奇怪的要求')) elif num < 0: await setu.finish(reply_header(event, f'好的,你现在欠大家{-num}张涩图,快发吧')) # TODO: 想想办法把负数给提取出来 # 等级限制数量条款,注册了用户信息 userinfo = UserLevel(uid) if userinfo.level < num: if userinfo.level > 0: await setu.finish(f'您当前等级为{userinfo.level},最多一次要{userinfo.level}张') elif num > 1: await setu.finish(reply_header(event, '啊这..0级用户一次只能叫一张哦,使用[签到]或者学习对话可以提升等级~')) # 频率限制条款,注册了频率限制器 flmt = FreqLimiter(uid, 'setu') if not flmt.check(): refuse = f'你冲得太快了,请{ceil(flmt.left_time())}秒后再冲' # 不用round主要是防止出现'还有0秒'的不科学情况 if userinfo.level == 0: refuse += ',提升等级可以加快装填哦~' await setu.finish(reply_header(event, refuse)) cd = cd_step(userinfo.level, 480) # 冷却时间 flmt.start_cd(cd) # 先进行冷却,防止连续呼叫击穿频率装甲,要是没返回图的话解掉 # 资金限制条款,注册了每日次数限制器 cost = num * 3 dlmt = DailyNumberLimiter(uid, '色图', 3) in_free = True if event.message_type == 'private' and event.sub_type == 'friend'\ else dlmt.check(close_conn=False) # 来自好友的对话不消耗金币 if userinfo.fund < cost and not in_free: if userinfo.fund > 0: refuse = choice((f'你还剩{userinfo.fund}块钱啦,要饭也不至于这么穷吧!', f'你只剩{userinfo.fund}块钱了,要不考虑援交一下赚点钱?')) elif userinfo.level == 0 and userinfo.fund == 0: refuse = '每天有三次免费次数哦,使用[签到]领取资金来获得更多使用次数吧~' else: refuse = '你已经穷得裤子都穿不起了,到底是做了什么呀?!' dlmt.conn.close() # 确认直接结束不会增加调用次数了,直接返还链接 flmt.start_cd(0) await setu.finish(reply_header(event, refuse)) kwd = state["_matched_dict"]['kwd'] or '' if r18_call: r18 = 1 if r18_call in ('r18', 'R18') else 0 else: if event.message_type == 'group': if max_sl < 5: r18 = 0 elif min_sl == 5: r18 = 1 elif min_sl < 5: r18 = 2 else: r18 = 2 msg = MessageSegment.reply(id_=event.message_id) if event.message_type == 'group' else MessageSegment.text('') # 由于当前私聊回复有bug所以只在群里设置信息开始为回复消息 # 有搜索条件,从本地库中提 if kwd: kwds = tuple(kwdrex.split(kwd)) success, result = get_setu(gid, kwds, num, r18) if not success: flmt.start_cd(0) dlmt.conn.close() await setu.finish(reply_header(event, result)) count = result['count'] # 返回数量,每次处理过后自减1 miss_count = 0 # 丢失数量 for data in result['data']: if data is None: miss_count += 1 continue img : Path = data['file'] logger.debug(f'当前处理本地图片{img.name}') info = f"{data['title']}\n画师:{data['author']}\nPID:{data['source']}\n" try: im_b64 = Image_Handler(img).save2b64() except OSError as err: miss_count += 1 logger.error(f'File {img} may be damaged: {err}') continue except UnidentifiedImageError as imgerr: miss_count += 1 logger.error(f'failed to open local file: {img}: {imgerr}') continue msg += MessageSegment.text(info) + MessageSegment.image(im_b64) if count > 1: msg += MessageSegment.text('\n=====================\n') count -= 1 elif result['count'] < num: msg += MessageSegment.text(f'\n=====================\n没搜到{num}张,只搜到这些了') # 无搜索条件,链接API,5次错误退出并反馈错误 else: logger.debug('Start getting lolicon API') failed_time = 0 while failed_time < 5: try: result = await get_lolicon(kwd, r18, num) break except BaseException as e: failed_time += 1 logger.exception(f"connect api faild {failed_time} time(s)\n{e}") else: logger.error(f'多次链接API失败,当前参数: kwd: [{kwd}], num: {num}, r18: {r18}') dlmt.conn.close() flmt.start_cd(0) await setu.finish('链接API失败, 若多次失败请反馈给维护组', at_sender=True) logger.debug('Receive lolicon API data!') # 处理数据 if result['code'] == 0: count = result['count'] # 返回数量,每次处理过后自减1 untreated_ls = [] # 未处理数据列表,遇到本地库中没有的数据要加入这个列表做并发下载 miss_count = 0 # 丢失数量 for data in result['data']: pid = data['pid'] p = data['p'] name = f'{pid}_p{p}' # 按 色图备份路径->美图原文件路径 顺序查找本地图,遇到没有本地路径的等待并发下载处理 imgbkup = [f for f in Path(SETUPATH).glob(f'{name}.[jp][pn]*g')] if imgbkup: img = imgbkup[0] else: imgorg = [f for f in (Path(MEITUPATH)/'origin_info').rglob(f'{name}.[jp][pn]*g')] if imgorg: img = imgorg[0] else: untreated_ls.append(data) continue logger.debug(f'当前处理本地图片{name}') info = f"{data['title']}\n画师:{data['author']}\nPID:{name}\n" try: im_b64 = Image_Handler(img).save2b64() except UnidentifiedImageError as imgerr: miss_count += 1 logger.error(f'failed to open local file: {img}: {imgerr}') continue msg += MessageSegment.text(info) + MessageSegment.image(im_b64) if count > 1: msg += MessageSegment.text('\n=====================\n') count -= 1 elif result['count'] < num: msg += MessageSegment.text(f'\n=====================\n没搜到{num}张,只搜到这些了') # 对未处理过的数据进行并发下载,只下载1200做临时使用 async with httpx.AsyncClient() as client: task_ls = [] for imgurl in [d['url'] for d in untreated_ls]: task_ls.append(client.get(get_1200(imgurl), timeout=120)) imgs = await gather(*task_ls, return_exceptions=True) for i, data in enumerate(untreated_ls): if isinstance(imgs[i], BaseException): miss_count += 1 logger.exception(data) continue if imgs[i].status_code != httpx.codes.OK: miss_count += 1 logger.error(f'Got unsuccessful status_code [{imgs[i].status_code}] when visit url: {imgs[i].url}') continue pid = data['pid'] p = data['p'] name = f'{pid}_p{p}' info = f"{data['title']}\n画师:{data['author']}\nPID:{name}\n" logger.debug(f'当前处理网络图片{name}') try: im_b64 = Image_Handler(imgs[i].content).save2b64() except BaseException as err: logger.error(f"Error with handle {name}, url: [{data['url']}]\n{err}") miss_count += 1 continue msg += MessageSegment.text(info) + MessageSegment.image(im_b64) if count > 1: msg += MessageSegment.text('\n=====================\n') count -= 1 elif result['count'] < num: msg += MessageSegment.text(f'\n=====================\n没搜到{num}张,只搜到这些了') if miss_count > 0 and num > 1: msg += MessageSegment.text(f'\n有{miss_count}张图丢掉了,{BOTNAME}也不知道丢到哪里去了T_T') elif miss_count == 1: msg += MessageSegment.text(f'{BOTNAME}拿来了图片但是弄丢了呜呜T_T') else: flmt.start_cd(0) dlmt.conn.close() await setu.finish(msg + MessageSegment.text('获取涩图失败,请稍后再试')) try: await setu.send(msg) except NetworkError as err: logger.error(f'Maybe callout error happend: {err}') except AdapterException as err: logger.error(f"Some Unkown error: {err}") if miss_count < result['count']: if not in_free: cost = (result['count'] - miss_count) * 3 # 返回数量可能少于调用量,并且要减去miss的数量 userinfo.turnover(-cost) # 如果超过每天三次的免费次数则扣除相应资金 dlmt.increase() # 调用量加一 else: flmt.start_cd(0) # 一张没得到也刷新CD dlmt.conn.close() # 下载原始图片做本地备份 if not kwd: async with httpx.AsyncClient() as bakeuper: backup_ls = [] json_ls = [] for info in untreated_ls: url = info['url'] json_data = { 'pid': info['pid'], 'p': info['p'], 'uid': info['uid'], 'title': info['title'], 'author': info['author'], 'url': url, 'r18': info['r18'], 'tags': info['tags'] } backup_ls.append(bakeuper.get(url, timeout=500)) json_ls.append(json_data) origims = await gather(*backup_ls, return_exceptions=True) for i, im in enumerate(origims): if isinstance(im, BaseException): logger.exception(im) continue if im.status_code != httpx.codes.OK: logger.error(f'Got unsuccessful status_code [{im.status_code}] when visit url: {im.url}') continue imgfp = Path(SETUPATH)/(str(json_ls[i]['pid']) + '_p' + str(json_ls[i]['p']) + '.' + json_ls[i]['url'].split('.')[-1]) jsonfp = Path(SETUPATH)/(str(json_ls[i]['pid']) + '_p' + str(json_ls[i]['p']) + '.json') try: with imgfp.open('wb') as f: f.write(im.content) logger.info(f'Downloaded image {imgfp.absolute()}') except BaseException as e: logger.exception(e) with jsonfp.open('w', encoding='utf-8') as j: json.dump(json_ls[i], j, ensure_ascii=False, escape_forward_slashes=False, indent=4) logger.info(f'Generated json {jsonfp.absolute()}') increase_setu(**json_ls[i])
def get_mitu(gid: int, kwd: tuple = (), num: int = 1, min_sl: int = 0, max_sl: int = 1): """从数据库中获得美图数据 Args: gid (int): 呼叫美图的群号,相同群内会有两个小时的记录去重 kwd (tuple, optional): 关键词,元组内的词会做交集查询. Defaults to (). num (int, optional): 查询数量. Defaults to 1. min_sl (int, optional): 最小的sl值. Defaults to 0. max_sl (int, optional): 最大的sl值. Defaults to 1. Returns: tuple: 返回是否调用成功以及返回的结果,如果没有查询结果会返回无结果还是结果已经用尽,如果有结果会返回数据列表 数据结构:{ 'title' (str): 作品名, 'author' (str): 作者, 'source' (str): 来源, Pixiv pid_p 'sl' (int): sl级别, 'file' (int): 文件路径 } """ if gid not in Called_Data: Called_Data[gid] = Group_Called(gid) his = Called_Data[gid] kwds = "%" + '%'.join(kwd) + "%" # like查询要在两边加上%占位符 kwds_demojize = demojize(kwds) # 查询数据库要用转义为普通字符的emoji with GalleryDB() as glrdb: # TODO: 优化随机查询的方式取代orded by rand() if len(his.called) > 1: cmd = f'''select title, author, source, sl, visits, id from gallery where sl between %s and %s and id not in {tuple(his.called)} and (tags like %s or title like %s or author like %s) ORDER BY RAND() LIMIT %s;''' else: cmd = '''select title, author, source, sl, visits, id from gallery where sl between %s and %s and (tags like %s or title like %s or author like %s) ORDER BY RAND() LIMIT %s;''' params = (min_sl, max_sl, kwds_demojize, kwds_demojize, kwds_demojize, num) results = glrdb.queryall(cmd, params) # 随机查询不在called列表中的图片 if not results: if kwds in his.called_kwds: return False, f'暂时没有更多关于"{kwds.replace("%", "|")[1:-1]}"的美图了' else: return False, f'没有找到关于"{kwds.replace("%", "|")[1:-1]}"的美图' # TODO: 换了标签之后再搜索到相同的图用这个标签不太合适,换成"有x张相关图刚才已经发过了" mitu_ls = [] # 所有美图列表 for record in results: if record[2].startswith( 'Pixiv'): # 暂时只有pixiv图片,加入非p站图片之后要增加拼装文件名方法 filestem = record[2][6:] # 名字是去掉Pixiv 前缀剩下的pid_p searchfile = glob.glob( str(Path(MEITUPATH) / ('sl' + str(record[3])) / (filestem)) + '.[jp][pn]*g') # 从本地图库中搜索图片,暂时只搜索jpg, png, jpeg,正常情况应该只能搜到一个 if not searchfile: logger.error(f'Not found file {filestem}') mitu_ls.append(None) continue elif len(searchfile) > 1: logger.warning(f'查找到多个名称为{filestem}的图片,请检查图库') logger.debug(f'Found {len(searchfile)} file(s):' + '\n'.join(searchfile)) data = { 'title': emojize(record[0]), 'author': emojize(record[1]), 'source': record[2], 'sl': record[4], 'file': searchfile[0] } mitu_ls.append(data) his.called.add(record[5]) # 添加id记录 his.called_kwds.add(kwds) # 添加关键词记录 his.check_expired() # 检查记录是否过期 if len(results) == 1: increase_cmd = "UPDATE gallery SET visits=visits+1 WHERE id=%s;" increase_parm = (results[0][5], ) else: id_ls = str(tuple([r[5] for r in results])) # 如果有多条记录就传入元组同时加1 increase_cmd = f"UPDATE gallery SET visits=visits+1 WHERE id in {id_ls};" increase_parm = () glrdb.update(increase_cmd, increase_parm) # 调用次数加一 return True, mitu_ls