From 1ede13c4d66e01ca042af8e8893acdd4294a6c3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=91=E5=B1=B1?= <2596473306@qq.com> Date: Fri, 31 Mar 2023 13:34:21 +0800 Subject: [PATCH] =?UTF-8?q?NGCBot=2018=E8=AF=9E=E8=BE=B0=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Api_Server/Api_Server_Main.py | 499 +++++++++++++++ BotServer/MainServer.py | 253 ++++++++ BotServer/SendServer.py | 144 +++++ Cache/Cache_Server.py | 51 ++ Config/config.yaml | 198 ++++++ Db_Server/Db_Point_Server.py | 172 ++++++ Db_Server/Db_User_Server.py | 222 +++++++ Output/output.py | 17 + Push_Server/Push_Main_Server.py | 96 +++ README.MD | 608 +++++++++++++++++++ README.assets/image-20221212162417977.png | Bin 0 -> 135011 bytes README.assets/image-20230305191526567.png | Bin 0 -> 30091 bytes README.assets/image-20230305191835360.png | Bin 0 -> 3648 bytes README.assets/image-20230305191853177.png | Bin 0 -> 18894 bytes README.assets/image-20230305192022516.png | Bin 0 -> 71662 bytes README.assets/image-20230305192150988.png | Bin 0 -> 24302 bytes README.assets/image-20230305194540725.png | Bin 0 -> 27585 bytes README.assets/image-20230305194645867.png | Bin 0 -> 31171 bytes README.assets/image-20230305194812168.png | Bin 0 -> 53381 bytes README.assets/image-20230305195314814.png | Bin 0 -> 48852 bytes README.assets/image-20230305195409045.png | Bin 0 -> 26181 bytes README.assets/image-20230305195433874.png | Bin 0 -> 26216 bytes README.assets/image-20230305195530398.png | Bin 0 -> 30581 bytes README.assets/image-20230305195628326.png | Bin 0 -> 25351 bytes README.assets/image-20230305195801227.png | Bin 0 -> 43487 bytes README.assets/image-20230305195945397.png | Bin 0 -> 76130 bytes README.assets/image-20230305204137643.png | Bin 0 -> 30302 bytes README.assets/image-20230305204308526.png | Bin 0 -> 17152 bytes README.assets/image-20230305204511873.png | Bin 0 -> 45696 bytes README.assets/image-20230305204619914.png | Bin 0 -> 18534 bytes README.assets/image-20230305205946424.png | Bin 0 -> 31491 bytes README.assets/image-20230305214235159.png | Bin 0 -> 21227 bytes README.assets/image-20230305214357922.png | Bin 0 -> 8263 bytes README.assets/image-20230305214449681.png | Bin 0 -> 18097 bytes README.assets/image-20230305220141174.png | Bin 0 -> 19710 bytes README.assets/image-20230305222312844.png | Bin 0 -> 21227 bytes README.assets/image-20230305222435542.png | Bin 0 -> 21596 bytes README.assets/image-20230305222721764.png | Bin 0 -> 24382 bytes README.assets/image-20230305223211110.png | Bin 0 -> 22839 bytes README.assets/image-20230305223648781.png | Bin 0 -> 20647 bytes README.assets/image-20230305223804540.png | Bin 0 -> 17773 bytes README.assets/image-20230305223951890.png | Bin 0 -> 17163 bytes README.assets/image-20230305224247670.png | Bin 0 -> 16263 bytes README.assets/image-20230305224907222.png | Bin 0 -> 23342 bytes README.assets/image-20230305225059253.png | Bin 0 -> 38858 bytes README.assets/image-20230305225932950.png | Bin 0 -> 46121 bytes README.assets/image-20230305230219618.png | Bin 0 -> 47242 bytes README.assets/image-20230305230345964.png | Bin 0 -> 39509 bytes README.assets/image-20230305230427577.png | Bin 0 -> 39457 bytes README.assets/image-20230305230611246.png | Bin 0 -> 89595 bytes README.assets/image-20230305230716625.png | Bin 0 -> 21514 bytes README.assets/image-20230305231120081.png | Bin 0 -> 26038 bytes README.assets/image-20230305231212883.png | Bin 0 -> 70340 bytes README.assets/image-20230305231323334.png | Bin 0 -> 13506 bytes README.assets/image-20230305231636005.png | Bin 0 -> 8785 bytes README.assets/image-20230305231701746.png | Bin 0 -> 11925 bytes README.assets/image-20230305231820468.png | Bin 0 -> 57940 bytes README.assets/image-20230305231930846.png | Bin 0 -> 80187 bytes README.assets/image-20230306085542234.png | Bin 0 -> 62460 bytes README.assets/image-20230306090537764.png | Bin 0 -> 9118 bytes README.assets/image-20230306090547336.png | Bin 0 -> 14556 bytes README.assets/image-20230306091138533.png | Bin 0 -> 66821 bytes README.assets/image-20230306091344261.png | Bin 0 -> 73653 bytes README.assets/image-20230306091443104.png | Bin 0 -> 103923 bytes README.assets/image-20230306091506855.png | Bin 0 -> 133414 bytes README.assets/image-20230306091542863.png | Bin 0 -> 35539 bytes README.assets/image-20230306091621220.png | Bin 0 -> 23879 bytes README.assets/image-20230306092504776.png | Bin 0 -> 58719 bytes README.assets/image-20230306092645321.png | Bin 0 -> 6423 bytes README.assets/image-20230306093225214.png | Bin 0 -> 123056 bytes README.assets/image-20230306101314509.png | Bin 0 -> 21276 bytes README.assets/image-20230306101510700.png | Bin 0 -> 28604 bytes README.assets/image-20230306101556676.png | Bin 0 -> 31272 bytes README.assets/image-20230306101820442.png | Bin 0 -> 15565 bytes README.assets/image-20230306101933687.png | Bin 0 -> 29506 bytes README.assets/image-20230329095008188.png | Bin 0 -> 68541 bytes README.assets/image-20230329095026331.png | Bin 0 -> 22588 bytes "README.assets/\345\205\263\346\263\250.gif" | Bin 0 -> 320713 bytes Recv_Msg_Dispose/FriendMsg_dispose.py | 90 +++ Recv_Msg_Dispose/RoomMsg_dispose.py | 275 +++++++++ Recv_Msg_Dispose/Thread_function.py | 393 ++++++++++++ main.py | 15 + requirements.txt | 10 + test.py | 42 ++ 84 files changed, 3085 insertions(+) create mode 100644 Api_Server/Api_Server_Main.py create mode 100644 BotServer/MainServer.py create mode 100644 BotServer/SendServer.py create mode 100644 Cache/Cache_Server.py create mode 100644 Config/config.yaml create mode 100644 Db_Server/Db_Point_Server.py create mode 100644 Db_Server/Db_User_Server.py create mode 100644 Output/output.py create mode 100644 Push_Server/Push_Main_Server.py create mode 100644 README.MD create mode 100644 README.assets/image-20221212162417977.png create mode 100644 README.assets/image-20230305191526567.png create mode 100644 README.assets/image-20230305191835360.png create mode 100644 README.assets/image-20230305191853177.png create mode 100644 README.assets/image-20230305192022516.png create mode 100644 README.assets/image-20230305192150988.png create mode 100644 README.assets/image-20230305194540725.png create mode 100644 README.assets/image-20230305194645867.png create mode 100644 README.assets/image-20230305194812168.png create mode 100644 README.assets/image-20230305195314814.png create mode 100644 README.assets/image-20230305195409045.png create mode 100644 README.assets/image-20230305195433874.png create mode 100644 README.assets/image-20230305195530398.png create mode 100644 README.assets/image-20230305195628326.png create mode 100644 README.assets/image-20230305195801227.png create mode 100644 README.assets/image-20230305195945397.png create mode 100644 README.assets/image-20230305204137643.png create mode 100644 README.assets/image-20230305204308526.png create mode 100644 README.assets/image-20230305204511873.png create mode 100644 README.assets/image-20230305204619914.png create mode 100644 README.assets/image-20230305205946424.png create mode 100644 README.assets/image-20230305214235159.png create mode 100644 README.assets/image-20230305214357922.png create mode 100644 README.assets/image-20230305214449681.png create mode 100644 README.assets/image-20230305220141174.png create mode 100644 README.assets/image-20230305222312844.png create mode 100644 README.assets/image-20230305222435542.png create mode 100644 README.assets/image-20230305222721764.png create mode 100644 README.assets/image-20230305223211110.png create mode 100644 README.assets/image-20230305223648781.png create mode 100644 README.assets/image-20230305223804540.png create mode 100644 README.assets/image-20230305223951890.png create mode 100644 README.assets/image-20230305224247670.png create mode 100644 README.assets/image-20230305224907222.png create mode 100644 README.assets/image-20230305225059253.png create mode 100644 README.assets/image-20230305225932950.png create mode 100644 README.assets/image-20230305230219618.png create mode 100644 README.assets/image-20230305230345964.png create mode 100644 README.assets/image-20230305230427577.png create mode 100644 README.assets/image-20230305230611246.png create mode 100644 README.assets/image-20230305230716625.png create mode 100644 README.assets/image-20230305231120081.png create mode 100644 README.assets/image-20230305231212883.png create mode 100644 README.assets/image-20230305231323334.png create mode 100644 README.assets/image-20230305231636005.png create mode 100644 README.assets/image-20230305231701746.png create mode 100644 README.assets/image-20230305231820468.png create mode 100644 README.assets/image-20230305231930846.png create mode 100644 README.assets/image-20230306085542234.png create mode 100644 README.assets/image-20230306090537764.png create mode 100644 README.assets/image-20230306090547336.png create mode 100644 README.assets/image-20230306091138533.png create mode 100644 README.assets/image-20230306091344261.png create mode 100644 README.assets/image-20230306091443104.png create mode 100644 README.assets/image-20230306091506855.png create mode 100644 README.assets/image-20230306091542863.png create mode 100644 README.assets/image-20230306091621220.png create mode 100644 README.assets/image-20230306092504776.png create mode 100644 README.assets/image-20230306092645321.png create mode 100644 README.assets/image-20230306093225214.png create mode 100644 README.assets/image-20230306101314509.png create mode 100644 README.assets/image-20230306101510700.png create mode 100644 README.assets/image-20230306101556676.png create mode 100644 README.assets/image-20230306101820442.png create mode 100644 README.assets/image-20230306101933687.png create mode 100644 README.assets/image-20230329095008188.png create mode 100644 README.assets/image-20230329095026331.png create mode 100644 "README.assets/\345\205\263\346\263\250.gif" create mode 100644 Recv_Msg_Dispose/FriendMsg_dispose.py create mode 100644 Recv_Msg_Dispose/RoomMsg_dispose.py create mode 100644 Recv_Msg_Dispose/Thread_function.py create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 test.py diff --git a/Api_Server/Api_Server_Main.py b/Api_Server/Api_Server_Main.py new file mode 100644 index 0000000..8ef98b9 --- /dev/null +++ b/Api_Server/Api_Server_Main.py @@ -0,0 +1,499 @@ +# -*- encoding=UTF-8 -*- +import datetime +from lxml import etree +from urllib.parse import urljoin +from Output.output import output +import feedparser +import requests +import urllib3 +import random +import openai +import time +import yaml +import os +import re + + +class Api_Server_Main: + def __init__(self): + + # 全局header头 + self.headers = { + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36", + "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9", + "Accept-Language": "zh-CN,zh;q=0.9", + "Accept-Encoding": "gzip, deflate, br", + # 'Connection':'keep-alive' ,#默认时链接一次,多次爬取之后不能产生新的链接就会产生报错Max retries exceeded with url + "Upgrade-Insecure-Requests": "1", + "Pragma": "no-cache", + "Cache-Control": "no-cache", + "Connection": "close", # 解决Max retries exceeded with url报错 + } + # 忽略HTTPS告警 + urllib3.disable_warnings() + # 获取当前文件路径 + current_path = os.path.dirname(__file__) + + # 配置缓存文件夹路径 + current_list_path = current_path.split('\\') + current_list_path.pop() + self.Cache_path = '/'.join(current_list_path) + '/Cache' + # 初始化读取配置文件 + config = yaml.load(open(current_path + '/../Config/config.yaml', encoding='UTF-8'), yaml.Loader) + self.system_copyright = config['System_Config']['System_Copyright'] + + # 读取openai的key + openai.api_key = config['Api_Server']['Api_Config']['OpenAi_Key'] + + # 设置初始上下文消息队列 + self.message = [{"role": "system", "content": "你现在叫NGCBot,你的主人是云山,请牢记"},] + + self.http_proxy = config['System_Config']['HTTP_PROXY'] + self.https_proxy = config['System_Config']['HTTPS_PROXY'] + + # 读取配置文件 + config = yaml.load(open(current_path + '/../Config/config.yaml', encoding='UTF-8'), yaml.Loader) + self.appid = config['Api_Server']['Api_Config']['Appid'] + self.appsecret = config['Api_Server']['Api_Config']['Appsecret'] + self.key = config['Api_Server']['Api_Config']['Key'] + self.threatbook_key = config['Api_Server']['Api_Config']['ThreatBook_Key'] + + self.pic_apis = config['Api_Server']['Pic_Api'] + self.video_apis = config['Api_Server']['Video_Api'] + self.icp_api = config['Api_Server']['Icp_Api'] + self.extensions_api = config['Api_Server']['Extensions_Api'] + self.attribution_api = config['Api_Server']['Attribution_Api'] + self.whois_api = config['Api_Server']['Whois_Api'] + self.fish_api = config['Api_Server']['Fish_Api'] + self.wether_api = config['Api_Server']['Wether_Api'] + self.dog_api = config['Api_Server']['Dog_Api'] + self.constellation_api = config['Api_Server']['Constellation_Api'] + self.morning_api = config['Api_Server']['Morning_Api'] + self.threatbook_url = config['Api_Server']['ThreatBook_Api'] + + # AI对话接口 + def get_ai(self, keyword): + output('[-]:正在调用AI对话API接口... ...') + os.environ["HTTP_PROXY"] = self.http_proxy + os.environ["HTTPS_PROXY"] = self.https_proxy + self.message.append({"role": "user", "content": f'{keyword}'}) + rsp = openai.ChatCompletion.create( + model="gpt-3.5-turbo", + messages=self.message, + ) + os.environ["HTTP_PROXY"] = "" + os.environ["HTTPS_PROXY"] = "" + msg = rsp.get("choices")[0]["message"]["content"] + self.message.append({"role": "assistant", "content": f'{msg}'}) + return msg + + # 美女图片接口 + def get_pic(self): + output('[-]:正在调用美女图片API接口... ...') + url = random.choice(self.pic_apis) + try: + pic_data = requests.get(url=url, headers=self.headers, timeout=30).content + save_path = self.Cache_path + '/Pic_Cache/' + str(int(time.time() * 1000)) + '.jpg' + with open(file=save_path, mode='wb') as pd: + pd.write(pic_data) + except Exception as e: + msg = f'[ERROR]:美女图片API接口出现错误,错误信息:{e}' + output(msg) + return msg + return save_path + + # 美女视频接口 + def get_video(self): + output('[-]:正在调用美女视频API接口... ...') + url = random.choice(self.video_apis) + # url = 'https://zj.v.api.aa1.cn/api/video_dyv2/' + try: + try: + resp = requests.get(url=url, headers=self.headers, timeout=80).json() + if 'url' in resp: + src = resp['url'] + else: + src = resp['mp4'] + except requests.exceptions.JSONDecodeError: + src = re.findall('src="(.*?)"', requests.get(url=url, headers=self.headers, timeout=20).text)[0] + # print(src) + mp4_url = src + if 'http' not in src: + mp4_url = 'http:' + src + video_data = requests.get(url=mp4_url, headers=self.headers, timeout=60).content + save_path = self.Cache_path + '/Video_Cache/' + str(int(time.time() * 1000)) + '.mp4' + with open(file=save_path, mode='wb') as vd: + vd.write(video_data) + except Exception as e: + msg = f'[ERROR]:美女视频API接口出现错误,错误信息:{e}' + output(msg) + return msg + return save_path + + # 备案查询接口 + def get_icp(self, keyword): + try: + domain = re.findall(r' (\w+.\w+)', keyword)[0] + except Exception as e: + msg = '语法格式:\nICP查询 qq.com' + output(f'[ERROR]:备案查询接口出现错误,错误信息:{e}') + return msg + url = self.icp_api.format(domain) + try: + data = requests.get(url=url, headers=self.headers, timeout=10).json() + except Exception as e: + msg = f'[ERROR]:备案查询接口超时,错误信息:{e}' + output(msg) + return msg + if data['icp'] == '未备案': + return '该域名未备案!' + msg = f'======== 查询信息 ========\nICP备案号:{data["icp"]}\n备案主体:{data["name"]}\n备案类型:{data["tyle"]}\n{"By: #" + self.system_copyright if self.system_copyright else ""}\n========================' + return msg.strip() + + # 后缀名查询接口 + def get_suffix(self, keyword): + try: + word = re.findall(r' (\w+)', keyword)[0] + except Exception as e: + msg = '语法格式:\n后缀名查询 EXE' + output(f'\n[ERROR]:后缀名查询接口出现错误,错误信息:{e}') + return msg + url = self.extensions_api.format(self.key, word) + try: + data = requests.get(url=url, headers=self.headers).json() + except TimeoutError as e: + msg = f'\n[ERROR]:后缀名查询接口超时,错误信息:{e}' + output(msg) + return msg + if data['code'] != 200: + msg = '查询结果为空!' + else: + msg = f'\n======== 查询后缀:{word} ========\n查询结果:{data["result"]["notes"]}\n{"By: #" + self.system_copyright if self.system_copyright else ""}\n============================' + return msg + + # 归属地查询 + def get_attribution(self, keyword): + try: + phone = re.findall(r' (\d+)', keyword)[0] + except Exception as e: + msg = '语法格式:\n归属查询 110' + output(f'\n[ERROR]:归属查询接口出现错误,错误信息:{e}') + return msg + url = self.attribution_api.format(phone) + try: + data = requests.get(url=url, headers=self.headers).json() + except TimeoutError as e: + msg = f'\n[ERROR]:归属查询接口超时,错误信息:{e}' + output(msg) + return msg + if not data['data']['province']: + msg = '查询结果为空!' + else: + msg = f'\n===== 查询信息 =====\n手机号码:{phone}\n省份:{data["data"]["province"]}\n城市:{data["data"]["city"]}\n运营商:{data["data"]["sp"]}\n{"By: #" + self.system_copyright if self.system_copyright else ""}\n=================' + return msg + + # Whois查询接口 + def get_whois(self, keyword): + try: + domain = re.findall(r' (\w+.\w+)', keyword)[0] + except Exception as e: + msg = '语法格式:\nWHOIS查询 qq.com' + output(f'[ERROR]:WHOIS查询接口出现错误,错误信息:{e}') + return msg + url = self.whois_api.format(domain) + try: + source_data = requests.get(url=url, headers=self.headers).text + except TimeoutError as e: + msg = f'\n[ERROR]:WHOIS查询接口超时,错误信息:{e}' + output(msg) + return msg + msg = '\n' + source_data.strip().split('For more information')[0].strip( + '
').strip() + f"\n{'By: #' + self.system_copyright if self.system_copyright else ''}" + return msg + + # 微步ip查询接口 + def get_threatbook_ip(self, keyword): + try: + keyword = keyword.split(' ')[-1] + except Exception as e: + output(f'[ERROR]:微步ip查询接口出现错误,错误信息:{e}') + reg = r"((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}" + ip_result = re.match(reg, keyword.replace(' ', '').strip()) + if ip_result is None: + msg = "语法格式: \nIP查询 xx.xx.xx.xx" + return msg + elif len(keyword) > 0 and ip_result.group(): + search_ip = ip_result.group() + ips = str(search_ip).split('.') + continuous_bool = True if [i for i in ips if ips[0] != i] else False + if ips[0] in ['127', '192', '0', '224', '240', '255'] or \ + search_ip in ['1.1.1.1', '2.2.2.2', '3.3.3.3', '4.4.4.4', '5.5.5.5', '6.6.6.6', '7.7.7.7', + '8.8.8.8', '9.9.9.9', '10.10.10.10'] or \ + '.'.join(ips[0:2]) in ['169.254', '100.64', '198.51', '198.18', '172.16'] or \ + '.'.join(ips[0:3]) in ['203.0.113'] or \ + ips[-1] in ['255', '254']: + msg = "[微笑]暂不支持查询该地址!" + return msg + if not continuous_bool: + msg = "[微笑]暂不支持查询该地址!" + return msg + try: + + data = { + "apikey": self.threatbook_key, + "resource": search_ip, + } + + resp = requests.post( + self.threatbook_url, + data=data, + timeout=10, + verify=False, + ) + if resp.status_code == 200 and resp.json()["response_code"] == 0: + # 查风险等级 + sec_level = resp.json()["data"]["{}".format(search_ip)]["severity"] + # 查是否恶意IP + is_malicious = resp.json()["data"]["{}".format(search_ip)]["is_malicious"] + # 查可信度 + confidence_level = resp.json()["data"]["{}".format(search_ip)]["confidence_level"] + # 查IP归属国家 + country = resp.json()["data"]["{}".format(search_ip)]["basic"]["location"][ + "country" + ] + # 查IP归属省份 + province = resp.json()["data"]["{}".format(search_ip)]["basic"]["location"][ + "province" + ] + # 查IP归属城市 + city = resp.json()["data"]["{}".format(search_ip)]["basic"]["location"]["city"] + # 将IP归属的国家、省份、城市合并成一个字符串 + location = country + "-" + province + "-" + city + # 查威胁类型 + judgments = "" + for j in resp.json()["data"]["{}".format(search_ip)]["judgments"]: + judgments += j + " " + if is_malicious: + is_malicious_msg = "是" + else: + is_malicious_msg = "否" + msg = f"\n===================\n[+]ip:{search_ip}\n[+]风险等级:{sec_level}\n[+]是否为恶意ip:{is_malicious_msg}\n[+]可信度:{confidence_level}\n[+]威胁类型:{str(judgments)}\n[+]ip归属地:{location}\n更新时间:{resp.json()['data']['{}'.format(search_ip)]['update_time']}\n{'By: #' + self.system_copyright if self.system_copyright else ''}\n===================" + else: + msg = f"[ERROR]:查询失败,返回信息:{resp.json()['verbose_msg']}" + output(msg) + except Exception as e: + output(f"[ERROR]:微步IP查询出错,错误信息:{e}") + msg = f"[ERROR]:查询出错请稍后重试,错误信息:{e}" + return msg + + # 摸鱼日记接口 + def get_fish(self): + output('[-]:正在调用摸鱼日记API接口... ...') + try: + pic_data = requests.get(url=self.fish_api, headers=self.headers, timeout=10).content + save_path = self.Cache_path + '/Fish_Cache/' + str(int(time.time() * 1000)) + '.jpg' + with open(file=save_path, mode='wb') as pd: + pd.write(pic_data) + except Exception as e: + msg = f'[ERROR]:摸鱼日记API接口出现错误,错误信息:{e}' + output(msg) + return msg + return save_path + + # 天气查询接口 + def get_wether(self, keyword): + try: + city = re.findall(r' (\w+)', keyword)[0] + except Exception as e: + msg = '语法格式:\n天气查询 北京' + output(f'\n[ERROR]:天气查询接口出现错误,错误信息:{e}') + return msg + url = self.wether_api.format(self.appid, self.appsecret, city) + try: + data = requests.get(url=url, headers=self.headers).json() + except TimeoutError as e: + msg = f'\n[ERROR]:天气查询接口超时,错误信息:{e}' + output(msg) + return msg + try: + if city != data['city']: + msg = f'城市中不存在:{data["city"]}' + return msg + else: + msg = f'\n今日{data["city"]}天气:{data["wea"]}\n日期:{data["date"]}\n当前温度:{data["tem"]}\n最低温度:{data["tem_day"]}\n风向:{data["win"] + data["win_speed"]}\n风速:{data["win_meter"]}\n湿度:{data["humidity"]}\n{"By: #" + self.system_copyright if self.system_copyright else ""}' + return msg + except Exception as e: + output(f'[ERROR]:天气查询接口出现错误出现错误,错误信息:{e}') + msg = f'城市中不存在:{city}' + return msg + + # 舔狗日记 + def get_dog(self): + url = self.dog_api.format(self.key) + try: + data = requests.get(url=url, headers=self.headers).json() + except TimeoutError as e: + msg = f'\n[ERROR]:舔狗日记接口超时,错误信息:{e}' + output(msg) + return msg + try: + msg = data['newslist'][0]['content'].strip() + except Exception as e: + msg = f'[ERROR]:舔狗日记接口出现错误出现错误,错误信息:{e}' + output(msg) + return msg + + # 星座查询接口 + def get_constellation(self, keyword): + msg = '' + try: + constellation = re.findall(r' (\w+)', keyword)[0] + if '座' not in constellation: + constellation += '座' + except Exception as e: + msg = '语法格式:\n星座查询 白羊座' + output(f'\n[ERROR]:星座查询接口出现错误,错误信息:{e}') + return msg + url = self.constellation_api.format(self.key, constellation) + try: + data = requests.get(url=url, headers=self.headers).json() + except TimeoutError as e: + msg = f'\n[ERROR]:星座查询接口超时,错误信息:{e}' + output(msg) + return msg + for news in data['newslist']: + msg += news['type'] + ':' + news['content'] + '\n' + msg = f'\n星座:{constellation}\n' + msg.strip() + f"\n{'By: #' + self.system_copyright if self.system_copyright else ''}" + return msg + + # 早安寄语 + def get_morning(self): + url = self.morning_api.format(self.key) + try: + data = requests.get(url=url, headers=self.headers).json() + except TimeoutError as e: + msg = f'\n[ERROR]:早安寄语接口超时,错误信息:{e}' + output(msg) + return msg + msg = f'{data["result"]["content"]}' + return msg + + # 早报推送 + def get_freebuf_news(self, ): + yesterday = (datetime.date.today() + datetime.timedelta(-1)) + morning_time = yesterday.strftime("%a, %d %b %Y", ) + str_list = "#FreeBuf早报\n" + try: + rs1 = feedparser.parse('https://www.freebuf.com/feed') + # print(rs1['entries']) + for ent in rs1['entries']: + if morning_time in ent['published']: + title = ent['title'] + link = ent['link'] + str_list += '\n' + title + '\n' + link + '\n' + if 'http' not in str_list: + str_list += '\n今日暂无文章' + except Exception as e: + link6 = "\n今日暂无文章" + str_list += link6 + output("ERROR:获取FreeBuf早报出错,错误信息: {}".format(e)) + str_list += f"\n{self.system_copyright + '整理分享,更多内容请戳 #' + self.system_copyright if self.system_copyright else ''}\n{time.strftime('%Y-%m-%d %X')}" + return str_list + + # 获取先知社区文章 + def get_xz_news(self, news_list): + news_list = "#先知社区" + try: + rs1 = feedparser.parse('https://xz.aliyun.com/feed') + for ent in rs1['entries']: + if str(time.strftime('%Y-%m-%d')) in ent['published']: + title = ent['title'] + link = ent['link'] + news_list += '\n' + title + '\n' + link + '\n' + if 'http' not in news_list: + news_list += '\n今日暂无文章\n' + except Exception as e: + link6 = "\n今日暂无文章\n" + news_list += link6 + output("ERROR:获取先知社区文章出错,错误信息: {}".format(e)) + return news_list + + # 获取奇安信攻防社区文章 + def get_qax_news(self, news_list): + news_list += "\n#奇安信攻防社区" + try: + rs1 = feedparser.parse('https://forum.butian.net/Rss') + for ent in rs1['entries']: + if str(time.strftime('%Y-%m-%d')) in ent['published']: + title = ent['title'] + link = ent['link'] + news_list += '\n' + title + '\n' + link + '\n' + if 'http' not in news_list: + news_list += '\n今日暂无文章\n' + except Exception as e: + link6 = "\n今日暂无文章\n" + news_list += link6 + output("ERROR:获取奇安信攻防社区文章出错,错误信息: {}".format(e)) + return news_list + + # 获取安全客文章 + def get_anquanke_news(self, news_list): + news_list += "\n#安全客" + try: + resp = requests.get('https://www.anquanke.com/knowledge', timeout=5, verify=False) + tree = etree.HTML(resp.text) + divs = tree.xpath('//div[@class="article-item common-item"]/div') + for div in divs: + href = urljoin('https://www.anquanke.com/knowledge', div.xpath('.//div[@class="title"]/a/@href')[0]) + title = div.xpath('.//div[@class="title"]/a/text()')[0].strip() + publish_time = div.xpath('.//span[@style="vertical-align: middle;"]/text()')[1] + if str(time.strftime('%Y-%m-%d')) in publish_time: + news_list += '\n' + title + '\n' + href + '\n' + # print(href, title, publish_time) + if 'http' not in news_list: + news_list += '\n今日暂无文章\n' + except Exception as e: + link6 = "\n今日暂无文章\n" + news_list += link6 + output("ERROR:获取安全客文章出错,错误信息: {}".format(e)) + return news_list + + # 获取各平台安全文章 + def get_safety_news(self, ): + news_list = '' + output("[+]:正在爬取安全新闻... ...") + news_list = self.get_xz_news(news_list) + news_list = self.get_qax_news(news_list) + news_list = self.get_anquanke_news(news_list) + output("[+]:获取成功") + news_list += f"\n{self.system_copyright + '整理分享,更多内容请戳 #' + self.system_copyright if self.system_copyright else ''}\n{time.strftime('%Y-%m-%d %X')}" + return news_list.strip() + + # 测试专用 + def demo(self): + # url = 'https://tucdn.wpon.cn/api-girl/' + # data = requests.get(url=url, headers=self.headers).json() + # print(data) + domain = 'qq.com' + text = 'https://v.api.aa1.cn/api/icp/index.php?url={domain}'.format(domain=domain) + print(text) + + +if __name__ == '__main__': + Asm = Api_Server_Main() + # Asm.get_ai('你好') + # Asm.get_pic() + # Asm.demo() + # Asm.get_video() + # Asm.icp_query(keyword='ICP查询 qq.com') + # Asm.get_suffix(keyword='icp查询 apk') + # Asm.get_attribution(keyword='归属查询 17371963534') + # Asm.get_whois(keyword='whois查询 qq.com') + # Asm.get_wether(keyword='天气查询 123') + # Asm.get_dog() + # Asm.get_constellation('星座查询 白羊座') + # print(Asm.get_freebuf_news()) + # print(Asm.get_xz_news('')) + # print(Asm.get_qax_news('')) + # print(Asm.get_anquanke_news('')) + print(Asm.get_safety_news()) \ No newline at end of file diff --git a/BotServer/MainServer.py b/BotServer/MainServer.py new file mode 100644 index 0000000..b715c81 --- /dev/null +++ b/BotServer/MainServer.py @@ -0,0 +1,253 @@ +from Recv_Msg_Dispose.FriendMsg_dispose import FriendMsg_dispose +from Recv_Msg_Dispose.RoomMsg_dispose import RoomMsg_disposes +from Push_Server.Push_Main_Server import Push_Main_Server +from Db_Server.Db_User_Server import Db_User_Server +from concurrent.futures import ThreadPoolExecutor +from BotServer.SendServer import SendServer +from bs4 import BeautifulSoup +from Output.output import * +import websocket +import yaml +import json +import os + + +class MainServers: + + def __init__(self): + # 初始化读取配置文件 + current_path = os.path.dirname(__file__) + config = yaml.load(open(current_path + '/../Config/config.yaml', encoding='UTF-8'), yaml.Loader) + self.ip = config['BotServer']['IP'] + self.port = config['BotServer']['PORT'] + self.system_copyright = config['System_Config']['System_Copyright'] + + # 配置HOOK信息类型 + self.SERVER = f"ws://{self.ip}:{self.port}" + self.HEART_BEAT = 5005 + self.RECV_TXT_MSG = 1 + self.RECV_TXT_CITE_MSG = 49 + self.RECV_PIC_MSG = 3 + self.USER_LIST = 5000 + self.GET_USER_LIST_SUCCSESS = 5001 + self.GET_USER_LIST_FAIL = 5002 + self.TXT_MSG = 555 + self.PIC_MSG = 500 + self.AT_MSG = 550 + self.CHATROOM_MEMBER = 5010 + self.CHATROOM_MEMBER_NICK = 5020 + self.PERSONAL_INFO = 6500 + self.DEBUG_SWITCH = 6000 + self.PERSONAL_DETAIL = 6550 + self.DESTROY_ALL = 9999 + self.JOIN_ROOM = 10000 + self.ATTATCH_FILE = 5003 + + # 启动机器人 + self.ws = websocket.WebSocketApp( + self.SERVER, on_open=self.on_open, on_message=self.on_message, on_error=self.on_error, + on_close=self.on_close + ) + + # 实例化消息服务 + self.Ss = SendServer() + + # 实例化群消息处理类 + self.Rmd = RoomMsg_disposes() + + # 实例化好友消息处理 + self.Fmd = FriendMsg_dispose() + + # 实例化用户数据服务类 + self.Dus = Db_User_Server() + + # Robot初始化执行 + def on_open(self, ws): + # 实例化实时监控类 + self.Pms = Push_Main_Server(ws=self.ws) + pool = ThreadPoolExecutor(5) + pool.submit(self.Pms.run) + self.get_personal_info() + + # Robot 启动函数 + def Bot_start(self, ): + self.ws.run_forever() + + # Robot 关闭执行 + def on_close(self, ws): + output("The Robot is Closed...") + + # Robot 错误输出 + def on_error(self, ws, error): + output(f"[ERROR]:出现 错误,错误信息:{error}") + + # 启动完成输出 + def handle_wxuser_list(self): + output("Bot is Start!") + + # Robot 心跳输出 + def heartbeat(self, msgJson): + output(f'[*]:{msgJson["content"]}') + + # DEBUG选择HOOK信息类型 + def debug_switch(self, ): + qs = { + "id": self.Ss.get_id(), + "type": self.DEBUG_SWITCH, + "content": "off", + "wxid": "ROOT", + } + return json.dumps(qs) + + # 处理缺口 + def handle_nick(self, j): + data = j.content + i = 0 + for d in data: + output(f"nickname:{d.nickname}") + i += 1 + + # 处理所有Roomid + def hanle_memberlist(self, j): + data = j.content + i = 0 + for d in data: + output(f"roomid:{d.roomid}") + i += 1 + + # 销毁全部接口 + def destroy_all(self, ): + qs = { + "id": self.Ss.get_id(), + "type": self.DESTROY_ALL, + "content": "none", + "wxid": "node", + } + return json.dumps(qs) + + # 处理带引用的文字消息 + def handleMsg_cite(self, msgJson): + msgXml = ( + msgJson["content"]["content"] + .replace("&", "&") + .replace("<", "<") + .replace(">", ">") + ) + soup = BeautifulSoup(msgXml, "xml") + msgJson = { + "content": soup.select_one("title").text, + "id": msgJson["id"], + "id1": msgJson["content"]["id2"], + "id2": "wxid_fys2fico9put22", + "id3": "", + "srvid": msgJson["srvid"], + "time": msgJson["time"], + "type": msgJson["type"], + "wxid": msgJson["content"]["id1"], + } + self.handle_recv_msg(msgJson) + + # 选择消息类型 + def on_message(self, ws, message): + j = json.loads(message) + resp_type = j["type"] + # switch结构 + action = { + self.CHATROOM_MEMBER_NICK: self.handle_nick, + self.PERSONAL_DETAIL: self.handle_recv_msg, + self.AT_MSG: self.handle_recv_msg, + self.DEBUG_SWITCH: self.handle_recv_msg, + self.PERSONAL_INFO: self.handle_recv_msg, + self.TXT_MSG: self.handle_recv_msg, + self.PIC_MSG: self.handle_recv_msg, + self.CHATROOM_MEMBER: self.hanle_memberlist, + self.RECV_PIC_MSG: self.handle_recv_msg, + self.RECV_TXT_MSG: self.handle_recv_msg, + self.RECV_TXT_CITE_MSG: self.handleMsg_cite, + self.HEART_BEAT: self.heartbeat, + self.USER_LIST: self.handle_wxuser_list, + self.GET_USER_LIST_SUCCSESS: self.handle_wxuser_list, + self.GET_USER_LIST_FAIL: self.handle_wxuser_list, + self.JOIN_ROOM: self.welcome_join, + } + action.get(resp_type, print)(j) + + # 获取获取微信通讯录用户名字和wxid,好友列表 + def get_wx_user_list(self, ): + qs = { + "id": self.Ss.get_id(), + "type": self.USER_LIST, + "content": "user list", + "wxid": "null", + } + # Output(qs) + return json.dumps(qs) + + def get_personal_info(self, ): + # 获取本机器人的信息 + uri = "/api/get_personal_info" + data = { + "id": self.Ss.get_id(), + "type": self.PERSONAL_INFO, + "content": "op:personal info", + "wxid": "null", + } + respJson = self.Ss.send(uri, data) + wechatBotInfo = f""" + + NGCBot登录信息 + + 微信昵称:{json.loads(respJson["content"])['wx_name']} + 微信号:{json.loads(respJson["content"])['wx_code']} + 微信id:{json.loads(respJson["content"])['wx_id']} + 启动时间:{respJson['time']} + {'By: ' + self.system_copyright if self.system_copyright else ''} + """ + output(wechatBotInfo.strip()) + + # 入群欢迎函数 + def welcome_join(self, msgJson): + output(f"收到消息:{msgJson}") + if "邀请" in msgJson["content"]["content"]: + roomid = msgJson["content"]["id1"] + nickname = msgJson["content"]["content"].split('"')[-2] + msg = '\n欢迎新进群的小可爱[烟花]' + roomid_list, room_names = self.Dus.show_white_room() + if roomid in roomid_list: + self.ws.send(self.Ss.send_msg(msg=msg, roomid=roomid, wxid='null', + nickname=nickname)) + + # 消息接收函数 + def handle_recv_msg(self, msgJson): + if "wxid" not in msgJson and msgJson["status"] == "SUCCSESSED": + if msgJson['type'] == 3: + output(f"收到图片:{msgJson}") + else: + output(f"[*]:发送成功") + return + output(f"收到消息:{msgJson}") + # 判断群聊消息还是私人消息 + if "@chatroom" in msgJson["wxid"]: + # 获取群ID + roomid = msgJson["wxid"] + # 获取发送人ID + senderid = msgJson["id1"] + else: + roomid = None + nickname = "null" + # 获取发送人ID + senderid = msgJson["wxid"] + + # 获取发送者的名字 + nickname = self.Ss.get_member_nick(roomid, senderid) + if roomid: + # 处理微信群消息 + self.Rmd.get_information(msgJson=msgJson, roomid=roomid, senderid=senderid, nickname=nickname, ws=self.ws) + else: + # 处理通讯录好友发送的消息 + self.Fmd.get_information(msgJson=msgJson, senderid=senderid, ws=self.ws) + + +if __name__ == '__main__': + Ms = MainServers() + Ms.Bot_start() diff --git a/BotServer/SendServer.py b/BotServer/SendServer.py new file mode 100644 index 0000000..c52e147 --- /dev/null +++ b/BotServer/SendServer.py @@ -0,0 +1,144 @@ +from Output.output import output +import requests +import time +import json +import yaml +import os + + +class SendServer: + + def __init__(self): + # 初始化读取配置文件 + current_path = os.path.dirname(__file__) + config = yaml.load(open(current_path + '/../config/config.yaml', encoding='UTF-8'), yaml.Loader) + self.ip = config['BotServer']['IP'] + self.port = config['BotServer']['PORT'] + + # 配置HOOK信息类型 + self.SERVER = f"ws://{self.ip}:{self.port}" + self.HEART_BEAT = 5005 + self.RECV_TXT_MSG = 1 + self.RECV_TXT_CITE_MSG = 49 + self.RECV_PIC_MSG = 3 + self.USER_LIST = 5000 + self.GET_USER_LIST_SUCCSESS = 5001 + self.GET_USER_LIST_FAIL = 5002 + self.TXT_MSG = 555 + self.PIC_MSG = 500 + self.AT_MSG = 550 + self.CHATROOM_MEMBER = 5010 + self.CHATROOM_MEMBER_NICK = 5020 + self.PERSONAL_INFO = 6500 + self.DEBUG_SWITCH = 6000 + self.PERSONAL_DETAIL = 6550 + self.DESTROY_ALL = 9999 + self.JOIN_ROOM = 10000 + self.ATTATCH_FILE = 5003 + + # 通用消息发送函数 + def send(self, uri, data): + base_data = { + "id": self.get_id(), + "type": "null", + "roomid": "null", + "wxid": "null", + "content": "null", + "nickname": "null", + "ext": "null", + } + base_data.update(data) + url = f'http://{self.ip}:{self.port}/{uri}' + res = requests.post(url, json={"para": base_data}, timeout=5) + return res.json() + + # 定义信息ID + def get_id(self): + return time.strftime("%Y-%m-%d %H:%M:%S") + + # 发送文本消息函数 + def send_msg(self, msg, wxid="null", roomid='null', nickname="null"): + if roomid != 'null': + msg_type = self.AT_MSG + else: + msg_type = self.TXT_MSG + qs = { + "id": self.get_id(), + "type": msg_type, + "roomid": roomid, + "wxid": wxid, + "content": msg, + "nickname": nickname, + "ext": "null", + } + output(f"[*]:发送消息: {qs}") + return json.dumps(qs) + + # 通用文件发送函数 + def send_file_room(self, file, roomid): + output("[+]:文件发送中... ...") + data = { + "id": self.get_id(), + "type": self.ATTATCH_FILE, + "roomid": "null", + "content": file, + "wxid": roomid, + "nickname": "null", + "ext": "null", + } + url = f"http://{self.ip}:{self.port}/api/sendattatch" + res = requests.post(url, json={"para": data}, timeout=5) + if res.status_code == 200 and res.json()["status"] == "SUCCSESSED": + output("[*]:文件发送成功") + else: + output(f"[ERROR]:出现错误,错误信息:{res.text}") + + # 图片发送函数 + def send_img_room(self, msg, roomid): + output("[+]:图片发送中... ...") + data = { + "id": self.get_id(), + "type": self.PIC_MSG, + "roomid": "null", + "content": msg, + "wxid": roomid, + "nickname": "null", + "ext": "null", + } + url = f"http://{self.ip}:{self.port}/api/sendpic" + res = requests.post(url, json={"para": data}, timeout=5) + if res.status_code == 200 and res.json()["status"] == "SUCCSESSED": + output("[*]:图片发送成功!") + else: + output(f"[ERROR]:出现错误,错误信息:{res.text}") + + # 获取所有群的wxid + def get_memberid(self, ): + uri = 'api/getmemberid' + data = { + 'type': self.CHATROOM_MEMBER, + 'content': 'op:list member' + } + output(self.send(uri, data)) + + # 获取@昵称 或 微信好友的昵称 + def get_member_nick(self, roomid, wxid): + uri = "api/getmembernick" + data = {"type": self.CHATROOM_MEMBER_NICK, "wxid": wxid, "roomid": roomid or "null"} + respJson = self.send(uri, data) + return json.loads(respJson["content"])["nick"] + + # 获取机器人微信ID + def get_bot_info(self, ): + uri = "/api/get_personal_info" + data = { + "id": self.get_id(), + "type": self.PERSONAL_INFO, + "content": "op:personal info", + "wxid": "null", + } + respJson = self.send(uri, data) + bot_wxid = json.loads(respJson["content"])['wx_id'] + return bot_wxid + + diff --git a/Cache/Cache_Server.py b/Cache/Cache_Server.py new file mode 100644 index 0000000..909dac6 --- /dev/null +++ b/Cache/Cache_Server.py @@ -0,0 +1,51 @@ +from Output.output import output +import os + + +class Cache_Server: + + def __init__(self): + # 配置缓存文件存放路径 + current_path = os.path.dirname(__file__) + self.video_cache = current_path + '/Video_Cache' + self.fish_cache = current_path + '/Fish_Cache' + self.pic_cache = current_path + '/Pic_Cache' + self.create_folder() + + def delete_file(self): + output('[+]:缓存清除功能工作中... ...') + if os.path.exists(self.video_cache): + try: + file_lists = list() + file_lists += [self.video_cache + '/' + file for file in os.listdir(self.video_cache)] + file_lists += [self.fish_cache + '/' + file for file in os.listdir(self.fish_cache)] + file_lists += [self.pic_cache + '/' + file for file in os.listdir(self.pic_cache)] + for rm_file in file_lists: + os.remove(rm_file) + except Exception as e: + msg = "[ERROR]:清除缓存时出错,错误信息:{}".format(e) + output(msg) + return msg + msg = "缓存文件已清除!" + return msg + else: + msg = "[-]:缓存文件夹未创建,正在创建缓存文件夹... ..." + output(msg) + self.create_folder() + + def create_folder(self): + if not os.path.exists(self.video_cache): + try: + os.mkdir(self.video_cache) + os.mkdir(self.pic_cache) + os.mkdir(self.fish_cache) + except Exception as e: + msg = '[ERROR]:创建文件夹出错,错误信息:{}'.format(e) + output(msg) + + +if __name__ == '__main__': + Fs = Cache_Server() + # # Fs.create_folder() + Fs.delete_file() + diff --git a/Config/config.yaml b/Config/config.yaml new file mode 100644 index 0000000..fcee73b --- /dev/null +++ b/Config/config.yaml @@ -0,0 +1,198 @@ +## 机器人服务配置 +BotServer: + IP: 127.0.0.1 + PORT: 5555 + +## 超级管理员配置 +Administrators: + - 'wxid_7bizfilssbwi22' + +## 关键词配置 +Key_Word: + # 触发美女图片关键词 + Pic_Word: + - '图片' + - '美女图片' + # 触发美女视频关键词 + Video_Word: + - '视频' + - '美女视频' + # 触发备案查询关键词 + Icp_Word: + - '备案查询' + - 'ICP查询' + - 'icp查询' + # 触发后缀名查询关键词 + Suffix_Word: + - '后缀名查询' + - '后缀查询' + # 触发归属查询关键词 + Attribution_Word: + - '归属查询' + - '归属地查询' + # 触发WHOIS查询关键词 + Whois_Word: + - 'whois查询' + - 'WHOIS查询' + # 触发摸鱼日记关键词 + Fish_Word: + - '摸鱼日记' + - '摸鱼日历' + # 触发天气查询关键词 + Weather_Word: + - '天气查询' + - '查询天气' + # 触发舔狗日记关键词 + Dog_Word: + - '舔狗日记' + - '舔我' + # 触发星座查询关键词 + Constellation_Word: + - '星座查询' + - '查询星座' + - '运势查询' + - '查询运势' + # 触发早安寄语关键词 + Morning_Word: + - '早安' + # 触发微步IP查询关键词 + ThreatBook_Word: + - 'ip查询' + - 'IP查询' + - '查询ip' + - '查询IP' + - '微步查询' + # 新增管理员关键词 + Add_Admin_Word: + - '添加管理员' + - '添加管理' + - '新增管理员' + # 删除管理员关键词 + Del_Admin_Word: + - '删除管理员' + - '删除管理' + - '移除管理员' + # 新增黑名单群聊关键词 + Add_BlackRoom_Word: + - '拉黑群聊' + - '添加黑名单' + # 移出黑名单群聊关键词 + Del_BlackRoom_Word: + - '解除拉黑' + - '移出黑名单' + # 查看白名单群聊关键词 + Show_WhiteRoom_Word: + - '查看群聊' + - '查看推送群聊' + # 新增白名单群聊关键词 + Add_WhiteRoom_Word: + - '拉白' + - '添加白名单' + - '开启推送服务' + - '开启推送功能' + # 移出白名单群聊关键词 + Del_WhiteRoom_Word: + - '关闭推送服务' + - '关闭推送功能' + - '移出白名单' + # 触发早报关键词 + Morning_Page: + - '早报' + - '早间咨询' + # 触发晚报关键词 + Evening_Page: + - '晚报' + - '晚间咨询' + +## API接口服务配置 +Api_Server: + Api_Config: + Appid: '' + Appsecret: '' + Key: '' + ThreatBook_Key: '' + OpenAi_Key: '' + # 扩展名查询API + Extensions_Api: 'https://apis.tianapi.com/targa/index?key={}&word={}' + # 天气查询API + Wether_Api: 'https://www.tianqiapi.com/free/day?appid={}&appsecret={}&city={}' + # 舔狗日记API + Dog_Api: 'http://api.tianapi.com/tiangou/index?key={}' + # 星座查询API + Constellation_Api: 'http://api.tianapi.com/star/index?key={}&astro={}' + # 早安寄语API + Morning_Api: 'https://apis.tianapi.com/zaoan/index?key={}' + # 微步查询API + ThreatBook_Api: 'https://api.threatbook.cn/v3/scene/ip_reputation' + + # 摸鱼日记API + Fish_Api: 'https://api.vvhan.com/api/moyu' + # Whois查询API + Whois_Api: 'https://v.api.aa1.cn/api/whois/index.php?domain={}' + # 归属地查询API + Attribution_Api: 'https://v.api.aa1.cn/api/phone/guishu-api.php?phone={}' + # 备案查询API配置 + Icp_Api: 'https://v.api.aa1.cn/api/icp/index.php?url={}' + # 图片API配置 + Pic_Api: + - 'https://api.vvhan.com/api/girl' + - 'https://v.api.aa1.cn/api/pc-girl_bz/index.php?wpon=ro38d57y8rhuwur3788y3rd' + - 'https://api.vvhan.com/api/mobil.girl' + # 视频API配置 + Video_Api: + - 'https://v.api.aa1.cn/api/api-dy-girl/index.php?aa1=json' + - 'https://v.api.aa1.cn/api/api-girl-11-02/index.php?type=json' + +## 积分功能配置 +Point_Function: + # 签到口令 + Sign_Keyword: '签到:NGC660安全实验室祝大家天天得0day!' + # 签到积分配置 + Sign_Point: 10 + # 积分功能配置 + Function: + ThreatBook_Point: 8 + # 增加积分关键词 + Add_Point_Word: + - '加' + - '+' + # 扣除积分关键词 + Del_Point_Word: + - '减' + - '-' + # 赠送积分关键词 + Give_Point_Word: + - '送' + # 查看积分关键词 + Query_Point: + - '查看积分' + - '积分查询' + +## 定时推送配置 +Timed_Push: + # 早报推送时间 + Morning_Page_Time: '08:00' + # 晚报推送时间 + Evening_Page_Time: '17:00' + # 摸鱼日记推送时间 + Fish_Time: '10:00' + # 下班消息推送时间 + Off_Work_Time: '18:00' + +## 系统相关配置 +System_Config: + # ChatGpt 代理服务器,如不使用代理也能访问openai,不填即可 + HTTP_PROXY: '' + HTTPS_PROXY: '' + + # 缓存清除关键词配置 + Cache_Config_Word: + - '清除缓存' + - '清空缓存' + # 版权信息配置 + System_Copyright: 'NGC660安全实验室' + # 帮助菜单关键词配置 + Help_Menu: + - '帮助菜单' + - 'help' + - 'HELP' \ No newline at end of file diff --git a/Db_Server/Db_Point_Server.py b/Db_Server/Db_Point_Server.py new file mode 100644 index 0000000..aa8a4bc --- /dev/null +++ b/Db_Server/Db_Point_Server.py @@ -0,0 +1,172 @@ +from Output.output import output +import sqlite3 +import yaml +import os + + +class Db_Point_Server: + def __init__(self): + current_path = os.path.dirname(__file__) + # 数据库存放地址 + self.db_file = current_path + '/../Config/Point_db.db' + self.judge_init() + config = yaml.load(open(current_path + '/../Config/config.yaml', encoding='UTF-8'), yaml.Loader) + + # 读取积分配置 + self.sign_point = config['Point_Function']['Sign_Point'] + + # 打开数据库 + def open_db(self): + conn = sqlite3.connect(database=self.db_file, ) + cursor = conn.cursor() + return conn, cursor + + # 关闭数据库 + def close_db(self, conn, cursor): + cursor.close() + conn.close() + + # 判断数据库是否初始化 + def judge_init(self, ): + conn, cursor = self.open_db() + judge_table_sql = '''SELECT name FROM sqlite_master;''' + cursor.execute(judge_table_sql) + data = cursor.fetchall() + if not data: + msg = '[+]:检测到积分数据库未初始化,正在初始化数据库' + self.init_db() + output(msg) + self.close_db(conn, cursor) + + + # 初始化数据库 + def init_db(self): + conn, cursor = self.open_db() + create_point_table_sql = '''CREATE TABLE IF NOT EXISTS points + (wx_id varchar(255), + wx_name varchar(255), + point int(20));''' + create_sign_table_sql = '''CREATE TABLE IF NOT EXISTS sign (wx_id varchar(255), wx_name varchar(255));''' + cursor.execute(create_point_table_sql) + cursor.execute(create_sign_table_sql) + self.close_db(conn, cursor) + output('[*]:积分服务初始化成功!') + + # 初始化新用户 + def init_user(self, wx_id, wx_name): + conn, cursor = self.open_db() + add_user_sql = f'''INSERT INTO points VALUES ('{wx_id}', '{wx_name}', 0);''' + cursor.execute(add_user_sql) + conn.commit() + self.close_db(conn, cursor) + + # 判断用户是否存在 + def judge_user(self, wx_id, sign_bool=False): + conn, cursor = self.open_db() + judge_user_sql = f'''SELECT wx_id FROM points WHERE wx_id='{wx_id}';''' + if sign_bool: + judge_user_sql = f'''SELECT wx_id FROM sign WHERE wx_id='{wx_id}';''' + cursor.execute(judge_user_sql) + data = cursor.fetchall() + if data: + return True + else: + return False + + # 增加积分 + def add_point(self, wx_id, point): + conn, cursor = self.open_db() + add_point_sql = f'''UPDATE points SET point=point+{point} WHERE wx_id='{wx_id}';''' + cursor.execute(add_point_sql) + conn.commit() + self.close_db(conn, cursor) + msg = f'\n基于你的优越表现,+{point}分\n当前未使用积分:{self.query_point(wx_id=wx_id, )}分' + return msg + + # 扣除积分 + def del_point(self, wx_id, point): + conn, cursor = self.open_db() + add_point_sql = f'''UPDATE points SET point=point-{point} WHERE wx_id='{wx_id}';''' + cursor.execute(add_point_sql) + conn.commit() + self.close_db(conn, cursor) + msg = f'\n介于你的近期表现,-{point}分\n当前未使用积分:{self.query_point(wx_id=wx_id, )}分' + return msg + + # 查询积分 + def query_point(self, wx_id, wx_name=None): + conn, cursor = self.open_db() + query_point_sql = f'''SELECT point,wx_id FROM points WHERE wx_id='{wx_id}';''' + cursor.execute(query_point_sql) + if not cursor.fetchone(): + self.init_user(wx_id=wx_id, wx_name=wx_name) + cursor.execute(query_point_sql) + data = cursor.fetchone() + return data[0] + + # 签到功能 + def sign(self, wx_id, wx_name): + conn, cursor = self.open_db() + sign_sql = f'''INSERT INTO sign VALUES ('{wx_id}', '{wx_name}');''' + self.add_point(wx_id=wx_id, point=self.sign_point) + cursor.execute(sign_sql) + conn.commit() + self.close_db(conn, cursor) + msg = f'签到成功 + {self.sign_point} 分\n当前可用积分:{self.query_point(wx_id=wx_id)}' + return msg + + # 清空签到表 + def clear_sign(self): + conn, cursor = self.open_db() + clear_sign_sql = 'DELETE FROM sign' + cursor.execute(clear_sign_sql) + conn.commit() + self.close_db(conn, cursor) + + # 积分赠送 + def give_point(self, wx_id, wx_name, at_wx_id, at_wx_name, point): + if self.query_point(wx_id=wx_id) >= self.query_point(wx_id=at_wx_id): + # 赠送人扣除积分 + self.judge_main(wx_id=wx_id, wx_name=wx_name, point=point, del_bool=True) + # 被赠送人增加积分 + self.judge_main(wx_id=at_wx_id, wx_name=at_wx_name, point=point, add_bool=True) + msg = f'\n您已给予 {at_wx_name} {point}分 \n当前可用积分 {self.query_point(wx_id=wx_id)}分' + else: + msg = f'\n您当前的余额不足\n当前可用积分:{self.query_point(wx_id=wx_id)} 分' + give_bool = True + return msg, give_bool + + # 主判断 + def judge_main(self, wx_id, wx_name, point=None, add_bool=False, del_bool=False, sign_bool=False): + msg = '' + if sign_bool: + if not self.judge_user(wx_id=wx_id, sign_bool=True): + if self.judge_user(wx_id=wx_id, ): + msg = self.sign(wx_id=wx_id, wx_name=wx_name, ) + else: + output('[+]:当前用户不存在,正在初始化该用户... ...') + self.init_user(wx_id=wx_id, wx_name=wx_name) + msg = self.sign(wx_id=wx_id, wx_name=wx_name) + elif add_bool: + if self.judge_user(wx_id=wx_id): + msg = self.add_point(wx_id=wx_id, point=point) + else: + output('[+]:当前用户不存在,正在初始化该用户... ...') + self.init_user(wx_id=wx_id, wx_name=wx_name) + msg = self.add_point(wx_id=wx_id, point=point) + elif del_bool: + if self.judge_user(wx_id=wx_id): + msg = self.del_point(wx_id=wx_id, point=point) + else: + output('[+]:当前用户不存在,正在初始化该用户... ...') + self.init_user(wx_id=wx_id, wx_name=wx_name) + msg = self.del_point(wx_id=wx_id, point=point) + return msg + + +if __name__ == '__main__': + Dps = Db_Point_Server() + # Dps.init_db() + msg = Dps.judge_main(wx_id='123123123', wx_name='123', sign_bool=True, point=230) + print(msg) + # Dps.query_point(wx_id='123') diff --git a/Db_Server/Db_User_Server.py b/Db_Server/Db_User_Server.py new file mode 100644 index 0000000..cf24126 --- /dev/null +++ b/Db_Server/Db_User_Server.py @@ -0,0 +1,222 @@ +from Output.output import output +import sqlite3 +import os + + +class Db_User_Server: + def __init__(self): + current_path = os.path.dirname(__file__) + # 数据库存放地址 + self.db_file = current_path + '/../Config/User_db.db' + self.judge_init() + + # 打开数据库 + def open_db(self): + conn = sqlite3.connect(database=self.db_file, ) + cursor = conn.cursor() + return conn, cursor + + # 关闭数据库 + def close_db(self, conn, cursor): + cursor.close() + conn.close() + + # 判断是否初始化 + def judge_init(self, ): + conn, cursor = self.open_db() + judge_table_sql = '''SELECT name FROM sqlite_master;''' + cursor.execute(judge_table_sql) + data = cursor.fetchall() + if not data: + msg = '[-]:检测到用户数据库未初始化,正在初始化数据库' + output(msg) + self.init_db() + self.close_db(conn, cursor) + + # 初始化数据库 + def init_db(self): + conn, cursor = self.open_db() + create_admin_table_sql = '''CREATE TABLE IF NOT EXISTS admins + (wx_id varchar(255), + wx_name varchar(255), + wx_roomid varchar(255), + wx_room_name varchar(255));''' + create_white_rooms = '''CREATE TABLE IF NOT EXISTS white_rooms + (wx_roomid varchar(255), + wx_room_name varchar(255));''' + create_black_rooms = '''CREATE TABLE IF NOT EXISTS black_rooms + (wx_roomid varchar(255), + wx_room_name varchar(255));''' + create_users_table = '''CREATE TABLE IF NOT EXISTS users + (wx_id varchar(255), + wx_name varchar(255));''' + + cursor.execute(create_admin_table_sql) + cursor.execute(create_white_rooms) + cursor.execute(create_black_rooms) + cursor.execute(create_users_table) + self.close_db(conn=conn, cursor=cursor) + output('[*]:用户数据库服务初始化成功!') + + # 查看用户WXID + def show_userid(self, wx_name): + data = self.judge_data(wx_name=wx_name, user_bool=True) + return data[0][0] + + # 添加用户数据(wx_name,wx_id) + def add_user(self, wx_id, wx_name): + if not self.judge_data(wx_name=wx_name, user_bool=True): + conn, cursor = self.open_db() + add_user_sql = f'''INSERT INTO users VALUES ("{wx_id}", "{wx_name}");''' + cursor.execute(add_user_sql) + conn.commit() + self.close_db(conn, cursor) + output(f'[+]:添加用户 {wx_name} 成功!') + + # 添加管理员 + def add_admin(self, wx_id, wx_roomid, wx_name, wx_room_name): + if not self.judge_data(wx_id=wx_id, wx_roomid=wx_roomid): + conn, cursor = self.open_db() + add_admin_sql = f'''INSERT INTO admins VALUES ( + '{wx_id}', '{wx_name}', '{wx_roomid}', '{wx_room_name}');''' + cursor.execute(add_admin_sql) + conn.commit() + self.close_db(conn=conn, cursor=cursor) + msg = f'添加管理员 {wx_name} 成功!' + else: + msg = f'管理员 {wx_name} 已存在!' + return msg + + # 删除管理员 + def del_admin(self, wx_id, wx_name, wx_roomid): + if self.judge_data(wx_id=wx_id, wx_roomid=wx_roomid): + conn, cursor = self.open_db() + del_admin_sql = f'''DELETE FROM admins WHERE wx_id='{wx_id}' and wx_roomid='{wx_roomid}';''' + cursor.execute(del_admin_sql) + conn.commit() + self.close_db(conn, cursor) + msg = f'移除管理员 {wx_name} 成功!' + else: + msg = f'管理员 {wx_name} 已移出!' + return msg + + # 查看所有管理员 + def show_admin(self): + conn, cursor = self.open_db() + show_admin_sql = '''SELECT wx_id, wx_roomid FROM admins;''' + cursor.execute(show_admin_sql) + data = cursor.fetchall() + self.close_db(conn, cursor) + msg = [] + for d in data: + msg.append({'wx_id': d[0], 'wx_roomid': d[1]}) + return msg + + # 添加黑名单群聊 + def add_black_room(self, wx_roomid, wx_room_name): + if not self.judge_data(black_bool=True, wx_roomid=wx_roomid): + conn, cursor = self.open_db() + add_black_room_sql = f'''INSERT INTO black_rooms VALUES ('{wx_roomid}', '{wx_room_name}');''' + cursor.execute(add_black_room_sql) + conn.commit() + self.close_db(conn, cursor) + msg = f'{wx_room_name} 群聊已拉黑! ' + else: + msg = '当前群聊已添加至黑名单' + return msg + + # 删除黑名单群聊 + def del_black_room(self, wx_roomid, wx_room_name): + if self.judge_data(wx_roomid=wx_roomid, black_bool=True): + conn, cursor = self.open_db() + del_black_room_sql = f'''DELETE FROM black_rooms WHERE wx_roomid='{wx_roomid}';''' + cursor.execute(del_black_room_sql) + conn.commit() + self.close_db(conn, cursor) + msg = f'移除黑名单群聊 {wx_room_name} 成功!' + else: + msg = '该群聊未被拉黑!' + return msg + + # 查看黑名单群聊 + def show_black_room(self): + conn, cursor = self.open_db() + show_black_room_sql = '''SELECT wx_roomid FROM black_rooms;''' + cursor.execute(show_black_room_sql) + data = cursor.fetchall() + self.close_db(conn, cursor) + msg = list() + for d in data: + msg.append({'wx_roomid': d[0]}) + return msg + + # 添加白名单群聊 + def add_white_room(self, wx_roomid, wx_room_name): + if not self.judge_data(wx_roomid=wx_roomid, white_bool=True): + conn, cursor = self.open_db() + add_white_room_sql = f'''INSERT INTO white_rooms VALUES ('{wx_roomid}', '{wx_room_name}');''' + cursor.execute(add_white_room_sql) + conn.commit() + self.close_db(conn, cursor) + msg = f'{wx_room_name} 群聊已开启推送服务!' + else: + msg = '该群聊已开启推送服务!' + return msg + + # 删除白名单群聊 + def del_white_room(self, wx_roomid, wx_room_name): + if self.judge_data(wx_roomid=wx_roomid, white_bool=True): + conn, cursor = self.open_db() + del_white_room_sql = f'''DELETE FROM white_rooms WHERE wx_roomid='{wx_roomid}';''' + cursor.execute(del_white_room_sql) + conn.commit() + self.close_db(conn, cursor) + msg = f'{wx_room_name} 群聊已关闭推送服务!' + else: + msg = '该群聊未开启推送服务!' + return msg + + # 查看白名单群聊 + def show_white_room(self, ): + conn, cursor = self.open_db() + show_white_room_sql = '''SELECT wx_roomid, wx_room_name FROM white_rooms;''' + cursor.execute(show_white_room_sql) + data = cursor.fetchall() + self.close_db(conn, cursor) + white_room_id = [] + white_room_name = [] + for d in data: + white_room_id.append(d[0]) + white_room_name.append(d[1]) + return white_room_id, white_room_name + + # 判断黑白表中数据是否存在 True False + def judge_data(self, wx_id=None, wx_name=None, wx_roomid=None, black_bool=False, white_bool=False, user_bool=False): + conn, cursor = self.open_db() + sql = '' + if wx_id and not user_bool: + sql = f'''SELECT wx_id FROM admins WHERE wx_id='{wx_id}' and wx_roomid='{wx_roomid}';''' + elif black_bool: + sql = f'''SELECT wx_roomid FROM black_rooms where wx_roomid='{wx_roomid}';''' + elif user_bool: + sql = f'''SELECT wx_id FROM users where wx_name="{wx_name}"''' + elif white_bool: + sql = f'''SELECT wx_roomid FROM white_rooms where wx_roomid='{wx_roomid}';''' + if sql: + cursor.execute(sql) + data = cursor.fetchall() + if data: + return data + else: + return False + + +if __name__ == '__main__': + Dus = Db_User_Server() + # Dus.init_db() + # Dus.add_admin(wx_id='yunyun', wx_name='云云', wx_roomid='123123', wx_room_name='测试') + # Dus.del_admin(wx_id='yunyun', wx_name='云云', wx_roomid='123123') + # Dus.show_admin() + # Dus.show_white_room() + # Dus.add_user('123', '云山') + Dus.show_userid('云山') diff --git a/Output/output.py b/Output/output.py new file mode 100644 index 0000000..9f78634 --- /dev/null +++ b/Output/output.py @@ -0,0 +1,17 @@ +from termcolor import cprint +import time + + +def output(msg): + if "error" in msg or "ERROR" in msg: + color = "red" + elif '[*]' in msg: + color = "cyan" + elif '[+]' in msg: + color = 'yellow' + else: + color = "magenta" + time_now = time.strftime("%Y-%m-%d %X") + cprint(f"[{time_now}]:{msg}", color) + +output('') diff --git a/Push_Server/Push_Main_Server.py b/Push_Server/Push_Main_Server.py new file mode 100644 index 0000000..383a1fd --- /dev/null +++ b/Push_Server/Push_Main_Server.py @@ -0,0 +1,96 @@ +from Api_Server.Api_Server_Main import Api_Server_Main +from Db_Server.Db_Point_Server import Db_Point_Server +from Db_Server.Db_User_Server import Db_User_Server +from BotServer.SendServer import SendServer +from Output.output import output +from chinese_calendar import is_workday +import datetime +import schedule +import yaml +import os + + +class Push_Main_Server: + def __init__(self, ws): + current_path = os.path.dirname(__file__) + config = yaml.load(open(current_path + '/../Config/config.yaml', encoding='UTF-8'), yaml.Loader) + self.db_file = current_path + '/../Config/Point_db.db' + + # 实例化用户类 + self.Dus = Db_User_Server() + + # 实例化积分类 + self.Dps = Db_Point_Server() + + # 实例化发送消息服务 + self.Ss = SendServer() + + # 实例化API类 + self.Asm = Api_Server_Main() + + # 实例化ws + self.ws = ws + + self.morning_page_time = config['Timed_Push']['Morning_Page_Time'] + self.evening_page_time = config['Timed_Push']['Evening_Page_Time'] + self.off_work_time = config['Timed_Push']['Off_Work_Time'] + self.fish_time = config['Timed_Push']['Fish_Time'] + + # 早报推送 + def push_morning_page(self, ): + if is_workday(datetime.date.today()): + output('[+]:定时早报推送') + roomid_list, room_name_list = self.Dus.show_white_room() + msg = self.Asm.get_freebuf_news() + for roomid in roomid_list: + self.ws.send(self.Ss.send_msg(msg=msg, wxid=roomid)) + + # 晚报推送 + def push_evening_page(self, ): + if is_workday(datetime.date.today()): + output('[+]:定时晚间新闻推送') + roomid_list, room_name_list = self.Dus.show_white_room() + msg = self.Asm.get_safety_news() + for roomid in roomid_list: + self.ws.send(self.Ss.send_msg(msg=msg, wxid=roomid)) + + # 摸鱼日历推送 + def push_fish(self, ): + if is_workday(datetime.date.today()): + output('[+]:定时摸鱼日记推送') + roomid_list, room_name_list = self.Dus.show_white_room() + msg = self.Asm.get_fish() + for roomid in roomid_list: + self.Ss.send_img_room(msg=msg, roomid=roomid) + + # 下班信息推送 + def off_work_msg_push(self, ): + if is_workday(datetime.date.today()): + output('[+]:下班消息推送') + roomid_list, room_name_list = self.Dus.show_white_room() + msg = '各部门请注意,下班时间已到!!!请使用你最快的速度火速离开,\n不要浪费电费,记得打卡发日报!\n[旺财]over' + for roomid in roomid_list: + self.ws.send(self.Ss.send_msg(msg=msg, wxid=roomid)) + + # 签到表清空 + def push_clear_sign(self): + output('[+]:定时签到表清空') + self.Dps.clear_sign() + + def run(self): + schedule.every().day.at(self.morning_page_time).do(self.push_morning_page) + schedule.every().day.at(self.evening_page_time).do(self.push_evening_page) + schedule.every().day.at(self.off_work_time).do(self.off_work_msg_push) + schedule.every().day.at(self.fish_time).do(self.push_fish) + schedule.every().day.at('00:00').do(self.push_clear_sign) + # schedule.every(1).seconds.do(self.push_morning_page) + output('[*]:已开启定时推送服务!') + while True: + # output('[*]:已开启定时推送服务!') + schedule.run_pending() + + +if __name__ == '__main__': + Psm = Push_Main_Server('1') + # Psm.push_fish() + Psm.push_morning_page() diff --git a/README.MD b/README.MD new file mode 100644 index 0000000..9fca671 --- /dev/null +++ b/README.MD @@ -0,0 +1,608 @@ ++NGCBot +
+ +![image-20221212162417977](./README.assets/image-20221212162417977.png) + ++一个基于✨HOOK机制的微信机器人,支持🌱安全新闻定时推送【FreeBuf,先知,安全客,奇安信攻防社区】,👯后缀名查询,⚡备案查询,⚡手机号归属地查询,⚡WHOIS信息查询,🎉星座查询,⚡天气查询,🌱摸鱼日历⚡微步威胁情报查询, +🐛美女视频,⚡美女图片,👯帮助菜单。📫 支持积分功能,😄自定义程度丰富,小白也可轻松上手! +
+ + +## 目录 + +- **1、[介绍](#1、介绍)** +- **2、[项目结构](#2、项目结构)** +- **3、[使用帮助](#3、使用帮助)** +- **4、[功能介绍](#4、功能介绍)** +- **5、[配置文件说明](#5、配置文件说明)** +- **6、[后续优化计划](#6、后续优化计划)** +- **7、[后续开发计划](#7、后续开发计划)** +- **8、[更新日志](#8、更新日志)** +- **9、[特别鸣谢](#9、特别鸣谢)** + +### 1、介绍 + + NGCBot是一个基于HOOK拦截机制的微信机器人,用户高强度自定义,支持多种功能,代码逻辑清晰,因为其HOOK机制,目前仅支持Windows版本。目前支持多种功能功能调用,其功能🌱安全新闻定时推送【FreeBuf,先知,安全客,奇安信攻防社区】,👯后缀名查询,⚡备案查询,⚡手机号归属地查询,⚡WHOIS信息查询,🎉星座查询,⚡天气查询,🌱摸鱼日历⚡微步威胁情报查询,🐛美女视频,⚡美女图片,👯帮助菜单。📫 支持积分功能,😄自定义程度丰富,小白也可轻松上手! + + + +### 2、项目结构 + +``` +│ main.py -- 启动主文件,启动此文件运行 +│ README.MD -- README.MD,一个介绍说明文档 +│ requirements.txt -- 该项目所需要的所有第三方库 +│ +├─Api_Server -- API模块文件夹 +│ Api_Server_Main.py -- API模块文件 +│ +├─BotServer -- Bot收发接收处理消息文件夹【重要!】 +│ MainServer.py -- Bot运行主服务文件 【重要!】 +│ SendServer.py -- Bot收发消息处理服务文件 【重要!】 +│ +├─Cache -- 缓存文件文件夹 +│ │ Cache_Server.py -- 缓存文件处理服务文件 +│ │ +│ ├─Fish_Cache -- 摸鱼日记缓存文件夹 +│ ├─Pic_Cache -- 图片缓存文件夹 +│ └─Video_Cache -- 视频缓存文件夹 +├─Config -- Bot配置文件夹 +│ config.yaml -- Bot配置文件 【重要!】 +│ +├─Db_Server -- 数据库相关文件夹 +│ Db_Point_Server.py -- 积分相关数据库操作文件 +│ Db_User_Server.py -- 用户管理数据库操作文件 +│ +├─Output -- 消息输出模块文件夹 +│ output.py -- 消息输出模块 +│ +├─Push_Server -- 定时推送模块文件夹 +│ Push_Main_Server.py -- 定时推送操作文件 +│ +├─README.assets -- 介绍说明文档贴图文件 +│ image-20221212162417977.png -- 没啥用 +│ 关注.gif -- 没啥用 +│ +└─Recv_Msg_Dispose -- 群与好友消息处理模块文件夹 + FriendMsg_dispose.py -- 好友消息处理文件 + RoomMsg_dispose.py -- 群消息处理文件 +``` + + + +### 3、使用帮助 + +#### 3.1、第一次使用请看此处 + +**注意:此Bot只能在Windowns系统上运行!!!无法在Linux上运行安装** + +首先请克隆代码到本地,使用命令如下 + +```shell +git clone https://github.com/ngc660sec/NGCBot.git +``` + +也可直接DownLoad + +![image-20230305191526567](./README.assets/image-20230305191526567.png) + +下载后解压放在本地,再下载DLL注入器以及安装相关版本微信 + +![image-20230305191835360](./README.assets/image-20230305191835360.png) + +![image-20230305191853177](./README.assets/image-20230305191853177.png) + +下载安装后请先打开微信,并且打开注入器进行注入 + +![image-20230305192022516](./README.assets/image-20230305192022516.png) + +**注意:选择适合自己微信版本的DLL** + +注入后,直接启动main文件即可,命令如下 + +```shell +python main.py +``` + +![image-20230305192150988](./README.assets/image-20230305192150988.png) + +**出现此处显示,恭喜你🎉,机器人启动成功!不过不要高兴,你还需要进行下一步操作** + +与机器人私聊发送一条消息并且在`Config`目录下找到`config.yaml` + +找到`id2`,这是你的微信ID号,请牢记! + +![image-20230305231120081](./README.assets/image-20230305231120081.png) + +打开配置文件,将你的微信ID,填入其中! + +![image-20230305231212883](./README.assets/image-20230305231212883.png) + +这一步是添加超级管理员,如果你想知道超级管理员有什么功能,请往下看文档。 + +那么问题来了,我想添加多个超级管理员怎么办?请按我下面的格式写 + +![image-20230305231323334](./README.assets/image-20230305231323334.png) + +**恭喜你配置好了超级管理员,你已经可以完美使用此Bot了!恭喜🎉** + +**如想要深度专研,请看下文!** + + + +#### 3.2、相关BUG说明 + +##### 3.2.1、Bot的微信号必须修改,否则会报错! + +##### 3.2.2、其它Bug请提交iessus,我有时间就会回复😄 + + + +### 4、功能介绍 + +#### 4.1、娱乐功能 + +##### 4.1.1、AI对话功能介绍 + +AI目前对接的是ChatGpt API接口,其AI算法强大,使用方法请看下图 + +![image-20230305194540725](./README.assets/image-20230305194540725.png) + +**此功能可私聊使用!**使用方法请看下图 + +![image-20230305194645867](./README.assets/image-20230305194645867.png) + +**由于很多朋友反馈有关键词拦截,而且经常调用不了ai,所以接入官方接口,如果您访问不了官方api接口,请挂代理,这里可以用我的方法** + +![image-20230329095008188](./README.assets/image-20230329095008188.png) + +在配置文件中设置代理即可 + +![image-20230329095026331](./README.assets/image-20230329095026331.png) + +##### 4.1.2、美女图片功能介绍 + +图片功能,可在群内发送😍涩图,使用方法请看下图 + +![image-20230305194812168](./README.assets/image-20230305194812168.png) + +##### 4.1.3、美女视频功能介绍 + +美女视频功能,可在群类发送优质视频,使用方法请看下图 + +![image-20230305195314814](./README.assets/image-20230305195314814.png) + +若机器人发送的是如下文件,不要慌,只是接口问题而已,此问题只会偶尔出现,不用担心! + +![image-20230305195409045](./README.assets/image-20230305195409045.png) + +##### 4.1.4、备案查询功能介绍 + +此功能能查询网站备案信息,轻松获取逼站的备案主体,让社工跟进一步!使用方法请看下图 + +![image-20230305195433874](./README.assets/image-20230305195433874.png) + +##### 4.1.5、后缀名查询功能介绍 + +此功能能够查询任意后缀名,让文件不再陌生!使用方法请看下图 + +![image-20230305195530398](./README.assets/image-20230305195530398.png) + +##### 4.1.6、手机号归属地查询功能介绍 + +此功能能够查询任意手机号归属地,让你跟女神更进一步!使用方法请看下图 + +![image-20230305195628326](./README.assets/image-20230305195628326.png) + +**注意:手机号是我编的,不要尝试社工我!** + +##### 4.1.7、WHOIS查询功能介绍 + +此功能能够查询任意域名WHOIS信息,让你跟麻花腾跟进一步!使用方法请看下图 + +![image-20230305195801227](./README.assets/image-20230305195801227.png) + +##### 4.1.8、摸鱼日历功能介绍 + +此功能能够推送摸鱼日历,让您一天轻松摸鱼,把控摸鱼时长,打倒资本家!让资本家无话可说!使用方法请看下图 + +![image-20230305195945397](./README.assets/image-20230305195945397.png) + +**注意:此功能已开启定时推送,在工作日可定时推送,默认推送时间为10:00,可在配置文件中修改。如要修改配置文件,请看[配置文件说明](#5、配置文件说明)** + +##### 3.1.10、天气查询功能介绍 + +平平无奇的天气查询,轻松在群类掌握你本地的天气,让你约会更轻松!使用方法请看下图 + +![image-20230305204137643](./README.assets/image-20230305204137643.png) + +##### 3.1.11、舔狗日记功能介绍 + +舔狗的日常是怎样的?如何当一个舔狗?怎么去当舔狗?此功能让你专心学做当舔狗,让舔狗不再稀缺!使用方法请看下图 + +![image-20230305204308526](./README.assets/image-20230305204308526.png) + +##### 3.1.12、星座查询功能介绍 + +想知道你的星座运势?想明白今天该不该做什么?要知道今天适合干什么?此功能让你轻松把控星座运势,人生大事!使用方法请看下图 + +![image-20230305204511873](./README.assets/image-20230305204511873.png) + +##### 3.1.13、早安寄语功能介绍 + +一个人太孤单?早起没人说早安?想要每天的早安问候?此功能满足你的欲望!使用方法请看下图 + +![image-20230305204619914](./README.assets/image-20230305204619914.png) + +#### 4.2、积分功能 + +##### 4.2.1、微步威胁IP情报查询功能 + +你叫王大锤,是一个公司的唯一一个混子蓝队成员,某天你单位的服务器被黑客攻击了,还好公司的大牛迅速响应,实现毫秒级IP封锁,此时大佬交给你一个任务。 + +大佬:”大锤,你看看这个IP,是个跳板机还是黑客用的VPS“ + +你:”好,我看看...“ + +此时的你心中忐忑不安,因为你只是一个混子蓝队,VPS是什么,跳板机又是个什么,有什么用,你怎么会知道。于是你只能在群里求助各方大佬,突然某群的一位群友引起了你的注意,内容如下 + +![image-20230305205946424](./README.assets/image-20230305205946424.png) + +你敏锐的注意到了其中一点 —> ""是否为恶意IP:是",此时你更加确定了这台不是跳板机!此时大佬又交给了你一个任务,让你看看另一个IP,于是你也参考群友的格式来发送,但是却出现了下面的结果 + +![image-20230305214235159](./README.assets/image-20230305214235159.png) + +没有积分!怎么办!百度有用吗!我会百度吗!怎么搞积分!联系群主!对!联系群主,于是你赶紧联系了群主,让群主给你加积分,但是 + +![image-20230305214357922](./README.assets/image-20230305214357922.png) + +群主说了一句非常恐怖的话! + +![image-20230305214449681](./README.assets/image-20230305214449681.png) + +于是你只能含泪给群主转了50,让群主给你加了50积分,害,都怪自己没技术,你这样责备自己。我以后一定要好好学习,多多努力。做一名NGC660安全实验室的正式成员! + +![image-20230305220141174](./README.assets/image-20230305220141174.png) + +拥有了积分于是你又开始快乐的给大佬提交情报,又开始了新一天的混子生活。。。 + +编不下去了,目前只有这一个积分功能,其它想要加的可以提交iessus,或者自己添加即可! + +##### 4.2.2、签到功能 + +作为一名合格的超管,总不能让群员V你50才给他积分吧,所以请看签到功能 + +![image-20230305223211110](./README.assets/image-20230305223211110.png) + +但是总不能一天签到多次吧,群友嫖我积分怎么办? + +**放心,本作者有练习时长两年半的开发经验,一天只能签到一次,每日00.00清空签到表,请诸位放心🙂** + +##### 4.2.3、积分查询功能 + +什么?你居然忘记了你有多少积分?这还能忍,直接让管理员给你清零好不好!还好我贴心,给你们安排了这个功能 + +![image-20230305224247670](./README.assets/image-20230305224247670.png) + +**注意:虽然管理员使用积分功能免费,但是管理和超管还是有积分的** + +##### 4.2.4、赠送积分功能 + +普通群友使用此功能,可赠送对方积分🙂,管理员就可以不用,直接增加积分即可 + +![image-20230306101314509](./README.assets/image-20230306101314509.png) + +#### 4.3、定时推送功能 + +**注意:定时推送功能只有管理员或者超管开启才能使用** + +##### 4.3.1、开启推送服务 + +作为一名合格的管理员,当然要学会如何去开启推送服务,下面我来教你 + +![image-20230305222312844](./README.assets/image-20230305222312844.png) + +##### 4.3.2、关闭推送服务 + +作为一名拥有高情商的管理员,当然要去学会如何关闭推送服务,下面我来教你 + +![image-20230305222435542](./README.assets/image-20230305222435542.png) + +**什么?!你居然忘记了这个群有没有开服务?你真不是一个合格的管理员,还好有我在🙂** + +##### 4.3.3、查看推送服务 + +![image-20230305222721764](./README.assets/image-20230305222721764.png) + +**你都知道有推送服务了,推送服务能推送啥你不会不知道把!不会吧不会吧!** + +##### 4.3.4、推送服务介绍 + +推送服务,可在**工作日**定时推送**早报,晚报,摸鱼日历,下班提醒**。仅仅如此,如果你想定时推送比较哇塞的涩图,其实也不是不可以🙂 + +#### 4.4、超级管理员功能 + +**首先需要知道在本bot中,一共有三个权限,每个权限的功能都是不一样的,接下来我会逐一讲解每个权限的独有的功能,至于那些普通的通用的功能,基本上群友能用,管理、超管、都能用** + +##### 4.4.1、添加管理员 + +想要管理群聊更加轻松?让小弟帮你管理,输入这条命令让你的小弟变成管理员! + +![image-20230305223648781](./README.assets/image-20230305223648781.png) + +##### 4.4.2、删除管理 + +什么?你的小弟叛变了?!看来他这管理员的特权是不想要了,让我们把小弟的管理权限给踢掉! + +![image-20230305223804540](./README.assets/image-20230305223804540.png) + +**不同地方的小弟,只能负责他们所对应的区域。说人话就是【每个群的管理是不通用的】** + +什么?!你不知道你小弟有没有管理权限?!那不好意思,本作者没有写查看管理的功能,不过当你再次添加的时候,会有如下变化 + +![image-20230305223951890](./README.assets/image-20230305223951890.png) + +##### 4.4.3、机器人消息转发 + +**前女友给Bot发消息要复合你不知道?没有关系,有了这个功能再也不怕前女友给Bot发的消息收不到了!** + +![image-20230306085542234](./README.assets/image-20230306085542234.png) + +**注意:如果发起会话的是超级管理员,那么消息将不会转发,转发消息的接收者为超级管理员** + +##### 4.4.4、消息转发给好友 + +什么?你没有加你前女友,那你前女友怎么会有你Bot的微信?没关系,我也是经历过的人,我都懂,所以我贴心的撰写了消息转发给好友的功能 + +![image-20230306090537764](./README.assets/image-20230306090537764.png) + +![image-20230306090547336](./README.assets/image-20230306090547336.png) + +你说你女朋友是个非主流,喜欢用杀马特文?那不好意思,可能你的消息转发不了给你女朋友了! + +**注意:在最新的测试中,有些颜表情当名字的也可以转发,不过请复制对方名字!** + +##### 4.4.5、清除缓存功能 + +当你的群友调用了很多图片或者视频或者摸鱼日历功能,就会产生许多缓存,此时可以输入此条命令,会将缓存清空 + +![image-20230306101820442](./README.assets/image-20230306101820442.png) + + + +#### 4.5、管理员功能 + +##### 4.5.1、管理员功能介绍 + +作为一名合格的小弟,一定要知道管理员到底有个啥用,这样才能更好的帮助老大去进行管理,管理好了,步步高升。管理不好,可能小命不保🙂 + +作为一个合格的管理员,你掌握的功能有这么一些: + + 1、早报推送【手动早报推送】 + + 2、晚报推送【手动晚报推送】 + + 3、开启推送服务 + + 4、关闭推送服务 + + 5、拉黑群聊 + + 6、解除拉黑 + + 7、用户积分操作 + +但是你知道这些有啥用吗,别担心,听我慢慢讲述,故事还长,洗耳恭听! + +##### 4.5.2、早报推送 + +**注意:早报可以定时推送,也可以手动推送!** + +平平无奇的功能,无非就是爬虫了,我没有要讲述的亮点,而且也编不下去了,直接上图! + +![image-20230305224907222](./README.assets/image-20230305224907222.png) + +今天看来没有文章啊,可惜可惜🙂 + +##### 4.5.3、晚报推送 + +其实一样的,不过你是不是好奇怎么触发的,在下面[配置文件说明](#配置文件说明)一章中我们会讲到 + +![image-20230305225059253](./README.assets/image-20230305225059253.png) + +看来晚报文章不少啊,爬取的一些社区相信你们也是知道的,我就不介绍了,手都敲麻了... + +##### 4.5.4、开启推送服务 + +我讲过,没看到请[自行跳转](#4.3、定时推送功能) + +##### 4.5.5、关闭推送服务 + +我也讲过,没看到也[自行跳转](#4.3.2、关闭推送服务)去 + +##### 4.5.6、拉黑群聊功能 + +遇到傻逼天天发送图片、视频,消耗你服务器资源?这种人最可恨了,玛德!所以我在这里提供了拉黑群聊功能,让此群聊不能使用**娱乐功能** + +![image-20230305225932950](./README.assets/image-20230305225932950.png) + +**注意:即使拉黑了群聊,管理员以及超管仍然能够使用娱乐功能!** + +##### 4.5.7 、解除拉黑功能 + +啊?群主把傻逼踢了?求你解除拉黑?因为他要V你50?OK!马上解除! + +![image-20230305230219618](./README.assets/image-20230305230219618.png) + +##### 4.5.8、用户积分操作 + +既然你是小弟了,肯定是会收点保护费的,既然收了,那不得给人家加积分啊,什么?你不知道加积分? + +![image-20230305230345964](./README.assets/image-20230305230345964.png) + +什么?他骂你大傻逼? + +![image-20230305230427577](./README.assets/image-20230305230427577.png) + +#### 4.6、普通群友功能 + +**作为一名遵纪守法的好公民,当然是要正常使用Bot的相关功能,所以普通群友没有什么奇奇怪怪的操作,你不会用的话,请回复help,超级Bot就会马上帮助你!** + +##### 4.6、帮助功能 + +![image-20230305230611246](./README.assets/image-20230305230611246.png) + +什么?看不懂功能怎么用?本作者自有办法! + +回复help+相应编号即可! + +![image-20230305230716625](./README.assets/image-20230305230716625.png) + +**注意:私聊无法获取帮助信息,必须在群内发送HELP!** + +### 5、配置文件说明 + +##### 5.1、机器人服务配置 + +![image-20230305231636005](./README.assets/image-20230305231636005.png) + +此处别乱来,改了就GG + +##### 5.2、超级管理员配置 + +![image-20230305231701746](./README.assets/image-20230305231701746.png) + +添加超级管理员的地方,如果不会,[从头](#1、介绍)开始看文档! + +##### 5.3、关键词配置 + +![image-20230305231820468](./README.assets/image-20230305231820468.png) + +这个比较多,都是相应功能触发的关键词,比如说美女图片,如果你再多添加一个关键词,就可以换个关键词触发!来试试 + +![image-20230305231930846](./README.assets/image-20230305231930846.png) + +**注意:修改配置文件后需要重启Bot** + +其它的类似,相信自己!一定能调教好此Bot! + +##### 5.4、API接口服务配置 + +此处只需要获取相应的KEY + +打开此[网址](https://www.tianapi.com/source/e81d05c8b1) + +注册登录后,获取你的appid、key、appsecret + +![image-20230306091138533](./README.assets/image-20230306091138533.png) + +你要用什么功能,就开通什么接口,不过目前只接入了配置文件中的接口,相关接口请查看配置文件此处**【注意,此处展示的天气查询接口是旧版接口,如果你想使用这个旧版接口,那就不需要开启天气预报接口服务,直接打开如下网址】** + +![image-20230306091344261](./README.assets/image-20230306091344261.png) + +开通这些接口即可 + +![image-20230306091443104](./README.assets/image-20230306091443104.png) + +在此处搜索接口名称开通 + +![image-20230306091506855](./README.assets/image-20230306091506855.png) + +开通后你会获得一些配置,在此处查看 + +![image-20230306091542863](./README.assets/image-20230306091542863.png) + +这是你的Key,复制,粘贴到配置文件中 + +![image-20230306091621220](./README.assets/image-20230306091621220.png) + +**Appid与Appsecret,这两个是用于旧版天气查询接口,因为这个接口是免费的,所以采用了此API接口,如果你不想用,请自行修改天气查询的相关调用代码,如果你仍用旧版天气查询接口,请看此处** + +打开此[网站](https://www.tianqiapi.com/) + +![image-20230306092504776](./README.assets/image-20230306092504776.png) + +在此处将两个相关参数放到配置文件中即可! + +**微步Key:**这玩意自己去申请,我相信你混子蓝队的水准是能够自己申请的,申请key之后放入配置文件中即可 + +![image-20230306092645321](./README.assets/image-20230306092645321.png) + +**图片API和视频API:**这两玩意别乱改,调用的话是随机调用的,不会出现一直调用同一个接口的情况 + +##### 5.5、积分功能配置 + +很简单的配置,一张图概述 + +![image-20230306093225214](./README.assets/image-20230306093225214.png) + +赠送积分说明请[点击此处](#4.2.4、赠送积分功能) + +##### 5.6、定时推送配置 + +![image-20230306101510700](./README.assets/image-20230306101510700.png) + +**注意:如果是在早上8点,请输入`08:00`!注意格式!** + +##### 5.7、系统相关配置 + +![image-20230306101556676](./README.assets/image-20230306101556676.png) + +版权信息处,为使用各个功能时结尾处的信息 + +![image-20230306101933687](./README.assets/image-20230306101933687.png) + +其它的就不介绍了 + +### 6、后续优化计划 + +```shell +1. 优化群消息处理【已优化】 +2. 优化相关配置信息【已优化】 +3. 优化积分模块【已优化,可@多人加积分】 +4. 优化好友消息处理【已优化】 +5. 优化总体架构 +6. 好友消息转发【已优化】 +7. 优化多线程消息处理【已优化】 +``` + +### 7、后续开发计划 + +``` +- Github工具 + CVE 实时推送【连接不上外国站,已阉割】 +- MD5解密【暂时没钱】 +- 开发Web端管理系统 +- ... ... +``` + +### 8、更新日志 + +``` +- 【2022.12.8】 推送Bot 1.0版本,为初始版本 +- 【2022.12.17】推送Bot 1.2版本,新增部分接口,重写部分代码,新增积分功能 +- 【2023.1.1】 推送Bot 1.3版本,重写部分代码,优化代码逻辑,优化积分功能,优化定时推送功能 +- 【2023.3.6】 推送Bot 1.4版本,总体代码优化,优化定时推送,优化积分功能,新增消息转发,维护API服务调用 +- 【2023.3.29】 推送Bot 1.4.1版本,增加多线程处理消息,重写AI接口。可能会出现消息串群,@错人的问题,等后续优化更新 +- 【2023.3.31】 推送Bot 18诞辰版,修复1.4.1版本,消息乱串问题,支持AI上下文检索,优化消息处理代码,实现功能分区分块处理,由于挂了代理之后,当调用ai对话接口时,会出现ERROR报错,这种问题是正常的,能弄到国外服务器就别用国内的 +``` + +#### 最后,若在使用过程中有任何问题,也提交Iessus,或者关注微信公众号,后台回复消息 + +![关注](./README.assets/关注.gif) + +### 9、特别鸣谢 + +- https://github.com/cixingguangming55555/wechat-bot + +- https://github.com/zhizhuoshuma/WechatBot +- https://github.com/tom-snow/wechat-windows-versions diff --git a/README.assets/image-20221212162417977.png b/README.assets/image-20221212162417977.png new file mode 100644 index 0000000000000000000000000000000000000000..eef3614042306d718554e33d50b014ce98bad308 GIT binary patch literal 135011 zcmeFZ`9D|d`#tWQ6qQh#O-ZFlgk*LQDn*6NBtkM~o=YStLdYBvndf;36*7fno=Rk% z=jpq)^L~Fm=TG?l@bP#(&N;90+|Rx5`?}V(*1E2Hd)|+ + + + + + ++;T4Uu`ELIYx5jl9+;R*jTq+k?ijkz772bqZ~ViXq~fPUoly5(X&eO z3HZtlU4F9T#I8Mm6r%Z$G_laf-nu#R?Opsk@{`xUhB1ljb*8m0`pmBil{)3zH{~s? zw=3QJGcWX@d$c4qdts(wUR}fH1o4&s@8|!m!2er;|F;7Fzh8lWZhx+<3}RJejf=s( zdNcFJZ6hIhbe5L$&OeE_lOGBYh)P1TVGwfBwC96-+^0{UD&+79A3A<6a@QSuj)rY~ z-1?0r9l_aJZlx6U<$A$Mp8HKF)~DUv+$tFy&XT*%kG2M}DV>We$E8U~`l!yHwdg6d zncUo%+`OHvdSRdEyLazi& M3LNE5Z zY_4+@&)u4GIdwhcb@N~U{*K25z1lZ@y|J;e?u;g->kDDtl*IQu5=}aK!O*C;M3}EO zO2VTf$2hdP@h=jR%0ofT_}53@^_d!u-!(z0@^P|Hf1N*n{@&Nu?P=N-GrwzMq-7l} ziEEIoX)C5`u8*dciWL1V@H!-9rQ< z3sYMHCvfXT;o#jIkbTJMX-< zl%SY6Xj$~{r}y$a_+j+>Yd~e42qT}_u?rWhT2nM8OIA(lLM~Vc7@{b}EQJwwyK%iH zkX_4h`txzg=XBF!xZfVTp>X%zhf-9t4e`qlek8SL-EYR9Q_}I7h5RDLUdfaSk&rk` zpDUUwzZoNSl7+>zBkTTnN4DttT(ghphH{E}zQbhkMY=TO&K$kE5U2I|R(@I*-^(XN zL`2S=J9pv2g}7~42ojPix6FEh#D~AXo;h=7IQQVSD_4RqnrgOL>wJEGAm3uBzKq=S z((#=;cV6r=tB_`Nnj4Ob>@lyTudc4 ZVKc-{5{fB zyedH@%yZ}CBkl$v?tPwJ&a30Yq2{7fAGh(-GynUstFp418M;@fNf*0qv}7x1wDbq6 zd>hqgv4$yy69w51THnk4ld4geV>#T!Pk)N|&_4$y9n95jx~;^hEyp`9=>J+al}}d9 z?sZ;eCwI-t%Hr%UGVLvyl9; Q9LEfLfn_WxoGdBSt@h)m|LpRi$ZqzWMgfn+7k;PNm&eGZyy=3Q zwYLIxvnTsetpPUl=^omV?;re|AFnNre2c1x5O(msy& y2Lv! Nm)U3ih)btu9i iS6sihl7ugA$o2=U8B2>D)qC+@yW z;weQir*`@Ei`{dpsm{X0M>L)0TmH~=rfEy`m%Gd9mD8H!^^fQFp2O3)AJXJ=T$&nN z-}CnF=jRAT?@PxUpAXg=w59b=bQeB3)|PLfPffb}&^e0UEtGsz`!!N@({=l9E=&JE zow<`IgVcdArI&naBZLKAHrDv*$0j8Ks; 5Nb5n6^EZo|myvDA_ zlr#0N8yb#SwQ2-um`fe^U6~*C!K!68h>Ug@+90DdEXSw%D`Gf4_7pp?$VE6-pA5Pz zv4yL_R)7rmBVUjM_?g0*h61z0nAp1G |E#%?SE2Sjmn+XaZj3}~!J6bO5R@9_Q>gJMx=U#Yu(OxuBpX+|Mkk5esXwSHp zd0w1Y?mp4mSbnY(M`F0>kXct{$yN)gX=y{FS}ij5>Iy7}AKuMuNl+a95Er4EWzhI| z8(H~~konI~0R6FNJcy2;zkUG{Cjhb~02`G8v#M82+76>r7nIZPh~=7eBYG6m?v#x^ zA2&l#0I1_koVUI>%djz8@)bXJ+Mf8*S7BjYNS3+v*E)CaW^{BPe}X(Zt5Gob%SUwL zM{?)0z05~>jcl8=H(U_)w{G3auvaX!9>+Q@F8%Vc8E*O=prpCewGa1FlkzH>YanFy zcTH<1WWu$Qtrg}^LUIFL<@NS`qY_;+! DrPl4rl`E;m z3#!=c 4zsjciZ&Rla_6x5;B|qe5fH()CCpd zv)3Wf;GMsI{=_rB5ww}0Ad3n9gYZENtmy{w9eF}VRnq^IHp8g{1 e6-_`#5 zOH=*f0#?YqnN}^AZY)xjwlSNm55O|uoW_bmtz*TWi|<_g)>jur4 o1!#_w z6TQ1&2kdRtu+fkuTN1n#e X_I;#cmEQDnjja6f z_^8(*4sVg#05+u_iw0r*OSbneEpgqY3O41mzEIPW;HdF01J(YCiiv^EQR|u~SXdUv z+ARQ!M;^{0uuvpMJ}95pDsj|unpe#G3Y_`kO|OOko7 @#eAizwd3(4i*0gXu2Q0^^>D?7Jr4$O+ zzK@T$m9EXyY^<*m*GN*y9Md>=>eOI*CBtrujMatlP_BDl*>k&YrQiMh=TTc*+ZF#) zit#s?99o5qQNA;ED!q}t!VU|hVrlvf5vvo0si-ms*lwRyPJ3%ZMcmCs^Xt<$e- 2{_`O%A|J*=gcR_QSW@EgKn#`;6*8|+ z`)dE=x$ord6m<{{8$b>6F LUBvI X6un1T7Yu|lzOqf70QH#z&YY3 z`V_8Sy}BY{W5pK&p2wDwcbuP}UrjGjt8}C637On|t--v!yeSEMK+9n~8;R(q_3PI! z`CGTH2C!Pqw Ge0-dp<{G74)}|{rt0IMo zuY0uVXg4#UQeb(%*nU3aUJi;;!<+Lls738uo#u)kZk_D#i$);;s;!xpu3Jy_l^OI7 zAW72{KHM7S@bvUNgJ&AvIXR%1pl~0X#ZSLgvp??qfW!jg`pbzN@w{~R?p-YC%us_% zdFIKJCu2=39YauICr1)fHG#};oO-~A_=m#$wXfZ})dXJSUt%te%Hh)RGC(HiTzE4R zakC_w+cVC~sm5Ln2xM2&v~Itr^X0`(*E^j##>DL*Bs5*t=SNx+rH}g#3=9}#dR;!@ zhcrd*+-n_)`~L2Vf4Sf;*S>{LQ&Go{;1NBxzh2@m4{&HY{z0{TO2PkFG3)czH*Lh| z>ik^*1}nNYB{4PmBT1zh+{x@?@8-rL0Ul%0>KVG#MCnJI4eMih+12yRR+yJ(hd`^W zy_IMPDDY>IJEv8$)>rlfm#IL2>K>kIaM|D{ceR@xOh$?f)rVV+{t#Yp1JKl GG4##gDk5k1k$IOG}6dqp-9~p8K!?QzcHT II63wr)n0Xh#Zh2 zREOxd1<$QJWklWkY*Mm32o2Jbq9Ir~cvdmtyEnaHQ&W@cv9?l|P02$VuQ$e!LeZWb z=XJi&IWKQNq@IQ}3+C1b**EVgvO_{Y_#S`L`m^<2)D-W)_gK{h!O5abm(7i1 QcPE&`D@0&OXjPP5@x~#SUB`Zc=du*Jso*6Bif1diAU4 zb`@adcxNt|SQ_s6{q@j@wi8>?rb10a(~Jy|^g76{lJV|AYfAftactn?$8A(@Co4`~ zeSMhMNVzR78ge7japnq7@6l6+C|X0M>*J=wHm6=(I!?nP`+`ypPy&9E$(AD@hqwf+ z0^Cb}K4*JM#kf(vn xxplf_;a7pRgLX=Ex_^S6A> zVtZW3D}@XE%6@+2-bTsRjmoN{aDbUc0F!~SM!{*|Si=tjD+V5JtS#f#st;+}_M`6X z>O9Gzo=5EII#3Zb$WpEpU?sFX;NPfuFzWmF@6*33Bkcx*+xKZIV81iXrHN%XZcB?o z()G6@(sgoGO91#6O}gG_SA0Pk-dt{Q5$QOYTCy@i#^?xELQn%xEy0OAW(bW+Z&uta zu`_=fW58;Ukd{HFEe`n05u(Z?w`T|O2vyFN*YE0~K6h*vGpnSx(dE;8ci9&=V~V@L z3TQvH?k#a*bet-4+estk!_XZg?L*<-RQXEOWdjtH!J!sxJR^N}{)m3q`G*5lwU)a< zN|q}{H}>Ydl+~grL2)T|STyO#8qVo75&S9iFGc}CKxl=KoWFy#B9^BNzsINJd7)JF z`UAI?cEjxX{xcb7{pBF9q7~C987Lw7AV!D_6Y#M%j-PE-pQJ57MvJj5hzau2z%%mi z6%%hCnQ6tv*KiN!cXQ41;3Z&J?&%F6tERxf2<(BNCLu@8`z(V7-D0t^MLMu>pW zkFe8wDojN~Lleq#e+C*<%HqebN}q;Xws 00A^!2tCg8^@>Pr8Wq+Y)u)7&)(T$9~eXrdXMZluB@RMghw| z%sxUhfPZ)feE@tEjag(NcxfRL>)xp`itK8Z$(7O6-eLzEAhHkdTWDI)hkWGsp>yho zum7`&PXhw$dM?X;G0yD<#ARPTaqIoHU a2 zj 9?H8Pwdr6?81bS5;0V-z>77w&``cn6z2EGLk4) z!TTgg%Xt~p<|vPWS;8TKl&=A7a$$VaAYsGuwQRSOE|+d _Ww(>hZJ*92Zo)HTKio3g$xZ^&B``rarZ^Vt;Gk(85Bkx@tst&>+ z!%@tG+*ire3y-#iA>{x;>wX{)6m|8d7YDov{~Z3&vCM{&!H@?){W(CPTR{D2Yicjz z2ssE7j={S+fUQ+dbbUW>2Dxir 1GG*t6$;0WO%;lAL 4e|+-#?Xj6t@PAk(6y!54m{>r^5 tv<1YBS_aqHE#<(dEb y> z=jT6F@U>u)qAP!5u&gY#LJPyqLkdX&cEhW-`_J4OSe-2G&AKm-D{aojiCzFfgQL~| z2zN8tl|Ng}5}Bl&{u6MC%+!KPn449%sJqZ;X^2`{Ap^{!VQ5FahJ_##z#5=E&pCRO zk avw3~4yAkRSc_Wa%Yskf6K@Gqi z2e*E`<5+rSfz4z@$;*eQ6mjFg%JlSfb8~YRM$K#2YCFMCKhen1IVS)@pvt^+Gzi?} zc}&Q4K;ATHv3JjRSRP*c&p;lF02V3eW4ZH?{vH%Z{Gy_ytv`V3Qj==0Kg19AcXfXB zkV*Oy2tQrnp-S*mZjfa_6QL;JF!h@}Zl3^!-Pl;2^g6(1&3cNAh*8-n_=?VO-Wrqs zAP6=#7EAR-ZmL1F9%NU&R61Dg&%#~UL*I(a7NJ`7^^W(Jy5Ne)2NZI6K=tXJir}z_ zRCTKsMF6}L>KHPx6-5E766R>+ds+9}TQO;{P^<&RO-Y$JhlR1_eh)_CYZ23wuE)ks zuH&|}u%D66E5ihDZO_yfo&PTH(Vzm%L{-_bRRG_KYC^@(bY4CG1Zd{&vRKLJ;)r5i z6T}gTtwRoiw-FYscDb8Q(!Pykd=tOzR3ErC`|^4PAm~gOBk|Zv%x7-Bt6G}=1=m8r zVzBlYc}l@Z!WtMcE;XCX0Mj@N%nU*v6@)-&;73j1S=}c)_LN^THUNufMBRXiw0{Rt zTVz9chVypAp0lkF x+C$=0 zr2HWC2uLj|eYwCNRGn_i=Ib6>${=z56&@U5Sz4vejZ#N|TAM4arLubRB`BJR ;zHC-d=DfDm>H`^fh9a=Xf1DHMR44&f$R?DzI00J>RskK#0rqeddzh{ z_hIAb&;RG~-}oHB6)<`aMIxg>9cu~`y%PlgoO0T+Yufr&Pb*HII<+yXwFyxIjX*}l zK;e1^PfC>$@Q^31dWTx67VZD5?gL^fF!6T-|wHWCt3Rt7^&Mc~H6t=J*P ziEK Rt5-J=gWyJ&B1fJdU}L?Vba+dGMk`LStVwMl?B>p^foJVt z^1;op!M7li;`AFKJS}S9oFAsWhvGT(l-7i=`aUQ!w^3UfV*2-%i277niArb=rv)@- zPmQw#VP2r~0GrXfk^O|x>zfEM5c$~m@1;_mR)*vJ{r%;*>fgM1gJ{sOY?MSg-sc-f zUcnp*34S2=UZ%S4Vs8 48fAjNe(8mA%9WIF}7 zDxq#ExLE1@4>eTdsuYIG;F}PnHRlS)3Pw{LU|&5swuGGnGlOq)dw#rgQTII@fQB?5 zt;c_FN1KOeAOOnT&$J@&oC&`RbO_m}088*!y$T4yXiDKZc6Rn6n6>?l^1+kV;bOp3 zXsl&aCuy3pR!s+E+mY9S6f7rB6xAi}u>IdzfZ2M%{^xKu#!zSU1a2@^yu8?x2Zogv zJ{P$$7Y8qx$^ZUbZK|MI-$y`YgzYmTdH$g^gnbIYEI+slTgqRT80j#PFGk%3(-uoA z01uB)9_*{s|C%aklm!rfsMvF_m?Xeu=PIZ~lhkrkK-pA63_m{lON{yjgYZg>kH`u{ zFIX3~=cSp!I=CjS5oQ-GAzwjJi4Z+T4iTMe&=?JP)yrGdVTpmGk{R+BK@a8S#!(!O}cFBF0}HPbn$g&I#k| 7g>XM{OJgn@LoOx- z!|w_IW2OU^$p!>eebYk}dIBR+v2FwTVZ&_x=5VRDuiAW%Zjkgf*Tp}LI#|BSyeW{U z2vHa3wyTAxwM1cR)GQ)+20QyWR%5a;rqYX}gzxUBXa0_oIyySdm0_=6TY}>1Z=`SG zg^E8A2f41-`o&(w4@!Zc*%xBp`1uhm9I}1&*&)=#IkW^|8b|`Jxx-l1^QJ%@!(+2i zEUe(7KFzM~cd3~B4RmrnhW3t1pq|&I<8Z9CwSGbB03oI2+pre|LjzaVgfotg55&`Q z+bp3s;VOPzD?pnKPwF$MIogtld@4-Z;X(nn)<1g%tuol>s9Qeg^N`YF)J4b{W@+y= za1w%UY#4#K1MWc*o#E-Es$`xr3fo6ohRCy=QS3x$79b0uov<-?b#$a(9-aR}YZ|7@ zwvX8LK4ol{#k;*_J5%`pBge>l(5^!%MA^+YY(Z?ECU?Eq>p1)D5O~3m+aY7*z>6c? zSg3id{?qHRK(Y?h3{b}$!xmPF)!~>k&PU@N}??rvlV*wODFW|0A4`ghRs z1YTUfa)tlPiS4`gD`S^oCWnIt(G&( XZC|! z#JpKOON8z53bjVuKd1`TVa`4Xs8RU3SmuK}IKjp6fENwEy@i^`^PjyLeJQ8cG?0Rh zk8?#D9p)abjhP^W02_1&Jw2~nyC#G!X0#jdood+s h{m1eMe2O$8jbF#N|bFa(O53ta2V^S{; z`@VYh4Rr=QJokR{DH$fKv9@$v5!JBxPqbGw8SaN>u%tpi&v|`i`|g9+-f;GxU`qn; zYtrwOD@Ccf)LZ)vIByM^DOuB0k+AIF>qun#BhfeT6GT?WGPjXY3Izw2xZKblJkP`w zg>6+~-NA9|-Q_G}$sHcK2kNDWh5-~y4wjUobd3v;3Q|Mq!+4(XDOa9QiBtt~Xw6*) zCaPr{2E2Mj7*KNrU4g+h-gs)Kbl1UDX&(mkL}Z?lT2hn3O&-U?X6>{>c4dz0Wxm6o zpd-_Q=36^L2_e2P*}G}L%#lCfhhpRAltQy_AL!9RlRIz3utO1$2>=wy Y2ZXR5{PIB_PZZ?8iyswvYq}(_zCU$k=@V|_}h9wNt)W{D~7!#>j%)> zoS6+vLnoT8LevebVboo4p$ep0UG{m~H42)dH+SW})AWq~|s5q0@WDVE-p7%zKmv z_Q4H!*fB@i2yZVWm4e2*G9WCBZv2uj6Vob6&mr_}9JWzPz;vI&QnjI2qLXw^EoTgx zcCGIT)l*7(dvrq>rxSo7+r@~a;WSo-5XVZCAff^Eb(|Ks13lG$h$<}6;ROG0*yipv z2nbADsG=gHLjcP%Y(PW58q!Z5N^43Mc;*7noZeU$0naUOq7DHLTM&!{&I|#mC|$%h z=tbPZHegjdVC_J7!F)0xM70MMRb~nbFU1g~9%4=dWzYJa4&j6X^mS_#SoGiyNUsT> zdII2zEZ7vqT9G{J^&-Lzjd@%es_ZI{T>(N$hiCtW+%2HB%lfDm9NbNKl8xABv>m~| z|Gg*33Tb(_%6QL-#u(|9k@~rve~yuZ3s|E}GurqieT8QO(FFAc4}g(WtgWjL?2ZH7 z-My8)4{O-VHnXjqM?S=rPlGlH^jlZ7Br5g7ZC@#5;MTh~XM{hEa9SY5+56boJ@f)| zQ?zoxkk!BSp~ h@s##qyKO?jw$XpSb0qEjlclOW4N< zy{WEmK=uZq+1fDg37%oxE^-6AxiwUPd!X`xQNL)@WiE}d+p-gG@v$;7^`nW;Z`SwE z8w0Pw4lc*b*RMN)dt{`uKZJIv^kHNikEcz^0F}bXz^XwIAc?smatXVQhu3e;JyGvx z7F3E3Bk*DkMN*7<4bE%hK}N~v`}1KifJagRyD-1O;A{tuSNY-yjq#lC5n=!dCfX@7 zXyS7arO0UCB>fbhae$-~41Uyy;NY(H<)O$@2!utf6Yf(5Yf9AZPugHLK&`s8o4SK1 zPn^I_{qmJ&$j7g-$cE*iRRT>^JXOBC;^`g-ShA+Y^FPiC5Bb{wx99~|=@=q0P&WL6 z7_xXocLNu#ppn?>bh5=s)X2ie5h)tuVLgAIXvj2ra6;1~&533wE!rsXh4B9fx>;dk z1r5>$D)@$})$xUW18#{Z2Wr+HGFPt(01b&|E=234AuaIYeqjm3ZVL=(M5Kp%(@pHo z5llV+!I3Yr#~#7^C%mNh? G)71KcmE_nwsY z!9swacK5`2`+rzYo(zY(3}0&=R19} (f1au)?6i T0lvXl>G_2b(T?XMDRnhDMS%&7dJT)mCNWRDQ zR9IZ1r3I24Ee+hqTwT&$W+^u>;Iz^RZ*~xj5RD&G+qg)V4LfM94ErZ6XV7NB+IU$jQ1yDC z8b?gI;YQ+NmF|^0aQa3cl(2e^kqUB-7@OKiT?5k%8|)a0hhBDuXpycFF-0Zm2qKy} zk2cL*HphABVUDT1o5VR1qg*tkWja2>KM G+Q%Bz?O;`@n}6Ob=e~W6XrkfH;aDt96cUM=- b~p1RnR@hDN(e_%r?*@ 2}d 1m+pyhcHm%u7?t{kc1qJWB$5hxRcUyvPXESbk}cuk)+oZs6)ax)UA32 zaP}Ct7X{CrDhFhPj*p=wK8v349J-yCY?x6PkqEox7vM1^Ll)YaA^Hd#t&Ixy7m%SL zTyW8?uqy&J6Vdl_YlIR4gqSh)=1YQ`MDUcXSt&fNPhP(!gg!)fMfg5o@Zxyqp}HO5 z2}Wy6(|`nl{%~qT=>9qoe%w^_k%({80T!EBYO2$TN)e= Ggw|xb3BOv^uzZEo|ecxlU=uQ29^2qck zC%nnA_8*6>Z7tehH_)ZOo1;p=@COtto t$ofY zYQ)$BU?2>)izO@nZj7h~$DA)CV{}U_g6txOQnC#d@x`$FAYXUyKmDqjjOg?c5sqag z90bt*iq}8)rY7Ht{R-aU8_bU)a5(324_pd7JRu8^X5meL_Y)Ht4mZYNS7FhSu#}l2 zzLy73s0bSw#(X+L1kUx+Gj0WV kKn(sUN~M9Cwj%X(iSH&9mSb4f*jaMD7~Es9E#rjC$YnE46g)|Z{T?86}Z9PMx< z8A#xl&!15-DNxB!+@#mUep(W4D6oV;SSVe9dHA5ocOuTGHc`gD_S{8DNyOYEVy ZC}WP6oQx&$!fqj0x5bH(~*P~NX&n9(4Q>wg(JNVRoQQZXSjCxG8~XE==Kop z*0Xii>L~u&S01i7e}V&vTnXpY7PrhSBf2qXM^jJ&QP91W4|L+L36pG!fij99>{GnQ zSeRwHZ=z`k57S{O#pg&El1IfUmSC$O()c7+=A9^ #r4jKX8m?6-gP81Vp0Y`9o!rjA;n%R6p zHMryuaOt=&VlmgKolCWu9_CDzr3)|uWjDs^_f7OFd`?-zXC>yT{;tdBCK^&$!ocb> zECx|QxK*5q7{8XILuq5v1-BhvO)2UCQOt1uuXPNj?&r`H^7kbSu#YY=*e^0MP9zN) zA^NKWu&s!m9_C_^U5Hmjw2onyP8w+V$YscU1Swj;3@jdvlm89oeTg2s&KmmoM7;p% z=`pL=v*##}?Nk|A#craJhFD--D}tJV6GLzOlmMb0GX7#rPwN298t)Fk+#GA?hS7>x za_@G_RKlFsZFFr(%P{C#(J@OfM8q3>6$iMC7$haeRL@?AU47X4qIU-x;TZoZ-y#tj z;1e_2$f6Yt-3czdf#Sg;DZ*&Ws5vJHegPjg1dI}kgBdj)b)w90lq?;0Ob7ZSt(~%_ z;Q+0}) UvSZ@?x(!=tTDm^ pzq5 zn+}~14D0$>^U|s{0}nQu (#`wah0l;t9KHcz3#m$%TV@iNWzxO!?B!E#jHC%y>ro=iLO^=qlA_1Sg*X6| z5CBtIoFEq|GVJf!IQJ75Av#lpv4EYF5f#Crpm)fK!U1vpCNR(%8i&zg1CBOI;TYl* zEJuu59nB0D3!mUdbh+miGw>X+l=6{jEK{~ #5mlbus!7YMa79LDH(L@w z#@??H^B;HNY=}``a?pYmhw}lyF>br=Lj#scjL<2i_JFT>ua1htApo64MPEUc *i+I!!da(ju$VlQAoco*glAuv2(z#%HNvA0wkE_L|~AILMeSFvKTRp-vM- z1}-AePHCPT_<^KO8k~84kX- VP?Tf^$j1Z>fxG YJ$X#yv&EWQAbIKHMeI@rlW95~D^rTmIBDl9*Cp2!9>tW2BXi2-{m?4Vz7M zl;IT+XhRyfMW01;u=5^PN!k-usFS%}4`nc |L*RfS2}reAqbUlZRZ)fnFGWT4 zX;jNc3BiyyxhUk72E<#t+MNh7L^|4UR`B$&@NO+%k?q3$?Hh~^HVlX4S2wBFYE`c{ zmvD3h>}R@q(l|p`#`pEf{=kzbS7o2A4j#;?Ir`k~a+gZx$>+CaLQl%58uZ4EE>_O3 zNAx 0 0d#QdCDzxds|KjHj<2B<1M#In}Vq&R^FwY4d;u*3n$ z l6#)T(k Bdjd-v|d#f!P#vRl*91P-Exl 8vgUIw!-FOKMXW##29 zVU7cpeJU4Y@o1WtwN!)8=3|;zB{VUS&71dBXJlq(=I?=lCG`w+jgim7wo@cbCuTaL zIa^#<1CBB TZrprP?EDk@r>?6na% 36qYx`uel&av9k~MP6UIAS${^oZ65LYeVUQ z^2ES^#JPEhRO8zpKY#ulDP(s`RrTn;eSSefN}8G}c-W KVFI{`oL-M@9@MD!0*54k1b)6 @5mK%D_3PJ! z4 UIGc$(5&%C{5wY8aS{#t>M5%N()17Ui7)n?7`=1t$w(9pgH z4UCDh513wk0GqJV>;7c$u9s4s={jU8=Tkp?SjO?2{rmS{Dx~<&+FYvs@nZpCj>40V zG(!rz(Y+LpGOL`~N2+aVIyo}JTKfjtE4k+DKp$qQ+S}V9? 3g4TXX$~FAO-t+Z7h988U=0|OehUOy_4=T|8ZMigoqe*i*uC-&C&ivU zk{6DOQSaXs3t2qSiWsk{tE;Q727l@|kvj+iZEoIg5`HSr`Ol9ZKbD5eTRr;K?*8vA z0HGMxpHl;(EX>UMBC0-pdY6<$vB?85UdSV+Mt$($L0dKP34MkWc+N1RC)rt9-& cqNE|>&! ztRIk*mv3%ppfb%yc_Rkx;G%WmFVTy&wzdZADX^bsm2)q+{&&u{)s+=k`I#0n2i&r= ztz=|mBqS fX6yhS4jo4sjKgBc?`q=%qzRAE%=7 zK^;-NeS241SWhfYLn =6{y3TuCDjp%m%`1b9_?c0NWeRoVuObiVZqNAJE zWwy>{{JWh09HXS91XDgfnvB{vf%3M<5rZ9m{P^+yUG8yzi=eIF)O3rRn|tC37Z;b9 znAnF8ALuAO&K{Ht=8CQJ1+b#kjpKVM@HYzz1S0;PLkd@|((^wYKpR;Lq`SX@Y|TVL zN$DalFHQ*f7+FsM?=>{K(aYw!Z~=Vu__f(H?&3^rgZuXF0~@WYsVQ_`7nmqIc#3Zo zj1@6oUtdoY6vrjb&^qV&kptr79;eWp$ZBSW$9Fd!7yu+VOz{+j=H|`4!EYK56crZ_ z)rB6SrR|K7rYcJnKx$x26D>Un26HnrBr~={#QZ0=Z-*E)9(Ko$9ZM}(a$9@*=hnZ& zInkuYDqu7q+u1WI2O}HBs5uA-$9;GHlfg^uP)HaBB90p3oLEXyQWw@7jzv;(^7f)I z@u+94d3TU(j3SPA(v@*A7~gd2)T#ZuBxPkMAfyrO#wOO%vA3KY9aU9TQB|nLw{PDL zKzDqP!vMcL8$Y65@MzW%%iM0n>tBu5!X FX+1|dFG~H>{dd3+K z%D!}im6bKQ!W;Hv^Y`zs0|TE5ev^=p5G?*4Sw21s){+;e_7R#x96)ej|9+e!mwA7` zrn(wTVqj?KSA (?kP-xvMDUT+Se_Qgc$eC^1o`829cf*ttVFX|lIw2te;EFk;KJf%E zFPTv07w|4qQcTcEK@9-wR6~P6js+tQ3@H^~zI?%T50aUT^_E7i(p|Yyjc>!sWR+lF zP*lnN8*8ttstO1Rg|W2dmXwI_^S3uPrd#M>c$Uo6_F0U0;8~RnI5{w(`lhE>^Yt4T z?Ch3#IncyAouB z2Xk=iGioX-?UC 2*MWdt&U!$Osm-H&dTVu6Gey(`{!NHJ!US`bxrfale26 zE-NeRDjc_Z_T-5K>Y$|Lc1qeaz{-@Aa~vE5d2x0YF&XW_PTAV>**sRee*NXzkM{Qb zw{P!&&pWNmVTz!(s_K=${{vxP4-c|Ms`&KuX>=k>wdv)rU*A({eeuGD{*Qs+s9MGG zz0aOL{el$)ophcT|Gzo*(M2HRewGAOgXC={u)~(`-vtE9v7Ebi?Wz(xr&$yq6_t)d zp5L$DyLV4PVZY6#Y+qkrY>vl!HP{%SlW-97sBWF!AMrIC3_rd}(%BHbhXG+JY3beE z`=6BEz{tbRoBZ~3r^O90xT2@0my?rY#Or{(C6B^%z-*W(G+$L!6+o`Nvollq>@IhV z1uMEypVO9>lFEtr{O#Km7E)Jc9&?k|u3w*~J>B=iUFh`ws}78!VXbFhz_NL$?;iB} zH8Dv6akpQQcD0l|wC*;FMQbY~=bc9{UQmcp!yJ($H%2Q$iQ~U=nCoX-Nm%n9fJPz)FXuWG>boS0c&X-aZA4)J(j(cJmt=ZVv0IE-R z2^0b*#Z62mV7b8hSqHKf6$$ye?RBuX? s_Orzx&hsMQ5F==* zc>3VrqnG0ls(2j@fB79wZ#sU6>q!jASq#a#-SFt>>7+S)*uc>6tHrlZp9bcJo5j72 z9z5`(9Wl?RdthQx0+wsqmeCz+zc6M8SW;4IMUQiRX_^_yfZHRFLiLfw`vd@V9UTQU z4X^%h1dvdpN~gmyMqU3YqB4dE!d=nRBO+t7vUtv)pGIFT>B9#Vlvfb;sYiKt+nSp8 zQBzMf8@>EN$W3bMG@>hUh{KP}VeStT8yj2I@9+nM JbUp19HnxIJNTNDlhfKS z0Cpj44@}ayYZ1rtfZTWP+)0DegxG;{NNXR1cTOJsCMqt@!{+J +HjJIsZ2wywT1W@4~fmbE+zF;JgTaurrCgpn>!QY2%&|4VmF|5 z&;s>l6zwT1iz=4hjnam5QU{%nfC4}et;~Td>|~k)VTPi0d&NorZP%_{1I{n7$+iP% ziIJCfEZq2@@K8e51SJt-cGY)>fAzqG91Es-viV)(ahUQira6cd4DVPd5$C0agoO(& z@n( J)(l~5+=FA6>8c@JpP3-lhieY7U zrtT-)%WOv-hv~j&&z`(}D>xC2lziph2!4Wc@u{jRULyM@{u%-vcmcHPd+q7NDEgzd zmD!CTp-F3g%4Y*{-98K7c!d#6jhsDuw!^Wdv(o_#a$p7}*4DKu9fJ`>V~!?sx45+Q z5L)E%2Pi$bK`76K?EgQ{PC^nDCSYX<5gC2-Wn9RW*jp796>2ugU#qI-#@hWG_>d>W z`9Na&nUxhDbGa^8VN@Z&cO2()=qTfol4`rVo$%-sJ7N-9#*bsAV2jbbHx9cR8xkB` z7cO{_hbOC;6!Y1`AV*=aG%&l8cUV mc_h_i%XuNjZK>+8K87-jG-15{WLv5?SEEu=6| zD|t3=rz<(q&dZBtjd%OIxVS}Fq_dX7%x<8$`wtv&r`| *4 zT;*@yX2APk8pLoDXo!(NTt?p*6GjDhG>kDpX9^=1_oktt0X$2NiD@>L_X TMFFuM*k;Tk?iFI<(Dj-zRy8u BggsHmv4G-}a@ zH{|6VruzPBklTb0NX}HmeD~5N(uOpiqXaKGe||3x=4h3QaB=x<8g9Rc*(FR0fo~Pr z&+pn)A-S!r{7kC=th6*md+X%Izl-r`Ydq!o^IhtFF*i;-CiolihEmM^*@4^yB0w`D zAXsEmK^v^i%s#5Et*eo@J-8Ni<19OSc6K)V*|X>fsg*XM3L<%fgM+1{q}Du+qFVtr z4^99 !M3CpM^4aDvM@8pz_D19kUVkX#5s6_08j9) zS7hd{7Nh= unI8ps(d}u?QB($WLxA!TwQozx@bmgU! zr~HQ}CtaX8A?N$TsEu%9WU0{`ix}@$1_^7%I0uuJIO=)yD8?=+Q4VqJcGl%TB!cAQ zq%5}kjVdQ7bVdg8I5 G6;Cx}x9{y%lmp`4T*0^#^yzh7F20nCX*2-XOYCI%Px`D`4>m+jPEN`>cP1)vOuW$m z1S%>jZV(h3Q;9DNpSOPdmWKAykA5oGik4hit)389*K%KG>8b-LbZLTWX@$Kg22 SoaWs~N)hL^yn+II_) 4e08?1QPAER%R4i$TmBa;fyq&duS?D-|st zvGI5TgW{_?ih$Rz%PT6X1vI1uCBP_!g@mfgbN}`F@K*vnRdRR}1C!g9RD(@@pucLM za3ypuu^=x`_uf6HwWaewe%J}ewE{TRq`3|g2O_~2hnr5i-GJsq6b^9-Lz|nq7sD+? zc43qcI}E i_(dqQ;{ByvF$J_wThe zHNVXGklT@Xbq|`8ME6fY;dOX8(fx+-FLMyW_zBW=`WM-j(?_q-goYk`K4@2@J4!O9 z-36W1ME5c _-0sGgAGYnFbtDd9*i9zeW%zCL}E2z$6-!rD`#` zVEkymz+p9~+a>-74chQT13^B%zT#CZ$8B}>7Z2(HIhgo`zf$>_HZP+HM4!^bAJj)f zBiz@wj3&tluar<#H8e5l89a+jJH@DkfQx(g?p5q~VBo>E?cNqq&BLHk;dxuH()yo8 z*ogTvI;xdt#>B!x=0Vl>m0tW05V)YA08CTY6Q@e={q22(kumbstGB7CjE4`u2@fY; z^FzsgA1^|{gX85Dko!{Ps3NJqazFdLdD8~bZ{~mINf|VF^wXzL<9BF5eIUq!J3rpx z1p5RDJ(e&nm<(hrO`z;yKv>AhHK5q}pE-hvW!B*lxP(fNKj^BfA08gYNWx+G-?)^! zyQG~RzqrBAPoC&)&n+(Ma|NRt%gVtq)b8l_`L4U0TUP$w<1eL}TUvfQ?_g$QgVk9N zBllrbS{kSNKapFmfHI58e@Nt`-tAAy{#+VODJdb${CuCb_I50_`JqWDs?+T3n8q@g z&}fDE=p6Fbb!t+t&tPu1Zru3Ti(P8{{(Zj-`<>&DVOPWYuCkl{VtM?#z39cg*8iZX za+qlj`-=%B@G!WWsp;vBU%y^e__Zlr9?TBOijwsk#-w1!2V-MnCzx )IL1Z2Hhb*MnJWuo@B`tufOx<#MuUP*)VWB)=0C~(aT4Oe z9=$#K;;-G^>tIcM))I<}2ZHY6nhc`O*A7npNYS9BqkF;Zm+!PR^|&_I*dJB^=jxt) zo3u>VcN{(D28;5damNV`js~Zr0aq23ls1PAx*0zuy?-C*@Bf4v2I`0S_>b-mjV94c zk^1oU`uqDy#bC2w@csrW?5jlRK5S@17(dL-C#v_2jMNLQ+1t~SP)9Jf08e6u0t(7a z#SVB!d-P~VMh2SwYOr!%zrM7=fZNiBe1>{2Rr|&Nf0CAj (~2lVy08`5cTbaZrNL;yYSuD&A& z4-TS& _v-tTpvC*;YKZ8k&q9boeiuL1}OVP#`8ml%aQgZqaQxohj| z)!ln{y53MyLQ&^JhsJnZy{S>Z&T@^-!qIs67Bp z gx|J$i8nhKymysf6oZyRo5jR#y9R4N&)Pfm $+{r64gq|bxim=NDU1V)>z7_(Ho{p}{ROReJ6+vP?7c`BO z>IXU8dvY|R(EHcLYJemBG~;u< _E!ngAG5audu3#5ptW)Cwz|Il9H<<4 zmenKE2f$Frk(ub|s3`M|HM@wzcQba{gNV6)@9ks;mR;_-+!~B3iqWi3pFekZcY{ef z_g`ItxdPH~K->W3id4+a-5tM+z9Eb>6R&^LGc)1hjNkwnKBie@`&aS5?|s~Q?zc4n zHXM75t_vbsepy+LPELZ~t7~+*P?N#S9w#>Nom+-ZM=K3uVw~LE%;e&5`O3@7TUr8G z-a$zHgnI+djuQ~YPOCY`W;5E2q@|_Hf-YK(D5%>eCnS__Z^wmsSM|^Ae-10*93|e( zB?_1M*s({BEgwG~gEuH>{iIHB6DOU>nJ7J;#9fw=5rlvNb1|db>3KC4O9r)t;s*0( zI~m#Tl8ySCii+8}xfq*x9T&%PBvJ#SCo~ji$Xk(+eSLkh?{rWF(TAAq$gb(^EMyC0 ze~T^wEY}kB24IKejtH Lvd7_^Dy}9vD}~k!SjD~ zA#cDcva+*xx*q1zy^LN0xLJo#Ch85STtGkoW_$M)?Nq&T<;<~T$Djs8MI*ad((?0< z1l>bx6a*y}D~ECeoj0YPn4F9OWBdE}@4Ftew6e-y^^*KtUHu^?#k+7jaY$)Ae5}8J zFD`uea30|ReZv{g>Y#Xq1hhCuds;yE&8Rf-3OKu2(7}5V&2VAn5%L%(a{C4_B4-k) zTL!^>K+krTjSU?dcWO*^9T202&j{PV+|qKO-jG@yO*!$?f`WxKw6f1X!N!~DDfa3e z$3-Tj|1PBqmkkOE($8$i4rAJ&0L@Df4|u#7`gum(fj5+hQ4@;P&u@V23effJwdP?< zZ2{utr2qS~v|T|#0rstynp!B1)`W+@v=M(Wdkq-?vI)BmlX#c$5E7Y4X1EMt{EtH4 z<`Wp9niW5 OJ-fWPhP0=q{= zK5zyh^R=c1*k4VF*DsV3wk;^*z->3I3xW(IoEd3pMCow8@t<(|SnBEPdlUYy3d{P! zxSFl)5_}F^tuDOcElet~_o1QI{PK8^d0AOXx$Yw_>Dm=Ld~H@|pmKnqI8sz)M!^8M z>uTIXu|o$G5-(1{4oO;D^G+Q4f~<*-i6KVZ2QT495N5z!NXrL?hW67xw=W*GmDPj` z&5#)%4+CBjPk6lc@4$e7a3Bn3=<(vfp!oQBG=0rVk7CXC?qym#3y{Vu3Xo$6hJDUi zyf;tpKj`&&NO4UP-UBQs6a#p(h0*&{Qc^@v23?P#(F{ZG?2 P%ce=~Zwb0RdP=kZ3zs;bLE-jR`7=)pPAC!ueE-+cM}xjk$|*uMh4rL~Np zpE#6-;N2^ZCmUGaDS)v8C5g>$ilaPLt*wi3adr+45ANT$>gIxW!ecWC+bbw5^PweN z#(`#BH*N}P3NP!0k9w(d_}UUIprF+O?TV*(N5Rh_9<+QXCMKeb{!^Yv3Ow*;NZ@ca zYa2K+N;=1k=t5kh0bF!GH4^>P{%8^O#{L%HKwy|(U&b*g6fi9C;`Fo%+Jl(XsIr)w zpRbl}fvv)!S+oRk47~d-3TDJ*vziP&gGqr-wI`mQJ2#H=^5$4|YuN%Qh{+Tf^T7DR z%wF{qZ?5U#MP$%mAQ^Z;!vWW0fFp9jPlC_I|39kU1Fq-3egDU0gpg37QkjWl6_rY| zQ B ze!b3d9LIT_VV(yMmMXWQ6zdhgI}|L^uPY)d6s06JQb$@=)-&r${=fbk7R;MRhk6@x zhLIz?NuCEE`a}WsV@|od8mQ~OeO 3I8l3X`bj#B_o8+`oSAseIReLeEv#KIhKw z=dmbkF<_sVF5IPH$#vw&Y!ND~o|BKOfEE!-`s)I<)dt^(Dx=$P{`04zyc}#syz4~x zR?t&tn{cfmM@G%Nw4ETK7t*FLCS2IyV40a>91&OZXC8sXShv>S55RbVGpTPF5wN DcWMVn8RK3oQuLJt)iljzQ?FvfBt}e z7og kZ4(8&VK#ZE9nDljA8 z&YD$9hYCb?VMsnDKw{!vIT2(d)WK^;_u=3AcSnHRVtxG@LM5;I)vH%>BKziCgv~tQ z i3$YtrojGBTz{MoGbcGG-YW`Omp{FSiE05dzGT{S?OB6@^uu4(R@RzkQ^O zwqxj1V58QgQ#-ov54&~^hDxZfe#o9H3LSMQwIo1iQFsmoSo#27AWT-Z;Tz;Z&EbB; z^zQp7!V42JxIat-+(cW`uyW|v{VpIhAVa105CRFHxaP}B{&V2l?;&Y%Y${)*_2<#{ zGyuY;uV1l^KIZSQdqosV)D&6MXoF)mQ&tCERv$FzBz(({%IsMqsyAkVI%Ard+%Da% z+X3*S7inp)s+=}#*dQYltZe!^p 5)$%BrPL7TrdLTx`~HX= zN)_AyIKeZ;0Ge=5kqiwV(#8MODZ5V+nd_K&A)uoW6jy$=0*F@f$G(YYGTu#`;5sye zusM0!w3zsKcfzjjZR!7uV9d{`o(>8s?REATbS9%6zNIHGUbv8k)oIDT74~qw#9XI_ zqM60-lMh1u{XDhbmhK CJ23T=jr zj7<7)CT;{pg5jOZ*)|t^)6N@R;~Wt#lFDKWFa7f~aI%WZX>AX82o pH4butJr%iL+v17Y}k%Z*={<>srI?h8!kG5mEgz)I{ G{L%0~3{8nEWn3|FGH*t4U|LX0*xz!*IuZ!k95n!udr* z2M;dZ45UqYxD2?b?GBYbk)e0|*OwNP&d`=69o7lY;iRa#H9P25E;8;Y-ku~F>gYV7 zC{*7$h!*(72`mqaD4vi}#8g|7j%8(KyZCK`#JzbF&^Ce8ggoxy#TRnfHdAQgx!-DP zYV}-SdM5Ca%Q;oHAzL Eh}n-9@sYW*oLO2qo-W}Z*!s`L$j16TiEBx@a2IeWf@~eB zRaQEx;^uEv#;7DwMITJw@%!6Gre+QL_IG; a%o&24u$V3rOFi&_R}aoy-`jYI zgdRTub`^GEqm9k9bx%d91O5Eg+S?x$0~vw6ulF7F*A(&Z^V?&Wx>lvDs7d7%gt2rK z9KF5=XT5vj&{=)-=(#j1$7cHto;*rkzVkB|IZwnYcv@@AOHx*@x3wLgb|$9bNYfn+ z)U3v+1fZNo4SrNqTx?-w IeK9 r9{wa19)J z8J_}0C8O6;28x7nR>vqR0^c-_@Fhv~ZdMxQ_Td0>7KUB8YhGg4DW-4U)O4;{Ir?k~ z*xq6L9eh$<<-fz&{~oE9E*$l^-j3AqT>eigcj&ke9qRq_sm%wfV2_>SgAIl;$pI22 z*q2ZX%QsU*kQ3^?P7dfQNIM)8X*4N26%32dFCN1M=Q1KQ=CDlPS|Yq41W(SK!8POi z^zQv< Z?R66aHw6-PS7Q=~ G)P z*WL&aeD(W!W;CfR=|^bb?CP&DTQ(8em7`k_zdMcx>5&$@>m1s$xqb6^d22S;@o8x< z;ctT00Uo;8?9b%U0_-_AD7PQHc(IhOG%bUpaP#HV!=?V}x?^C`hfdniapCq R12vd%sDa{9p9_`vYpfQr2+HT#jBx z+6^qhuY#F}Fo7VMK%c5~{V@##(GNb-6i6D-5pK0_UzRZ7#q}!bKyM9G>6%_3OML4X zF~jhj*|MDkvux6SgS4PtYDgC)u2gx|Hpk=wukPB_+dZDWQTMf6i+{xk%K7v*60E z4wV)chY-jqSiH|;f#SdgC`=gu$a7nEtj->AJ-;(a9<`0d=FLh)Z$?NQ`d*$nG@?e! z`uV_pa?F2F@_hgP9m(V8@^TcyQ+Nx0e*e+TK0JyH7rKP-iWE}!>MX?!-PLr8x>pg5 zCMH?{W|3qBWYX ($n*~iSwnLsl=b0r2 z(NDF!$MJu7q0@}@8B+NH3&R#W+_;If_xSPtKl|HJRfGL2m_MDM7q+-zXwd1?F-}!R zszJA|U4wVe-4YcWyTit&k~aXR;2JT?-K5z}O0;{se(gI9H;!Ihj}HO@O(M->MhA(x zrMDDgscQ`qh9b1QISv{!IxMUh?Z&3vXQQK|L2TsyVf^sH)vMGCwiInZ?d99ve||Jh zVxg(&V>%>+AALncu3o>c(EAl^5v6P*mlufCjK>BAwqk{(l+=gDq1`%;nu6upwJM-r zvuCIF95AZk+A%)wsSM-W*RO+oHGThHn;)9tl19kwuPY3E6&j5>L5tY@>sMo8-@X9C zlpfDE+ilsRiF8tKMbDRL_R;!KVsII8-uS%ZWa8ROSFYH7xVzUN9Q4@N!K}@F^;r+n zFyRcCqoTSxZ^pteM(}6k!B~%WcT|R4Hga0qDo}LhLj#hF-*EMEBf4q3SALtk$L{6W zs;(_p*He_|_edp)rlxk2o_TB+BDNW@O#o}ytJ+H+4LE(;25I`mi<5a0oX)<(wDMC^ zFC*uDGDDj=mg4equLO#p(g7Drs;Z!O+6jl~MuENPG~r7oC0<;_0kr+Gr=!E!6I$&w zo0Tm-6?w>~c sC^GeEs_MmLVheQQ_g?Cr`eml!h7XcDNtPkeZb8?JdQs7PJnu zbsm3y^pQUi5Rmgw?T2}fge9;doZaRgAezo!mys3u_*`{{$s69^y4QPa`)DySF?tb& z;lp#@1`Lss!YgLm!9|E~B_t(>&Dc3*+&HvS*GM^#DBqofg%6_rVT-ECllJF4S)okg zmzl+$$-h2OY0Mb09iRWM`}uo>o_HvZJ$e1k9VCNUp> E-ibI*kEk^ZSqYK @Pkr=9{{=Aw|~EV`f}d4>i71H1~*p&y8@P!RaB5^4r?ZY6VZ9{d7r#$@Q|70 z?kVRjj2?z)o@xK(q;3jg-@F5UsUdvGA55MWFBUtLI!Q$ZV}B*Bl_n+>t+{aIZa#+& zy#TlN3|(zzCKPEzozaJ0+Dcpmw27M@)uDJT(v}Fy)N=fcn~3lS_$ eGjDqFCJd5a*bfQ%^C+_@%OP3{K0Hl`EM=`uzFLW&E71?p)rxccuWm z^?d#I?StF5>qv7nJ4qP| PZ z5na8ZLlN3ceii8j(#&!EIqJY9wAPCa59!1CDp# z &T$Qm!{{IN5&il(xj7lc;@`??OVnzlWxK%QP(`Gc&;m8z@SU4<@b^^f{L< z>5b@SBN*ku(Gbb)?ssqHA@f|8WOf9qhC=0O(@w`VN1%gxS0RwKAVrXd)YQ slw_l*T=MmT40o`#I1(kKkMz+uGMiDrr7Ijx}Z0w$Ol$~_~ygh zxN+z%;19 ehirM$ah-mOjudc0}yucJi;iDb GZH@ss 7M>FfU{ zce(v?%%8djJyw;R4A+^$l3BsQcK{aqKp7Xc?OtkZjL(KQ#i6HXicm7OpEWo)&4QLC zsbYp^RzXbblq3J?`$eqqCWNxeKN$#q%G7A-9VSdRZQi^v PSX@zO+2EV~#Di8cd@pe2EwKlke@MU*Q82t+j)#`Kj7RGV0v91G-oC zwS;puk(vp^f2dZ_)^pPu5B`GfAOs4gE9m#&rFQ)IP}7TAgR|pv zv8N2ex3zbH4q&jq2ALe8di3NmbTIHTGZ_5A{V2Jxuuy=?yipHR;#G2baMy%udAi}> zT>fC&jSJZ}`AT8gHuNe0JD;`Yl9A?IBmyCZcWAGtuwWo+;v}hu+c+#d^^H?juURAg zvwauH*XL+vblMB*{%-a8dw}*7dc&Q@R<8&mOKiQoa=`+U)Lg3Sm5kPdK0>0XPdSd} zL8vEfzjYUt+VjH1jRjf3yf^ZF%<|I#0o#x(UcEZT!^1;9PL6ZOvu|o@qA-3I{J5yB zY}xA7zGA*UKK<9bHSC(CrdCmRwQLDcGm;Ryw8B^K-zT`#%jyc?xI)HMJ6mEQT{9|B zU0q9opzu4?b>^5cWAY#GihdluFS%H6_Ux+4N|iGi%(Hkd#@!Dp9r+1lT?2&^shQjD zbA9K_$C3&@=~kE)gsc&C(LB;F|NK*1QxoH{LBoQHK!{E<9-wN{ejL~1Vq=A^S 5WHqtsJh6?M4y0|L*0D4~?xU#$nr4}vXomLjx_#7$Ytr4bxjkTYj3 z_hhoJpdcjGvVT{kV;gL3GuNtg>Cy%G<5j!ez^BCy6m^#Y(ppRfMR8#f2T4;?leQGt ziD%fDNI%hAyIXS~2tZ)cpt$#gJRZIb)xHxR{@(Tl @?INjhZy5F(Um0H!Lqe~M$qh*G^| zCdkWuR2<`Sg8YMyizkY81?QA^n*MBs?nA6Up@o4K8e7Xhe?I0>fp#W(eddrBkCy~O zy-70vD>9#W#9`{!sHk($%gcAu1@wkJ ~m1H-?2$^QM`Z3iPGo#@|!-d4a8EIGNBpce!p>?bf~zt1sZ`}Y0&Qdxb+ zgUOt>;hreA`s)JCi=|tN>?64nO>CQP!+!}}!PIPzp}h_$C@E>!yplsDB)A9V-X9IJ zrl`1)DnDW8#`amp#!UpHeC brl-A|I zCKThnrau6hb#@lKCD6fd1<(I0NIxaa!e#`hs0YhRNx5 P8s9{J9Y%hsI~5 zq4%LfsMRbdo#Dk4L~a}qlt~*-CCC5xrLv2V_sZklxLd$?t`E^~MP&VkSh^LY;2~^2 z;1v_=6PBruKe>SB+}X(qF;u^P{XF={$SS}ir%U&B*PbqIuz2xe7v=dgXNFN`Xa<@2 z6QUbu`2l^D92A3xA-8>sO}hPbtxA;LC*M$ch!S+97=M-VJ-LtN$~(aEi{F&Oh%n<< z^l9!tUAm~xm=KWe DVLm4;n7?oZqNaJ}a3=&4V z462zW81?-7M{Wp#0=RJ(XsnZy)4X|U+ xR_|9a3Vg!_bs71Eor4XuLG`z8Z4~@SH?L6OTv=ItE-t Qc!s8) pS@V`Dbv`Fi=qa`&{ogZXG(KzM8GU}_8qY84TU7tRFCsbExpOCn z4w(ZAORMocJ;LtXF$aqf;I;}!XJ>%~;q3BgxU;M0zKU3RlMcZD@|e0WldI?`IRp~R z =z#Sm?P_mpW#*AWDo=lVrO_7EJTe1t4QDbxUl>ae38PgZ zOxm&divN)qr+K7+I$876H~uC?dQv}oci-@lXW)szIhd*@8RXY{kiWn@J>6!JY+#;8 zSS>2d$T%F=Nk&R)OomOz<0^{Y#!rToY?!_m>SRqUaLn eL4?4Lsq-`tD+CL(~G5S`3oHwI?sw;Gvw2mdBa_5jiL7T_J)Ovqu zJ%<{_%uLt<^-#O%?3{}bq)fE+l7GXHTj4=?Z$v7;fA2&GdhQ&6efsYCLEqPmJvp{$ z!v}^LfEgJkjybPQ-S4@WFinewFlLg#xdKo!tBx2!L!)si(`h$}x>N%LdkM<`XxKS1 zo?^MVY;4gJIdJfxFkuST2{QSq#pyq_r_=ah!-f&_+A07nS|Z!Z>IIfiS^D@y3OWkd zcQb+j%&y(Km1_NrE|^}b^bOTz6-E(SQ=ij$6Bh+Egzg^TTLV%|N2gP)qGb?m2Iqf0 zwqw%LH0i&Z)GpkI#3pFJKazEe56fqNyuDh&;Oii (E;pt-^eyUR#RCb!e#J z!`A__FvntRcl=pS&;)NzYq7+4f@5@`&iK{&n-PKo>4S=#KfkA9J>V7tBeUC7&U1Ug z$$;K~f%)46o&ma7 q#5?Wh$Wd9=raMa0Jb;_=Z= z{$>hx>p;VU&tK_}YC_1z?S9rzmg*5CT!mwlIJe^zc1oD48pQeaINr5%$l$@kB$$#? zHSrF {> ze8j9%+O!u)pNPQ Qao* aRY1j!=IA z8%;wvUCIHR&Y%c^0IUU&^#<_L)vK=L8^^1BGLe-oqZE~tIM@RNTey%7b j*SMt} z*}5aL+JEJ0L7)$YML!L?KU0iYMeSJ}KXuoxKevO>#W(VLTopoE0*a=qP5wVE06j9+ z1x+dMvAJUSJc}cZx}BI2Q<+sUd^|fQnI6|%yI=tr_m=mc{_cJH_YpO#38t<@B-oq} zTYfOk(O*~L+QKhYRZt|`4o-Xj?j3~4Y0)#mIDGQiwyqgBQ;f|rPr6O(bzooKcz{F+ zb~#B&O#>|?);La+cD%|#aIsqk#cC4ge9PC@H!RGedd0INLl26HTeO;ba{igj6KIO+ z>H{~6EvZFoUw;303R)yyGekkraL-g|JOBVju6VF uN ziu(2GllrH#@DVG-g?3qeS9s{AwC6&2`1&ZFa;osb<`uNGAGbI#ovEZXUQO*ie_+8| zh8}pwl&qL8Q)ckH#I0BN{{xY?B@i%+CK(I(xD8rpCWS9%gR$)x;)_SrF?3zam(P*t z17yz^WFq5qsGz~U3#da8j(~|u*R5x;fxT(Bp}1@V(r^T}Zc>^4kwB{M5>8?zE7d;U z`8ln>=VC}k7dzQzW8hrcH_D9sk &{mqyrP#_v+m{Ke`a)%juV*A|lj*lKR?QF}en$NL^o> zPlD^>TJVtWyU6Xw4^iw#BO=5@Tc~RJU5|GEk!U}Ic~8!}ccTuKI;qc?GQ|l!OxKy< zA$U|j`}FDYw(9>x^Y*>y9%AB#lXYwB>R3EAVr|^zqel&CkhslTFJ~krDKOH3sF*QR zEChv}o&9wun8(AqEnB?U!O>9|WeU0a=#d+-m+vR{;?*tQ3n6WPxN{URZQX}s27H 3q2D&kpeq7s1!^!kkWix!JT!=D&KX9u+nQs z?-o#fh6KQ@T9+I4UCn$~%wnMXhnA_m>Tb2K9QTefH{w~w?s$l63yWfEX9_En&aff6 z<-=W|{md8#(Pe$IJ+HZWQ64JE(1rAY0IjExmJxHC8`trv& jr8)aC*kT_k6`f-*p!OTeI2H2p@-c6| z7bX6)rq9yFi zyntMuAgo{L vAUcYgz(DlE)vMDilDgKJcIJsEaAzbXN2isM5}?mCZJPndf>-{B z#BH{pV*UICt^5Yib|4%^I2Kx1grWbO+hfVnrMPL8DK8PyIS!05E#X&ww10@piWwDg z*ZxVsoL-M5uPx}YL}&J5y^YRGZr
f{!Lt@qE#hpvc2pAk#s93m-_1c+T!b9=5G*hX~;_7?L_;!*Wxs^Ietp? z)09Sx1MRi9J9X~dVKFRwHpbn~W#CO!)tLrjjj{_cBmE0iG@`SHrl#T6bzPBQam4$l zLf-Zd8firNx*4%2q{!!Iks|WK97x=oPbNFTI&T#WIw6qF)cjU`l*Z_!l*p8o^5Upn zZ{4{g83J?(WEFJDUh5)78|RZkZ{k7A`?(YnER151G*xK_Wxuw~pZ`=q4Vh@aC~p+H z_3qgdg}?aYsjEz;48hNB2M6Gm?h|@lx=13c_*@3kOXg4UvOoetA*m^A&ts +YT!A@sq3fe- zi0Exa3D{U$KjtFI9fQc+%z4sWcjixSINT`n=mpXQFo=u`i;kh@yWJ?A>5Q~o8<3Fe z?2A&-`?FCr8JtK=mnnhfVOjy{12+W61P7b$-rY(rr6~*W_eUGou;^h<$EDfhp#uMi zm+x%8Pm3|om(aNUSrI8r57#A`3^}4owFuPD2^O@Hfl_Q@M#KvT&jjV3gIlQ@WNCqc z0GS|~y=Co~cxohhPvE_ER#uVB8&LeEr07*09CVO|lYh-hCbbQ#-Z%O^o)tTI{(0y| z2wg=eXS8l a5?81qB}(vt#J>Hqo8dzc1+s43&cFMd|F32doBSb(H{~|pkvG47 zeK}xjLz=LO7e#-=j~`KY?~?qmg$RfkRFAtOal)kAp)Pxi&~)F8jt*RJC7O(Y6iI44 zrJvh-J<(*wT;OFvI4?=BnnE#1=ZMB8U41W5?ak)eNAvH|nzX!zHL=Z`Vr(cUmj7sn zamJ#l32NwMe0+VARP-rPNstsF)Lpcj7qVyruOeIkjUYZA)xJoIVc4X&kNht;cURY# z(}k|6&6u4$;ixR-g`(IT$sxmi#Ou**B^~d%lg&XtznAEHIf@&I7SP*8MYs`pi6K%x zQ8_^4`Ni3v-+-Az=*dUir%R)<@A+ l7^}W*?nSHi3jzwT7%jwsYOL6MZk>f_Xsnwrcb|BGbIyvO1}A|1MSKYz zIo^v`&A*#a|NX9Q!GF1(v)Ue7Rl}*PN~%=tXd^C}?*mZ$4WmljxgzHdMt;e4cszQg z;!lwY0S~flA$)eay3U(CcgT?|JYxbgfsA`&81rJfmj%@X)0O^p%Fby?NtOT>D9dg+ z_c|FEIC`~aDT7#zl8AI9aU)1+LWVI{q`1#BLH* tnN2VKbx)qMxNNZ&NUVrB$}y)@XAVTL!g8mVCVM@yi1geSjvCg9gcaLLhi)Z(O%- zGD0nK>Xq>D7s{&{>SmhIHk4y?jr7!bEV;OA*RB~sYV}m~z%hE^y#ZQ)CIxeQ>Ao9) zE4cBaA{;S495Q4za^mI7Rk54f=sU1YcK@|&8)3B^6pl`K2Du+_zTJyh?QrTp4k~S} z6pVOF!E(19n{%-?_x{0eoeY@!0=XYC!ZW#;%3>~NB3G^e*&W+8=ylK0TlpCM`}c=o zc;k7Ce^PpeIYtl{Nx&D^JBtr6cuI+W^yjN%83-f!*@z7e|NI8{Cg2W88u)uCNfA<* zj5j)EHn-0v3JKb(36C<)6qS|^mtIK=(fg;q3p4mXRZ~ ^??I?2kq^qm^J`S}l0Ew9idCjgeeg+Y=^QL!7L|3SutPbeTs?pO zowvbvu3p8idasqoG>~WV9YAMF9P^3AG<_miC{07TjJ)m3j5=YDj@bM~8zDh2n_F(N zwH4fD(2zGbH`C7~CFz=&m{e^}R;l5@0#u5()gQpxv?EfS2Mm}&zl}%=OnYI#SBKbq z$|5MjRp<6Ilt@N#aA+X2qC1?MvO|#H>PJr)@YHJ3{PYm3)+6o`haM1*(Qu4i_l`M< zvHPbFIpX}Sa!GNQ_$&$O+D+&>2{|V=yH6BdcW)*18e-siydVV2^J4GG2$o>p-YXI0 zVITt=18O6t3l4g+YIa;oPR@Pcr-WoTt#*zz04O#Lw$UEmr(HD*Ff{ZLyeMRmQsICA zPT17%_rQ)9R5VN|Qo%-aMT7|uQ`0n`iZtBJ$nD%_kuXaCLp$uK4K7~1cwp#)N3M;! zzXu+*B*Syo^xDN <-09$fERwmn;>i{lzA7VxS-mzgie$8a$}q|K3@HIjPh;l9u!*%>k7tiL36* zG)@*GWs)0zT<1jsR-pYBr5QLE^E@-NPoXbjmgC36E?s)_Wsl#(cg~k?Jah3)T HmZCe*9cu~##9Hr3Q@(bh(bFkAZC#f#g(DK;)2=5i7j^3bej)mT?j1f6KU(H(U# zE5$J^N}8KII*-gRCZ=PT`M+=1si-~KB{JDXJ10B4*fADSNW>sNz8jf$)N7@^Iu7_h zQB7|ZpR#UUDSb0}vOf1dekwO>yKFKyFTDNZBs{?gFBkYmfMvTskB~|L+_P_Ye-9Jr zNB{NO>Rp!nViW-Y@}t-ONvIn(pqg2JGVKkk#Ub7lgU;6!O)#dg=WWlcU#qM^3K$SF zd*;kU+B|4p717CRYV2+icRvl!$UuBh(9o&N7|1}m_l V4p&`*?*iHq; uQcY=31#Ub(dFKgh}IDx_MWS?$e@7jeL4N`Ym&$w^5g{`fw= zb;}k!fbxS3(_fd8p?~`tN>h$NiA7%75djw5HW6tsz&3*x!uC+xXBZD~EMa_V+k{=j zC4qWN1v@!GCiM!86}GazrY8T#T{_(r%a^kg8{F{liBB1>nr7I*d_QwdNA7&gF#7v1 z#Kdc%*Yx|pc+0mP;rf2bmcQ<&um&{F10^H?I;E%Sv^dkhh=c)3m6ex^4;ZJc973hy z;L>+h5Kuf7IoS6^(cZm#zj*$fKKA&QtNh&Io}6CcT;NoU_ykno`08>0y9Q4@WCPOa z%(04LggAA;Wdb7soqsBKdzJ*V&bk*IX>VV-;>_U$q 4K}s?epge^BSIu5%M8| zJbQLELmDf`p`U^%Ow_P3#95vONy>|EcHyw~RP8)Bw}2 E8H%C^O}tecSu!(LmR$JX+eb!R=>Wl;KmKn4G--(xi@C)XD4K%9|+7c(-7q zy+>QqM2J@`Aa=~2eZZ9#UofF0gAABqGV CCUImCagzA8A~( zo4bR9!^)KiGkS&e{>d_La21g-h&S%3^EEFN{e=&=Zzgb~<_!o8e5O*v5GkROB14!k z`7&bjRBZ9&? 2YmUvy2pocV$Y%Qr z>+y1Oo$QkqmQ7KdqhHH4CG0LOFx$9t)hcBBNZ4gGU5}l9A#_`PR4K?Ayv$a9+%$WO zRGQ_zlpUpX&ej_?3`j-d!Kh&S@YP%X^ZmjRL;IvTK{#N(GbYmp%8qh5KYtA{1Nf>) z7y({9fr%cjg}BF*AxF@7@Plw5#RFpaxoSi+%cr#)(GK3V_$OPTlVK~au0E+RrTacZ z_VcWCy1Ke=T2e|jYEn?FB1r_6sGRMh%Obl%AVOSO`|o!irJ<4c^r_d0Ro>~$jKfL= z>7Sa^{O4HOc{qA5; sph=x~sSlgOK!xMb3O=Vr_V|603N@T|O1L1W6t z12d cav>zWc*g3Ow>RFO@J0>u!4XIM|Okqs~xkuuGratvXaP z4UaXLnAa6ZaWam;#q<0%Y>$H#>eU# XpiPiXZdQjsN8=$ Bc7apDYSy;rtNjnqvvB{($)MS@&LGuR-3 zS$ky3L^L1(1W?Rnj9VYf6QeNFa}B?K9lPt$s3U6tAn!*+G`qD?5K>MeM7dlje%BPL zT##to2-rfzZP?vc@x?Nq-cwaojimQhefdJr_czjPy!+|Ta?lZe#L}hTz};Xgc`IbJ zy*jThC(eIQ9fiqtS)ahuSWP0BBSJMEgfXB;741H2Ul{F+m@W`pC6nu5hxmquW*t3x zl<_(#FD8uPjIhzqGgJ+%{9Bvi5k z72Cv7joN@^4qx6V7T IQMvg!&jP9v(1V=tqgK|WiIR3nrb?QSFa@`H=t^i>)C;oy|KDShCIUnK zB`23s=v){ep&qPUzH<)W6 x*B>Yn8Rm GS!VD#Whv_WTi~6`&j6)(KS#118$q?FepN2kaMUk;|87;AB=C ze*jP)riWWcKZSh(L$N|i1v)`!|E;lfuEIt(-LfI`@ab-_PmItzKfGdsd=OB}f3SOR zA~d-(Y01EogPnAByC{CTks8e)q2p*cxnZ)h^gs5Bfe~L_A5qO8Ja7PMa^$sZJ}JFs zk5N)$OTzK 2^m)|Lg}M#8ua$GF=)EzRJ(9sjE}2?$Ysy=9Smi z9~CQU65u85@#U@cF^B;OC>9ljkUSv!2cD-A*9x}G1e4}jXrT#4Ji?NGAe01uLc2E8 zRNy>pm|FxYaP9{G0D_l6@&}BboIG4b;z*b*t pLC)I{3vjyu5H zxAOy;cquB9GnmimH#9W#02P7V<11m9Ft^qiHI+}lHtaQdqUS1~7(9jxk9Ti<$RP(u z`&2uO2tjIpVR^5iU>tQp^FAB5 EWhGb#cgn{& z3uLIl7(FgKz>q_KlwD_Gv6U;%`5@gnYI}Sc3uycIA^H3iY?VaXRGB_P7xoz{h`h)U z9N*+7b$)5-exbRjr6g;dPBG)VvU2=j3Ko=$eGd-DN*iXpF)%dXGItPAajuB4UY%}W zpm~R;wrH0N2b^w?>;XK8Ox%XjUtDxwz2>{RCL*X0hvD$yfp&Ki*Q(Ui*3v|HKe1yP zj_}QE6#7#YePiQooO24OQ(KHq$Xn)6pNKCR^?hhRR?d1pU!|m|_z9tNYvITb{;_xn zRLxLJv9Pw@POS{E=5N;7^W&pBSf`rEVbT8nui=oEn3|3k6TxMQl@U31V(hut$jHUT zY<6AE&mFbP()aLTJrPLc-V>g)L`TZ&h}1+~IWdY)F_&_Re(|}HC2o2s^X&1h&TW3} z;N%3q-ZwSKqa)_{2N&plOuMNTFNYe$@j=F^pQw3%2Ss=j ya>JG9cj2Bk3MW0a9gU>e?l?vzm{7=R z{LcT6w#97%rKNX`Bc)>J&Ydm$Zbv->!_`we*=iEL8n6N=)s)TS^PMa10kKj Ve2TCz3~H9< zN1`bo<%*mq%B8*9OuP-9?r4QNg6-(*?d|VezWjDmtU^G#DAuO&QCX#pY+wr~PAkL; zYFZGrw4ihPHk&sm0HMywwVvzk)tye`eNN6r$9NJn-^A#hIpXMwin-Rg!Dt>=u}|H> z+%W=p!%ud`>WJ&u1N`RIfX6LAbTL65*#vET@doq P@mSwgpsDJcAf6#>O`%A~C2-gRBUa)GnpVsEkE00=KB4ohEttDix;SA%t_Xm^2~ zyEOOIh5l9B%*;X_Kb~>&zO2gHPWN0wQ}}oBde*hhExCX9?lh0KM-FO(6gAeAh7*j_ z@or4;Je_HT;|@AIGb{?ls8zR|{N+B2DCqXB^0 H6M zyQ;*`m{J7BnP=ED#gxc68F}O7OOU~Bf3d4fU_vyUKrL^xv*kBBju?J<@Bu5r82Mmk znH9EjoU%*f;+T5dW%4Kg9f^zqH>y;Es*zYV|^Vp@%NfsV`pQD zLVmKCE|46V2eM@7)J;pnL;JB4P=JhFPx~2$a}+Jj{L-x)W7mud_{IJ;k%U%q;YU=% z*EV$Y7Y!3&8q~>F=`T+P1hDc@te_#1TOLvOE05Op`t`92)xZw4zP>n=d8Y j~u@v^eiXU`ZuxpMK{L_QiO6i 7ihX80V6&LLkP7WM>fh?A{hPW_*XPlD-j(`LqRVX zAHo%;9+k9PrGk>P=k2 PX8 _X07A+a6ZK1c|fw4Pn}@f%7F z@f#{Bcipw?MdfR fr9Z$4oJ2y zC}rlLaFg+(z6aNB-psK>YNy^Rx`#2YT&f*Bi+Xrj5T2>_UuI<`&=>2-;i6FY?E;=K zct65vpI&@(!tXB>ZYWopYiayg?d&5=N!)<)2k(KIao%bAzf-iwL!PVus3%}x6E2 Y z9!e;Znx91nf*%%+m6U}Oam9SR6#f3iY=K#W9Ym9(S DuK{pH#K2^fnaf zd`738mk^E6v?I<+w3;Nieq#zKnoNKI8Kt*U5gm$40e`| >$kLj(BIlv>S}P@CHtXvuv@n(K9qRa-;F~?Nj3??PNdhXEQ&8 z@OFys*Wt&+S>N;R>sJLA^$SRZAiHPoGdi&C3vY)W+;~+~4TTT;5OsbBe&s; &?7c8%pm63T&0Oeq-PMN}h@{1==bntIzpFE7x7S`OSG@;$>KT@I*LDRM>gv38s zt;FPrLav`%SLMKkyFZpxR(=?}l`*o9(e|NuamGFSgLlI(H~3WF>VcqmUeQW3ML64d z68EW>)$!$%LVq~-Y@36JHUq7F@xVEVt`1ZA6M{6Z&oHfX&m*kKMMqBjD5L JIzE0bLb1BXoLtXGOhL5 zgc5>M*#7;=l#P_{6Gf|`H<&S)_9D8YZfbk*VT&`M$fgN<$a$fuxqB8aKsC~=d;kpE z=(Yws1E_nUvrHbjPL!<@Rv#kt<>AVFU(WUhb+D33iRf_>H{PDLb_59 reJj4$m8D&Co)1hRw+L%gwmxVMGM5w64^lnyJmT8Si`ks%(fN`l zlP)Ca%MAz4 @wspvl_jrUu1TCL0-G2^;$Psov*nOkv0w@<>KR;o#!Do6syfGlBdhHDK z(!gNjeoG;rqaP?Pp2oHmhiO)Wd@NQ{{a|1~jA4<}JF+}rqW`pJ1fiEQq(^SUdDF4p zC>#gmal3QT%r1SWe~)UEaIx&{A3#Er2(2OVGDn}~Av(uHj1T))=LQB3F07!HgY~z~ zR2dRBv~U0Ut-wDIr sMT=&O2sPqS`;^faQs)x$=(#^^iF(t`Qp!tk_;_#b z@x8GT`Q?b%!4rcrIbl*>TVwMXJ{FJb2LZG)=AxQ4N_sSFv)+yS`1Z?oqO?0Ax)auO z_4F1OTJE!Cbb<=vWV!N^*t@|DPPh9EJeWLDMP=>%Tvziyn2H-1LPSmn)dPAtb7pYk z{F6*h=*ZzEofsW);^uUAkByBVFW=} J>l2r%X4-^5!e2K)8Q4j=6FdW!3 zN!3{VZ`5&*hZur7p6l`|e~0!XBv7&wPV1chTR8nPM-9G(r^d0_6N&qDIuvv=mm>?u zI@3E6JjP9!Fxu{TipCoj)8gWBH2 IX43rSMjr z`4i;66DAmz |P7Z(zIctQE{kBgaA{#UD9O#0mI1 z2;+#k|Cz5l!8K2c&V$9v%E}@F3iCUh0H4FxqN1EXryXY>%gyg;!F>73F>hIGi?ocP z-2vOikj{&{rvyjsmB2e^+carpB(y!$6W~vvR)UA*zA)H&EEC6X9cg;u#*KwFGv=-8 z1Ox->J2$iIRw_vFWdCIg^G_jz(a_Ks`=U*jr_P4Hh0fKGReT?%oUfvyq9K|)_KEET zz3ivmBG=|a6H3@v9{y`uu)zvrW8rY21tpHN`zat`K$}}$NVe&Myeg-M>`N^8HP^s^ zN#rUrVo7L0FI;U{A?Kd=%dL%$013 xZmzFHE^w z)Az82R8VHTYomg{vc&`?C8k|ePFj0tV;s^>9h6>YLJri|I7R7235#It(z4#=2KS3d z%$~0p?!-q51}}!7-GA7c!9#}@#%xVSenL0%gTZ_)kxB=)?K)rssN;V>dK-aPHgt zo*aG#8T0d7_zmFD8@)P^uI3*^=fgep(Z~IM_n>7MamJpt7DQ=4353U>db5XxG7HGe z2~q{mV_om{-=Fw_)J38p^!{P`Zn!6eXP)y$D<$p|79&6?Bifn(pLy{NS?;EO$M`|v zkp`eZWm_jBd>Ni4+@G*HDZQTGV--2qIu129qRqbzHihodh_eZ^pbbt=+uyfWFaPa3 zfKn~p>-WyN$O8uyW5jE){8IY)B})=3nuJNQl5wB_n>SZSW&LSxzG)h_9H$CbR_%@E zI-yT74er8P*}HcuzpUyCI{=)@Y}*mOGHoCDfWPaQjsm%U-@SVi3nZtG9s9-P{XPpR z=Cz3;#Et;ifDz_&^1&fe5)xj+Fbl|Lmv2vH!sYkM?BOLB0A`6I!h#5bIh!sjZvr&% z*=cbJ7zm_`^BVaExTakByQ1uS^@`VOj$6tAY5ZsVieuytOz2;AY4sTmm)kmW$0BM- zd`ng}{Y+<)kX~Lq|1oH-PvM}sT|g6%cx3EHesK
1}v9d;lmJseCAD2Q O3qCQv$gR@y$Uf>z$ zbbl@`n88A)tT1l{@b(Vv@6xNjUY{On+)A;bLrvJ7#nWNAoYS=jz1xQUwW7moX|8yC zSZ-ck@94~#!Z%o;;s>fh{HBFHamO`KRkx$;Yc4=Ybf#qLt>D# e|J3#dE9`wQ$`jiD(R^qt|IPS`O38<>Lo}d|an%p`k&E zk4iv5LVw=B>P)T|f23s9*d1Ln-1$V^F;k~vDSdfi1z}Uro(oHKwX}HZu)w#irj8jq z76L@i^$O7r;en|P!*;B#EF-(V@hIvj2pd|B*h%adTlmnoy%gb@mP?&<+vG#3;^Dux zP{%UIF}zKD9{H>Im< kV<()7=rEq!{ogq zJx0xxa&zVa*TPO`kp0Rld_taMG #fbZyND>>dg^4U!yHZO2&bh5 zoIb%YjuEEk3%-aPOrB1!yu!YoUx^c6`Yk WnsN fielr+R-yoV2Izu{C0)*HQgrTohHkJiv_GO%0amxA56`K?$Heeuri z$x@==b&N5$U>j3`^uuQiawuL%|07zb^~*WyELtAsF!p9t)DLn2Fz*Su@gtAZ<$?aK zu%A>Fe4YxfkEkfIpP3rUn-uLO=;6uCMrNO|4dwkQ6bR0d1Gbr}1{ `3wd6U$4dJF5r*$fvI8F}euT9mYm z49i1O9$juucpbUAt$ Un8lf@8mI zd@K|ge;PvJ;^H9@`2L=+0JJ8GVkm3>bF!%H(FzuTlr|#q1;E+4r{fWK@@4k}{w!4E zMvpd_#!ZtZX7+4r!o1Xb%J|{KOAiine`0*e1L9dOy~Lm=ayH}XQ(=YBt0``VBD_>H zyfaf%{bxw02#Yz9P%SogFriegF?xXduX*`p|IJjv)L?vRphm7aR|8R=if-DoXFZbd z0){?&`ZVf~?O`q!f^tL|9&98AEQ&iFNPRt^Vlv`;HjeA8Qrhvq55C7EM0UnbtReq; zp{3Yc$qyF{Cf`qH{F{AJA{917ILrh%(h3HW;e$m|GtE|wdq=Xqq_3Ik1aGPQpzNeS zo*P|FeN1Ap# S`v4l6Umguy;RF4QIBbu2N zK)|A7_7Ma4&7tH(tVJaX-&}z2IE%vVF3KVUa+~40em%zV;#+ uGIVa=Og!H$N6mEtKC)R$rxBs_-wFQN4$|Z!PTyZ`fEkBFV z|L$00uwe7anHtllqhXQKo-HRvgq@Z0YzsXi_$`pk4_-MT3pbvz2Fu`x+IU=)gglKY zJHA?N9NjS%+EAZd=7=A7iun-z=L&)oys#*TTC=I4;a|6G*9`^i?;?|OFw}#=na?7i z4hc~*G DHF^ohnV@|B>wqhVv*PfiTf86}`AJ(`T^ffr?z(m_(TAW8~tcZ^r8 z2vxH;V+xRD*H|iySy|p$+HnNrlp#z7s#W_X{ssMYQHh{9AY;3FlbaOWGKdkk*TgEk zw1Lp2=%A$LMXl3NFNemXP!EW*y9dhy$~}AE`TIBEl?|-B3&R|W$@w%z{in-S5$`xw z=@0X4$=Lke#i}x*$+$DIx4C%`hF^=GE5Tb4FN}Nx_W-UDgY#bASk_iR04J*R8XigZ zY1}QsLFJ{E7)Cw!2H!tmTn--Azn*P*rw}Xhx0s8K8cA*3qkH#JBSye!ls@A>KL7C_ z@UdksHkbmaNB#Pj4`5FtP||!=VHt y~+Iyz;3fx?X*rj;^b{L7sd z=?wV4OHcQSm~SBkFFe8hx_j-%siOiEGs{RZ?hLxnE@6D^U804g73pGILvlaxs;7m! zYUBNo2Ow9kt?TiDu9C*^!R`W3Kj@>BS V`^01!PYl~J}q-ZV2(-*s~st9Wx3>BmU*ft$X(zhH{=vaN@K@9&&e_7D(V?- zPz_qt!qCf>4|nA$pGJ?~Kt C55 zEk+nlNquJ*F={x{Kj6j_5o?Rnj&^zN(!0Mg>ZjE4dsbkExpA56a>P|ii&U?lKYw@A z+p(JUAvntW7*}4W8Sa^Nn|VyGHc5-ck*uT=&rXg1RNDiB0P*2rms6}61kxp0^7IT0 z4e#6(yln=CW7!_b0RCDje)k;fVb1@%1baNj)6t uX*PQd zjPkt Ef=epTOZg7j2zRcn$!GYZ znRamEW`-UDnspIe=jHuBrp`Q&%XRDfYO6HM+$4l3g$5}q38_RHNF_r^r9=uP8kDIC zm6Q^Z+NGjFNrsRlDUB#XlO{t0m3TjQ`+aiuALlvG*?Y(D{$2OF*7{CMkB(`~7(oa_ zhopQje%6z-Hrp+_x$h4@awM#vfWm;_ I# u6VO;0PT}l9JmQn4iIc7#j(h< zd*#BH@e3jh&>Ng{=Z=sXN$1E*1sf$62TsKZ0kP~t+;dCM=_ek5jS2euFF*bs5)lyx zzE14t1>Bar*TME9NG7g&aYjg?08JA&L|lwQm|q;8;_1`A1IC(u4MEkFe_q%T>7D*k zTR}mj Q0|xA1x)uG)>E_Mg3t63iPfocOGkQT@b33C)Sq*v|EQNp}H#=lXJmZKui}K9qFVn9Q}5QK7ff z!A zqcJU_Df2x_IZB)L_W zlwx3UKCjU646?O)p?|q`WEj01f0|;TyuJ_liaQvn0Z1=bI4%fec?L*Q*DhVY)jbT# zvA+v}p*Y{xmd hXp8u8H8q9J zl2a%@`Yoy+Pp?}|BYfNhWc+!mUJc%^H=`@|+!>{*DePk$_c)ZOMb@v<51>jrf8ubW z? D^c*)vnF z3%|?to @j0tA>P$X@Yhd6&!Sx>_7(RJm%;yS1&J%(0un|=ca+Zu5udST| zIWTf$Z$&TEIK6Ur_8qd6bvWd;6D`2oMb1`LK>KEZaogdU^8`VZ?schov=rCIdFQd_ z?(A8!J1d!fX3xHCn-~|DLJC^-xzP{^rQPoTky{I3(3mhjYw)P9v9rJl51rWvj|#Fc zZpgb{^YyYq6sM8i3^b8QeF4*ZRVdl&D-osKO;l7Nkdx1N$hAl$kHM4M9Y^DM5$^$> zej=Iz#nU}s_lPPw$`%=dJiZPM3Jin*dO3xnj@aMCD5|SWnDZZB{R8F|${Fu!u>A9~ zGGA_8z%Kf8K`2NNY~_b*$S8neea^figM?dg=9F+NC=<0o&JkimgaPl-*J{-M{zN-B zB&uA%OVDHHft>^rGf6RK-QFCH>V90|bzGucX-R?sz^i=6Ke<|r796`j>+La`qV!U{ zXA|*&qYo)7e+N)rP0e0TMnYn)wRKRyBO}rKPVD)nF`@(nB_|6_5OtoYf1}ST9Z$hP zmLx$&6E?q}Xt$k?YSw6G)vNvcrQ+vpg^JC}nj$|Xgl|$e@IJ4h(Gbo_NUX2^Qoh0m zXAb99i*J&SAe%n*>Q%Y$@_=azxhYugzAID~<@28e@iQR$V=J${{ cuH@vp60|)@chgi)x4NMJ;o>$>X4~9v0--`gT(R^q8)tG#y;{z*9BU_lh zo4cNZTG3{;ZPIltJN$+1kdMd#_d+7IC%qUHpGk}L16)%qqZM4mVv3GmQtE{r5~gNa z6(qHG?s5?=J6+h^gNNDC`?yl0>N&|u1OjL}_9zVqZ9szDty?#nsN}df-s7?r+6*O9 z>kQ=grY$d#*G~Hud5iiZZph#_N=?nw-29f_7bNGB=M5os;Ec#E#W|;NVgLhJew|~< zYftC-Tk`NAYN3cBIO^}`+VTiEDl>C(<@Zi~5AUdT(K$k`T @A4MR$@2rRLo0ft67ZwFw#cVWo1Qncp9A2u&3WELN ziDERdbdgE+G5~1%ubS##!liJ7Jz!lDBg_$?(ZA=Zt}k7FpRbq4nL0|3tuqe=Dw!Xe zKZw0eyYsFTeIk>TOGf1mI}q} EKzzXsoVbad%gDU1vhHraI-M}ypwg1{lBsTH*#zeIfV zK~}fXGFKEoVob9-yO0Z4&EvN+fCmzc6Z?G=#Tcee*vr7Lb1{`Z686jcG}!ZM4rZ$s zu)@A?HwAmyliBiE?-AE)ruV`-!mSm3P*GJi62bg00-dUxSVo7=llnw9TU&T3cQ}c{ z?Y%z;Bt4GUUcK&?l*A&fU3#sd>%Ud^>CXEZ0xnWhs?s~u+grZBtE(eEVHoKjkg}gv zZb24K5A*5MCt|+wlZA=*=-xd%G7^t{%hG_O$3COBOo*YSIQo6%zR1X2O(VC@)^>IQ zpP%C#xjt&aC}Gb{p0|7C4wxlLZ&=LjS?R4`VbRl?&8h-fAU7ATk zh>a|3z|+l(kbG}HTO{6XZ^04mWhOp8EdY|hND$xuSqukDOREe@+VJ6t5UC^e=;4D! z?iAekku8K3FicG|G|XHe!g}-+rY |nGk4vvmZeLpS# zix(l>@%C z%0#rKC>9nJXX<`yhSVKNxeYa z4PWA$lq6iDx-) }`EDKlRxszA-TW$E-=g9hB^@V7 zcdG%xZ(Hm4n@)nmhXkQW?OFPq6gfczFs$dLj?=5vD_2Hy=^%W5NSL+tbkkT-l*NJW z*N8P*Q1g?WLRQ Rx-apKD#|1JeiSX$0LYV(-KPijB| zYCal1In9Ha5j$UEMbz5nYM3bakh&34ut~-yCNph>Kws|z?h)+o{Z^mVZZfxdyZP+d z(pg Gl@M_?JQAYCddqB+iOmLrx|@b$ag Rq%hF^~F2N0s20!Zq&jdy{J15-ZY)|J2Ra7CIvX*d2`vqZe2iDuZ}|1*UyMAS$@ zoH`Lw=AQ$g4ZqRav-sGgfr?%zJy02PMN#W 6;OZBgU-PYjydGMvfRkQ~18@;#-d3 zjQ&do0M94?e1*^1)aOZ+){t(0KchnAi4)7vH{nLW@_cN&6IA5Nl^Zsqul}f}w4#|+ ziYX-i;83WErlX|So-rg5?CI#l_5b2MrP~E2+V3LF_3GBQHzXkZ=WU#OZ~i6lBY}%b zZECQ=q`SUOa!0@*P}9Ib(GWYMc#mrh6$@nqAS6A4-Sp8i_f?ofWZKU75(2chAkya& z^#I#=*fqtKNn9Pw>P&=4ENDRhe`%JoYa5ZvqYDgKW_ryk3WWi;M31bKob*Rg9j91f z9qb@6Y;3LMn2N>090AIc!5`emneS5|D% SrR$RUGh*JD0 zrQ@ALJAG-Q@l0*@MRmo^Ct0@V@l?CD#{<&;PhB{?w`*TjNgBiQ6xk_7%O_-tEiJ zw^5oZs*(LQV!!xHMMQ5ZIU1<1yUz1mdU}nm6>|Ze-I=!gAd*V|f%Q1KW`l1sk-ej@ zU?eP?@}6&)piXC0EKir=aiS%Nn`3Hqel@xc=c0q?^f{X~M+N#AM<~smajNI@yq7oL zFz_SnU$AgtusAzETCI0=oQ*BRaoAY=zIy#ST4sMNB pI(0X0mj=S zGY-XM;wVG@*SchTB=Kfv-|Ah%l8V+}wX(+IBP1AFhSXgzKYomX%a4xs#r=Y>A+MmI zb|`lq7pfHvjhNS|RouvHMJ{~S@;|B7&irsZTf9YJZX zK6@k`{){(IGf(Az4i<|;r#M%(a=huEI3dgM=CPWZCC{J7q>Bkwdgl#W4I0+>^zmRQ z(b8JWlml4NMs33d3vg{}Dx|H0t)h0)*p7=R0e~{UGa(NEnpy~^fI-O@k(q|Qq@tn% zwJZZXwWH3vNUPT}EjhZjyy@poow{)OvP!1QXk9%$LP&)8vesQJh`|dM8(U?)t0RqO z%Oan_m%LDP2^l=Yv~hMJ^kD^-@(o#n(q_@$GM31GF|vz-3`P2(G^6mlk%`G%*(>;c zWEXdx<&8OZX2o{DBcwlam7Tu5z5+wv`sxX~x?(#F)i2I_plJ->U-Vi+&l~bkOtIBk zZ0vy9pkCkmJ_dx$pMWOMgwq1{L42mRKyTEp)fF(rj|~kCG&K55;pmsROg0uBCytSs z8cKW0@ZmF+B{qL^3}6Slrcp#_=$-Lxz5fS@?vk2tDC;(^Fip+ACA|UKxi@E1ts?x{ z`MRB31F0*;q^8pxJoi%-#ML6g-B`PLg2 4f z?C?UX^*E64IK%z@4X7)Lq%q^h364P8KfleJLw0M?(E-j~x_9P|D eSbK%*6W+(pJ3}DmTBmFD)JpqzZt??_GzWvs$=NuJQ_pwT$_xZTP^x9a50i+vprP z@h5!xls|pSTCl0g5{iMyLjj+5n?z3j*c|bJogRuYVf&RPOrVZO#~ V(&-Lhm9DhB7uG}nQF9O2=|F2izK@t