From 27206ac2c554cf566c3e3fe38c48678bd24ed7eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=B7=E5=8D=A1=E8=87=AA=E4=BF=A1=E4=B8=80=E7=82=B9?= Date: Wed, 10 Jul 2024 09:45:25 +0800 Subject: [PATCH] Initial commit --- LICENSE | 21 +++++ README.md | 1 + README_zh.md | 1 + banner-0103.png | Bin 0 -> 41784 bytes fuck_down_test.py | 67 ++++++++++++++ qq.py | 223 ++++++++++++++++++++++++++++++++++++++++++++++ qq_gundong.py | 53 +++++++++++ requirements.txt | 5 ++ 8 files changed, 371 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100644 README_zh.md create mode 100644 banner-0103.png create mode 100644 fuck_down_test.py create mode 100644 qq.py create mode 100644 qq_gundong.py create mode 100644 requirements.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..04a51c6 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Shuakami + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b3294d0 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +
QQ Chat Exporter Banner # QQ Chat Exporter [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Python Version](https://img.shields.io/badge/python-3.7%2B-blue)](https://www.python.org/downloads/) [![GitHub Stars](https://img.shields.io/github/stars/shuakami/qq-chat-exporter.svg)](https://github.com/shuakami/qq-chat-exporter/stargazers) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)](https://github.com/shuakami/qq-chat-exporter) English | [简体中文](README_zh.md) 🚀 Export your QQ chat history 🚀 [Features](#features) • [Quick Start](#quick-start) • [Detailed Guide](#detailed-guide) • [FAQ](#faq) • [Contributing](#contributing) • [License](#license)
## Features QQ Chat Exporter is an innovative tool developed to solve the difficulty of exporting chat history for QQ users. It offers the following core functionalities: - 🖥️ **Intelligent Window Recognition**: Automatically locates and focuses on the QQ chat window - 💬 **Precise Message Extraction**: Accurately identifies and copies chat message content - 👥 **User Differentiation**: Intelligently distinguishes between your messages and others' - 📊 **Structured Storage**: Saves exported messages in an easily processable JSON format - 🖱️ **Automatic Scrolling**: Simulates mouse scrolling to retrieve more historical messages - 🛡️ **Safe Operation**: Built-in protection mechanism to avoid accidental clicks on critical areas ## Quick Start ### Prerequisites - Python 3.7+ - Windows operating system - Recommended NT QQ version 9.9.10-23873 (64-bit) ### Installation Steps 1. Clone the repository (or download the repository's zip file and extract it): ``` git clone https://github.com/shuakami/qq-chat-exporter.git ``` 2. Enter the project directory: ``` cd qq-chat-exporter ``` 3. Install dependencies: ``` pip install -r requirements.txt ``` ### Basic Usage 1. Configure message colors (in `qq.py`): ```python my_message_color = (0, 153, 255) # Your message color other_message_color = (241, 252, 247) # Others' message color ``` 2. Run `fuck_down_test.py` to get the non-clickable area 3. Replace the non-clickable area parameters in `qq.py` ```python # Non-clickable area # Please run fuck_down_test.py to get and replace the following part avoid_area = [(1248, 589), (1299, 589), (1249, 611), (1299, 613)] ``` 4. Run the main script: ``` python qq.py ``` ## Detailed Guide ### 1. Environment Preparation Ensure your system meets the following requirements: - Windows operating system (Windows 10 or higher recommended) - Python 3.7 or higher version - Stable internet connection (for installing dependencies) ### 2. Installation Process After cloning the repository, we need to install the necessary Python libraries. The `requirements.txt` file contains the following dependencies: ``` pyautogui==0.9.53 # For simulating mouse and keyboard operations opencv-python==4.5.5.64 # For image processing and recognition pillow==8.4.0 # For image manipulation pywin32==301 # For Windows system interaction numpy==1.21.5 # For numerical computations ``` Running `pip install -r requirements.txt` will automatically install these libraries. ## Configuration Details ### Message Color Settings In the `qq.py` file, you need to set two colors: ```python my_message_color = (0, 153, 255) # Blue other_message_color = (241, 252, 247) # Light green ``` These RGB values determine how the script identifies different message bubbles. You may need to adjust these according to your QQ theme. ### Non-clickable Area Run `fuck_down_test.py` and follow the prompts. This script will help you locate sensitive areas in the QQ window to prevent the script from triggering unnecessary operations. Steps: 1. Run the script 2. In the QQ window, right-click on the four corners of the sensitive area, starting from the top-left 3. The script will output coordinates, update these coordinates in the `avoid_area` variable in `qq.py` ### 4. Running the Script Once ready, run `python qq.py`. The script will automatically: - Locate the QQ window - Identify and copy messages - Save messages to the `training_data.json` file ### 5. Precautions - Do not operate the computer while the script is running to avoid interfering with the automation process - If interrupted by special content (such as images, files), manually click on another area to continue, or press Ctrl+C to restart - It's recommended to use a plain or high-contrast wallpaper to improve recognition accuracy ## FAQ 1. **Q: What if the script can't recognize the QQ window?** A: Ensure the QQ window is active and not minimized. Try restarting both QQ and the script. 2. **Q: How to solve inaccurate message color recognition?** A: Fine-tune the RGB values of `my_message_color` and `other_message_color`. You can use a screen color picker tool to get precise colors. 3. **Q: How to handle a large amount of historical messages?** A: The script is designed to automatically scroll and process messages. For particularly long chat histories, you may need to run the script multiple times. 4. **Q: What to do if the script suddenly stops during execution?** A: This may be due to encountering images, special chat records, or other interfering elements. Please manually click on another area of the chat window to continue, or press Ctrl+C to terminate the script and restart. 5. **Q: Why do we need to run fuck_down_test.py?** A: This script helps determine the coordinates of non-clickable areas. These areas typically contain elements that might interfere with the export process. If you're unsure what this element is, please check `dist/fuck_down.png` in the repository. 6. **Q: Why does the wallpaper affect script operation?** A: The script distinguishes messages by recognizing specific colors. If the wallpaper color is similar to the message bubble color, it may cause misidentification. It's recommended to use a plain wallpaper with high contrast to the message colors. 7. **Q: Can I leave it unattended during execution?** A: During execution, you must watch it run. Sometimes it might click on images, chat records, or other things that interrupt the process. You need to click elsewhere to continue, or use Ctrl+C to restart. 8. **Q: How to ensure all messages are correctly exported?** A: The script automatically processes visible messages but may not handle hidden content. It's recommended to manually scroll to the desired starting position before exporting, and check the output file after the script completes. 9. **Q: How to use the exported JSON file?** A: The exported JSON file contains structured chat data that can be used for data analysis, backup, or import into other applications. You can use Python's json module or other JSON processing tools to read and process this data. ## Contributing We welcome and encourage community contributions! If you have any improvement suggestions or have found a bug, please: 1. Fork this repository 2. Create your feature branch (`git checkout -b feature/AmazingFeature`) 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`) 4. Push to the branch (`git push origin feature/AmazingFeature`) 5. Open a Pull Request ### Contribution Guidelines To maintain the quality and consistency of the project, please follow these guidelines: - Adhere to the [PEP 8](https://www.python.org/dev/peps/pep-0008/) coding standards - Write unit tests for new features - Update documentation to reflect your changes - Keep commit messages concise and clear ## License This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more information. ## Tech Stack - [OpenCV](https://opencv.org/) - [PyAutoGUI](https://pyautogui.readthedocs.io/) ## Changelog ### Version 1.0.0 (2024-7-10) - Initial release - Implemented basic QQ chat history export functionality - Added automatic scrolling and message recognition features ## Disclaimer QQ Chat Exporter is an open-source tool designed to help users export their personal chat history. When using this tool, please be aware of the following: 1. **Legal Use**: This tool is for personal legal use only. Users should ensure they have the right to export chat records and comply with all applicable laws and regulations. 2. **Privacy Protection**: Please respect others' privacy. Do not export or share others' chat records without permission. 3. **Data Security**: Exported chat records may contain sensitive information. Users are responsible for properly safeguarding and using this data. 4. **Liability Disclaimer**: The developers and contributors of this project are not responsible for any direct or indirect losses caused by using this tool, including but not limited to data loss, privacy breaches, or legal disputes. 5. **Unofficial Tool**: QQ Chat Exporter is not affiliated with Tencent or QQ. It is an independent third-party tool. 6. **Risk Assumption**: By using this tool, you agree to assume all risks associated with its use. 7. **Copyright Notice**: This tool does not store or transmit any chat content and only runs on the user's local device. Users should ensure compliance with relevant copyright laws. 8. **Technical Limitations**: Due to potential QQ updates, the functionality of this tool may be affected. We do not guarantee that the tool will work in all circumstances. By using this tool, you acknowledge that you have read, understood, and agree to abide by the above disclaimer. If you do not agree to these terms, please do not use this tool. ## Project Status ![Project Status](https://img.shields.io/badge/status-active-brightgreen.svg) QQ Chat Exporter is currently in active development. We regularly release updates and bug fixes. Feel free to follow this project for the latest developments. ---
**Like this project? Please give us a ⭐️!**
\ No newline at end of file diff --git a/README_zh.md b/README_zh.md new file mode 100644 index 0000000..1201200 --- /dev/null +++ b/README_zh.md @@ -0,0 +1 @@ +
QQ Chat Exporter Banner # QQ Chat Exporter [![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT) [![Python Version](https://img.shields.io/badge/python-3.7%2B-blue)](https://www.python.org/downloads/) [![GitHub Stars](https://img.shields.io/github/stars/shuakami/qq-chat-exporter.svg)](https://github.com/shuakami/qq-chat-exporter/stargazers) [![Build Status](https://img.shields.io/badge/build-passing-brightgreen.svg)](https://github.com/shuakami/qq-chat-exporter) [English](README.md) | 简体中文 🚀 导出你的QQ聊天记录 🚀 [特性](#特性) • [快速开始](#快速开始) • [详细指南](#详细指南) • [常见问题](#常见问题) • [贡献](#贡献) • [许可证](#许可证)
## 特性 QQ Chat Exporter 是为解决QQ用户难以导出聊天记录而开发的创新工具。它提供了以下核心功能: - 🖥️ **智能窗口识别**:自动定位并聚焦QQ聊天窗口 - 💬 **精准消息提取**:准确识别并复制聊天消息内容 - 👥 **用户区分**:智能区分自己和他人的消息 - 📊 **结构化存储**:将导出的消息保存为易处理的JSON格式 - 🖱️ **自动滚动**:模拟鼠标滚动,获取更多历史消息 - 🛡️ **安全操作**:内置保护机制,避免误触关键区域 ## 快速开始 ### 前置条件 - Python 3.7+ - Windows 操作系统 - 建议 NT QQ 版本 9.9.10-23873 (64位) ### 安装步骤 1. 克隆仓库(或者直接下载仓库打包的zip并解压): ``` git clone https://github.com/shuakami/qq-chat-exporter.git ``` 2. 进入项目目录: ``` cd qq-chat-exporter ``` 3. 安装依赖: ``` pip install -r requirements.txt ``` ### 基本使用 1. 配置消息颜色(在 `qq.py` 中): ```python my_message_color = (0, 153, 255) # 你的消息颜色 other_message_color = (241, 252, 247) # 别人消息颜色 ``` 2. 运行 `fuck_down_test.py` 得到不可点击区域 3. 把不可点击区域参数替换到`qq.py` ```python # 不可点击区域 # 此部分请执行fuck_down_test.py获取并覆盖下面的部分 avoid_area = [(1248, 589), (1299, 589), (1249, 611), (1299, 613)] ``` 4. 运行主脚本: ``` python qq.py ``` ## 详细指南 ### 1. 环境准备 确保您的系统满足以下要求: - Windows 操作系统(推荐 Windows 10 或更高版本) - Python 3.7 或更高版本 - 稳定的网络连接(用于安装依赖) ### 2. 安装过程 克隆仓库后,我们需要安装必要的Python库。`requirements.txt` 文件包含了以下依赖: ``` pyautogui==0.9.53 # 用于模拟鼠标和键盘操作 opencv-python==4.5.5.64 # 用于图像处理和识别 pillow==8.4.0 # 用于图像操作 pywin32==301 # 用于Windows系统交互 numpy==1.21.5 # 用于数值计算 ``` 运行 `pip install -r requirements.txt` 会自动安装这些库。 ## 配置详解 ### 消息颜色设置 在 `qq.py` 文件中,您需要设置两种颜色: ```python my_message_color = (0, 153, 255) # 蓝色 other_message_color = (241, 252, 247) # 浅绿色 ``` 这些RGB值决定了脚本如何识别不同的消息气泡。您可能需要根据您的QQ主题进行调整。 ### 不可点击区域 运行 `fuck_down_test.py` 并按提示操作。这个脚本会帮助您定位QQ窗口中的敏感区域,防止脚本误触发不必要的操作。 操作步骤: 1. 运行脚本 2. 在QQ窗口中,从左上角开始,依次右键点击敏感区域的四个角落 3. 脚本会输出坐标,将这些坐标更新到 `qq.py` 中的 `avoid_area` 变量 ### 4. 运行脚本 准备就绪后,运行 `python qq.py`。脚本会自动: - 定位QQ窗口 - 识别并复制消息 - 将消息保存到 `training_data.json` 文件 ### 5. 注意事项 - 运行过程中请勿操作电脑,以免干扰自动化流程 - 如遇特殊内容(如图片、文件)导致中断,可手动点击其他区域继续,或按 Ctrl+C 重新开始 - 建议使用纯色或与消息颜色反差较大的壁纸,以提高识别准确率 ## 常见问题 1. **Q: 脚本无法识别QQ窗口怎么办?** A: 确保QQ窗口处于活动状态且未最小化。尝试重新启动QQ和脚本。 2. **Q: 消息颜色识别不准确怎么解决?** A: 微调 `my_message_color` 和 `other_message_color` 的RGB值。可以使用屏幕取色工具获取精确颜色。 3. **Q: 如何处理大量历史消息?** A: 脚本设计为自动滚动并处理消息。对于特别长的聊天记录,可能需要多次运行脚本。 4. **Q: 执行过程中脚本突然停止怎么办?** A: 这可能是因为遇到了图片、特殊聊天记录等干扰项。请手动点击聊天窗口的其他区域继续,或按 Ctrl+C 终止脚本并重新开始。 5. **Q: 为什么要执行 fuck_down_test.py?** A: 这个脚本帮助确定不可点击区域的坐标。这些区域通常包含可能干扰导出过程的元素。如果你不知道这个元素是什么,请看仓库内的`dist/fuck_down.png` 6. **Q: 壁纸为什么会影响脚本运行?** A: 脚本通过识别特定颜色来区分消息。如果壁纸颜色与消息气泡颜色相近,可能会导致误识别。建议使用与消息颜色反差较大的纯色壁纸。 7. **Q: 在执行过程中可以放手不管吗?** A: 在执行过程中,你必须看着他执行。因为有时候他会点图片、聊天记录等等这种会打断进程的东西。你需要点别的地方继续,或者Ctrl+C重开。 8. **Q: 如何确保所有消息都被正确导出?** A: 脚本会自动处理可见的消息,但可能无法处理被隐藏的内容。建议在开始导出前,手动滚动到想要开始的位置,并在脚本完成后检查输出文件。 9. **Q: 导出的 JSON 文件如何使用?** A: 导出的 JSON 文件包含结构化的聊天数据,可以用于数据分析、备份或导入到其他应用程序。您可以使用 Python 的 json 模块或其他 JSON 处理工具来读取和处理这些数据。 ## 贡献 我们欢迎并鼓励社区贡献!如果您有任何改进意见或发现了bug,请: 1. Fork 本仓库 2. 创建您的特性分支 (`git checkout -b feature/AmazingFeature`) 3. 提交您的更改 (`git commit -m 'Add some AmazingFeature'`) 4. 推送到分支 (`git push origin feature/AmazingFeature`) 5. 开启一个 Pull Request ### 贡献指南 为了维护项目的质量和一致性,请遵循以下准则: - 遵守 [PEP 8](https://www.python.org/dev/peps/pep-0008/) 编码规范 - 为新功能编写单元测试 - 更新文档以反映您的更改 - 保持提交信息简洁明了 ## 许可证 本项目采用 MIT 许可证。查看 [LICENSE](LICENSE) 文件以获取更多信息。 ## 技术栈 - [OpenCV](https://opencv.org/) - [PyAutoGUI](https://pyautogui.readthedocs.io/) ## 更新日志 ### 版本 1.0.0 (2024-7-10) - 初始版本发布 - 实现基本的QQ聊天记录导出功能 - 添加自动滚动和消息识别功能 ## 免责声明 QQ Chat Exporter 是一个开源工具,旨在帮助用户导出个人聊天记录。使用本工具时,请注意以下事项: 1. **合法使用**:本工具仅供个人合法使用。用户应确保拥有导出聊天记录的权利,并遵守所有适用的法律法规。 2. **隐私保护**:请尊重他人隐私,不要在未经许可的情况下导出或分享他人的聊天记录。 3. **数据安全**:导出的聊天记录可能包含敏感信息。用户有责任妥善保管和使用这些数据。 4. **免责声明**:本项目开发者和贡献者不对使用本工具导致的任何直接或间接损失负责,包括但不限于数据丢失、隐私泄露或法律纠纷。 5. **非官方工具**:QQ Chat Exporter 不隶属于腾讯公司或 QQ,是一个独立的第三方工具。 6. **风险承担**:使用本工具即表示您同意自行承担使用过程中的所有风险。 7. **版权声明**:本工具不会存储或传输任何聊天内容,仅在用户本地设备上运行。用户应确保遵守相关版权法。 8. **技术限制**:由于 QQ 可能进行更新,本工具的功能可能会受到影响。我们不保证工具在所有情况下都能正常工作。 使用本工具即表示您已阅读、理解并同意遵守以上免责声明。如果您不同意这些条款,请不要使用本工具。 ## 项目状态 ![Project Status](https://img.shields.io/badge/status-active-brightgreen.svg) QQ Chat Exporter 目前处于积极开发阶段。我们定期发布更新和bug修复。欢迎关注本项目以获取最新动态。 ---
**喜欢这个项目?请给我们一个 ⭐️!**
\ No newline at end of file diff --git a/banner-0103.png b/banner-0103.png new file mode 100644 index 0000000000000000000000000000000000000000..7206465186cb5dbeb1cd7fc66a0e13f25fe73fad GIT binary patch literal 41784 zcmb^YbzD?y7dVQKg$cGudsIL`x`&i*kZzCZ z>23~wU9}NYvx7kJeqevjh11{>K_HjdOchmOs?t(WeQQfPT?1=9Lpmo*8?YJz;dX+8 ze_9&CbRRicT3FdZop>mZS3tqPvA5|dd9OV>y|`@+jG*#DBBx8iUp$m1FqjRLp5D>X zkNwYPv#@`_m7TIyOn60p{_HFzX#Xb3a4GJYg&YGn^I zw0k6Et#9{8K-bF3(Dsq3)gu85U43)#e|y76vbF$_;UhU)Ya>HDJ3thGp#XEXFnmP& zh>@EfOQD77zfd^7^WRv}|Np@Kc-g;L_5YBIFJLfRQ@y{Ow6deqJ-%a)Wj`Iz40>Gy zEH4@UiPg0|cJbMR{~_-Gy($8a^Iv-S7egmqoBu-t!D8sKwpiOLT3cK23hG)p z=stQTZ)jy;XlrOo#ZCXeEB^2BJ#%IMvj#Y|e`z!QU#G|`rch`Bfxt7xgm>eatO_9WrCsH&>?K3jNIKg%zC$!Ac_N_w!n_+2q?soiUfrT00~Cb{1& zLnsl49*1w1B=`2)*NB5slaHNsy{8jT_ij4$XG+AxOG%pP9cjvNOM~YH_?5`7p@ExQ z#$hzeTV!DA=hWRqm4eYK0hQ-?F*4tXLt~!JPQ%|qnYgW>mvEeac;R8;)nC#oDpp=m zHI=f~xWxchC@q?FUOv1-;f0g&AMAzN)Vzv{}xylt}mzn#~U4bT-R?|*Q47Vw3*OHKX&*VhGRr<0eTDxX`z-X zqM@H=K3bqb8Zs2$C4Jx5#gc)B3k@)Wwv`=$RpWXAe+X-e^wS6%Sko28ncgSE8ps0C zoj#06swm-8lv)#i{*y3K*xj^Fq#=;~dLTyonS?blJzQ#OPR5)eoEd%*Kng>p6|iCY z!gY}ZHd(broOzxu{3(;ws+zP9JI`V9i6Xy_aU1M@@RHliRKZ;JMM700OdRTFrdf42 zFkUjXhpRaX$kVH0VXvRAr-Pl24Sp_i57`w~yEzjIxGi}KhT+kj<%q`hsI(!ChHE=D z>7mV=hv=&V;-Vd-(o~F0rqIh^ukCj&_?I>|-D&RI{PD|)a&_3>5Ii`nu>)|fpTT)P zwKaD=r@>d0cBJb;@O>b0514_x!}nk~3})ao48tl{w&w4p-qM>_EMhf zR~>ib3ZI;A#>5L)x)?rQbSE9@BUI)#GL_U2S~GVCI62H~$~b&wNw2ea^S^u(tctR~ zIbst{T6xHXB*BkwAhkHJDTf_s>jlZ-P@>@VX96YojfV@gP4l zjiSX}f7ixHLTkhnu;=n+rsg#vtSvCV!WzObAsgKL8f#cHx|wnpCG|imtvE}CQYEB%FWRoaL~tdw!$_k;Po+Xvg@ z1W1CYe@=C#e0Y$&B#n{BC@bZkSI`WLc7qqLt`hpYel-M!c5zbx0RK*|m&(#eokZKl zqU*PNi%h}vb8};xG*}s4V)i;m0@gshuH7-eGP@Y)0-Qy@DlrQ%Z;SjycXxt;tD>$h z-4b@RkHIS56j3Yf)XVHFQSJQU900{>;}25CuW(N3kxNphUpCjs)z;9D$T!Pn1wee< zUea$F8c<5=?;`Y_k>UVIR7x`LIg-&ri5Ce7*tP6moB5%G8!wt+IO%5wrIeU9T3|_% zW}RMOogF$vN+a`cgMu4M0oAMbT&T%Vi`8gIXO=VkAkV3_BUDFh)t>$AlrM_WhQWj* z1EjzT(2WtF&8in;kk^IL0h4NgB47zJ=^0X#z+UPTW7P#*3|;-uHx#oH_80s;c7tvM z{)(KzDSU6wJJM@KMOp-FY5u%VM}+hX!Gk{xg~NJ!7cw1LjGJGto=s6D;w1h)@sA^ z_k?a!w>WOXyRAog%%K;M?@pux*$)z{&lytxYedU2GSov|!Qy}xhwiT6Kd+ITSk9jF z++SsKPfHnv;bd#t8pxX`%=asL50uK+rdD|5a{sARU|cX~Bf{d|%Yt#S!z(|v1VOOThhB*Mbjle`rl3^Z77Q>T z;9&BiX4v71rNM>O+8li8OG_Qk5M5ZBI5EMo_~>uAxarJZvQCx^IT{XlG41u_di+=? z27w?KW70}|5rBlDH(uztuW*tMb6Ht+hwoj7-nnNnDQ+e>%6lt}#uDB&=By75_98bpRFyM!GQB^xcvJ7N5r`slwV9(@r%cRGS&O#{^WSvXTTI`wFZ4QI@inYS{%kj2$qX z<*bk=p_Th9yhwubi>FFg|0%+yuGBs(-&to&kunIK$b`BkO!Dwovkb>>OC2%1eOel; z`-LoaRD<$IX%i+(^z8;?Ie^|_GA2w}5gR^`g+gG$st&RV7E_`cml z2w=^N?@~h1RL2H=KdWe@dofQMF!#F4`ohZto1-u28=6l~Ma_`$9B*emjTJb{i12@Q zurSC;po|n;=-G;tKpX|m6Ce$vrJ9cUK2hxuIuj1&G*r+g!}rR2rz#g!G<~V&Bv@y$ z<|cM%=~q4_;bqMqxhmR0E=MdV{RDYQ_N-zt+Fyk1EA3|zwMklmn1#35fsBui z=0`??H0=p4FB=;>ZW=nsNLCF#xNc|~3IBG{C$UTDO)&q(H!Sc8J_tTV89ZP8;}`|+aQ*wAJmP{@pb^Txdtd6|jPyqcH4jIEbYf9&iSSt?nk-M3RJiYLLM z=9{%H()){}(xwP6!vT9IZc-1l)F9*by?fYfg@5|qYDKxVhP9%E_%NTGe=4)9;cl1T z8Y7E(KvJ+7F$Z(312M!a-Pnyw&DO(+G2eYctcRsAI^|fYnDTOJXcy;nsF7sE&qZ`( zT?c~+9c+=hGR4!?9 zue84}*6)0D0wKiK^0uR<&L;^fACk1EY?K2u$c~QZjiZH44+--cYq!$%zU_-bUWL1? zV=$AEj@DvaI4lNB!A+UfHqeEKEMmvZEUqs7RDLkHm>CwD85Z(TwLjV+^@CQ7mVacR zOk<}Qt2QU^eY^bh3y_>o2Qvm;16eJFJM5y!z!z_i`Bahk*CXE9hMfUF>Tg z(35FZMjtk>WJSdlq>)^Oj5F&WajLN`PZe@7)_!)lL6~^VMdcD(9b>nnY1WriX5otGasaQ6g$sH*&-82|D%*L zD2?PT&a>mJkoVgM0ax%YR)H$gZjlg}Bh5EHvCGnrNR2VG5oA!zzq$zdIywG?y-|g! z-0bq>#}6>Kb@qEzrBqA82u?`A$z3uHB6%wgzzp~5d~$1SUjOvzEm?cxVz5~?1Ji8; z9X0Zt*L3gbgV%?YZ(q*0rjcCYEg4GSy#2AgJ)PvOq5N^$<0fHVWxG-n8}j=lbt4b^ zrpP}DHE%~6W~TK_iV5p@Y#8a<2-ol+ERlyLI~=gTnG5$QJA72d|D+Gk*l}pltD$(ph*Hl8S1{XRC5Hb>&M z{bixS+aPaJ&MKf;xMI`0D=q&kY2_tvQ5ysI^}$GXH1l79MdG?#?9 zg-kbJOteleqD7Pv8F&t|TsX|1alhO`!5B z29LuH3ZM?JRXlaoKH4q0G3dK-K{|V&8#BeXF1a|IzO?wgRgc?!Rv!D4yU_e|^IIC@ z(w4iWq?)r}IF(HtHDK9$o4_dOnVpY)mY(7dTJ z`l9ar69vzyUl*X=B#rj35v4!|f@4W%arGQloFJ+o)p9f75ao8TV&Tg3&|~nxRiziH zA`_9&bb6Z3B7tM?gIyc#cnSU>nXd!y*qwvdrH!@!h+S7(77#N!VFUzX0*wr?$$0

%gm$6&~_?txb!uIITwd9b$ecyrzPdt1ga&h`ATMnO!PHtLGYy|aCU z=dR2a*UOYO^LTUq-AUXj6?gy29lw*Z1B5^Lo#^2B7nPa#dg6;G2TF)9>?wEmc`lEO zSA|b=xJ0qBJ^MM8qG9H7?cvm0|9Lq*08`*&Iha~}q`)5}bhcU*ZWZo&{`q{WPR6(2 zwAX_FZf-ug&bDTPFqOoXyi~EcA^ga696}3DRXDm#1z6}3#aXsok3rJv(ZS4f4 z&cF$;;X%8cj=1U0-k6}Z-m;VR;?fu(nToG}^Y_KFN_MgSNwq}+8_E`)gXP=dXWU!K zM-;8QjAdyzJK88^v?<8q36Y;tb?f`;rv2j8WgU9xLC+NFx9?}r1XM?eQrBX|7YUYS zJ=Q--pK|TdJD0xHaIOAwg?xH^rCm3VSXZ2_M`yqqR~`7aE^gC|8M~cfuUpMUTG{}N}z0T-( z#)lKSZjloS9&MuM*=jh;n|R({{=0iSvaN+Bn)AI3K?ECJXh9*#8OZMgHN+VzT%!V% zb=HxS>t81|EUuq1^K(4MhU7WYJjam_osa8)ePOMq!>`Al7_qWdnm&Y+IH>_73pFkx@%V=+1K>Ab1tFQ*};)pam%=-^pi zRaC@`OnW5IY~jeqJm$=O_-00TTU&c=$L;jM08vb3u0gJBr1|s^C>Da&s56e~a?rakOS;h7RT&8=v7!CYBDrIMBbPI@fT>ZC9Qif z4acR{?}HwxV@q^z|MR51usUa0O^#N@N@(rpN)JKnh0|0Ya^JdtOR$SGLKgnw{;&HI zD}7zH;PKtRkLgvO{oH(Vkj0#NzDnccWPeihzaK|;y1nLB&mR_pzxZGeP-#{X=1 zhAz|OLJGhrOy2nO%O&{3Pb<-Sb2p}g&t%YA&cJdP097#l{DQJTT10oW%V-f6+cdhW)9v%{a4R5VqXUW<2pRBM%4Y{g1*(r7s}Mi&0<{ORc% z@|n}}ZN=}w?nvm{8{6gfPw}*4#K7aTv({&E*Y?21QM(jC+AFZqY0JqI!2f)F*l&e4G=kERM@vhy)4N~Y)Z{GvY+DwtZBdj<(?2sOa4FbU5@4%+Z(5mVxL_r0WH1^jF^Gl4O@qC>mMRc^85;A9A=e`3m z#;<@bFrz)uza(fXB`J$J-MGe8JMXVd-|JCLq)o?jjPQg*-!t_vGZGOIa_O05e}=w) z_jgf1)_^?sp0J80X|y<8fPf%@su7vsD6%KUYk$p$;M7NFQomJrZ(=gmj-h ze~&=p02%scUV4tFT2H*VdihmuE_<@ER=%8X-^jC?1yj{ZT+ZVy#!@Mf@^g~|LU@Cz)9`@76Y+)~=D&ql|>V>Z5rYF`$s zI%YbeH>4iOXULtEyCF7rS#Pg2K55bG52Z=5dMQ0eY}Js#rxQei*wxodkAl9xrh7`N zm**F&oe2{cEBjoHS`PbbWtpB|ZxwhVi9XUvr^+OKKP~J+#&vpVvwd#HtKJ?WtzwT8 zrng2EjDEt8bxj@%Ul{%{2H1=BVXkt$8wrRN{L37g_vB(GZBd;D>J-R(l1}ol(S7yq&w4@N>l|2zi-dl9Tp))Q}W!}Tr*5y+-8Vx zc+jJ+WYeu(ZJ=cbMupU!cbd!pL)!f%&MG915FQbV8D&pq4b zD#2~r&G4*C?iHn>;{U_;@1Q~P_xwkO%t ziNO$wY+4kA3m(1Tf#Rr>pdyrBA~W&*Ji0Z=ReFUmSMQ#rcqb@WU0dBk`qu`#{L_%s z$sgPM+KQkpWR$)pUG2z0_&#ejhIl&Zl<(LVxO-^1W=i|l@7%h|4omOgC`$q)36~z-vBzsR(~X(%-5FV$XYE~jXGtA)EBa(H_UZnK zeH~vKMP?v{g>V5qjyW}jN z#4b@ym8^oGetg7q{jf=cT`h&m_;I?HXuqe-&suJwc31dQ48>a}`ZrCNe+Un<{==vl z^g}dL6jAEeg0qM|g^)DpV7y4S8= z4IZ(IM4}b7wBXq}Irivoa;@2OZ=6koh}kzG)rVriL&MPp~x5YFeRZQLHW zv#_^0@zW1rfaNRpI@ICe8f1M$p%^_Pzyf}G`YhK__OzH0+K+z(#82AaKafl5rYSK? z_7S2H^!B)p>B$Nub)9DnAY_#wARxGQ#pZ}TGA1@wNnSpAVW=!8F_Aw*Hi3=Nw1tSt z;FGPbEsO1xn6R+0Gpw&SM`fdlGQnhRx@~v4;bw17kAZx%M*s4k23Fng7Yz*!shF6$ z7wiDAjh&eU^S#Xl^)f4XJDkf{Ph#ud{rgn3v~6i6N=iy|-D!T2cg1h=ZYN&5eqBqu z&gPFm&F)-|w$xOuVM~PRV6iDFywI@afpi>4SE^7v0f$YZ!y>TH8w#++VYRybRli=w z6Z*ZBCpW)9$Q`dLYo==PKnL#=nW}UIde1#rJkYDjO@Em|69?5o_n>`U;Q6pQAvL%} zL65UL+qh-kQvPM9=M)qoMaCU{lR

%gZ(j?dxl63bwXifm~S~7d0(MDwSFy>H7=y z5mKCWY5rt<+RVo7u!?$^@JBq7FgQGKe%=DkX(y7SQqTvah=M_-V_)a|YHI3=s)YIe!96J5M`u{=&ZWZt0) zAL&FvPX4pgxFULL${@7cseNp0Y_emL#)2)j7gQLqmzOeXzXKO~wAMx@AwGLAHJ-%! z-W*)u!oAxT3PPWy|2P~Gc^+-fG}h%{Vqtmt;!5_?+1cibN+U;yd+J&#)MkVmBPA~G z8h?7&@5?Clu6%hkNOx^r<5Qav(w~62_|>=b!a_n$mNKtiC4Ma`Vl`@gShF)78^h;W zo1L9q&DL>aWo4zgqoe=xKZ4cJ4ACG`^$OcG3fcHv0%jwVj(8qzc)#+nT?dcL!A3u; z)AFl=wkvpe%2rnSjR8a%sFq0crVw(?nlT*mr%#>SL@X?F2TRN|=H|@q-Z=@>AXAaq zSIoG}x5C0Pjz_vxDa=8Pw=i0xL8F|zZ03CE>h0|v1GUc%B<4(ZU8*S)HmaQL5+zf5 z{kpKfWo^taD5yVQt7^TOrl9zDYacZ`yNYNa@e20Eq@*PEVw2#_g`o_vqV$(kv43Xf zvml+OMjEYQ0tDOhG8v4oVI9sOu^ux!%9YL#vZcipt0RWJUvy0sWc zz$^khM8wQ8D}CMmS8b<1mv(k`)FP5xw-yHr^*<)3q|mlN)}E4+M>|B&k7b{-69O3) z)$ji>u;5@hT0OA3x~~G99k8)bJ$?PWz~tAHjix{+l@Z%*zYFzC%zCMF^tb(u1^DC@Cmn93Gr#BLuQca&OjBRa-m08;Q&dTwy21 z``A~YV~q|+@wjgG5Ls9Mv6FR^jb;)C6JW}SFPdYu?%|P<#`gYo!N3KROGc=YTp*PY z6JuD&-QIRe*dxF;RgW)j#L;2d%L|wBdVo5C7h-`MEm}uUu$uS%`eUQYb`-R|vqQzl zrvr!De}(!Q0wd|m*UIVal*>ukCj+*+UZErAjd}d|F>D6z7~`OAVv?Dq^8L8YjNM?Y z9{$7fGtm3ya2g&D=-2FQiAZ`KWi_>EY8smR4<1l_Y)AtB%T;r=J%&|ssqUz@c4wL> zsGX6Tl2Wwc1Mb?a|MavG);Xh4C~A6o6+ywbfcnQzp4=xP$xBRpRMxRLTu~H3rhkA{g!J~%jdt}ick|KMN*+CMg?+PoCA3^;o;%xbsjaqO=bbEXC@{6 zNyo@5C~VYUxcLkq>o{T``G*s$|K&DG6jD*+$})~M>)n!8OFTB?UZoZT$(BQ<=)I-7 zI&@4R376T_uV6w>+tkKDlAQW_{suc+JG&BFl!W_6uZoqiWq^Qy0E=-uaoHvtJG(g| zkYsmzGH3(XAQ~`|T?_yI{j`@UBBtxJU4*=D<@@{lH`>r+b#;yY1SxqMWtHJ~`^3e? z84p@VYg}VJ(q97R4OTf8n9dj)7(9FW^ha6=2qx52RE(Am?(Q|FpiFskjnnaow<&-0 zs*sS7xPi|V5D-B9>{(~E^BN39>c}mi;B7!m9ehXCK;k#rU{E;Fs!lXA19!o$e|u$e z?H8rUh>b=LoB{XoFu3DYo;~}ORw9z09XYjXzjwx( zMEgX3Yr);ErGg==sy5UHD=TXZqyC310<7^{*B$hA#z#Es45W9p!1f1gvKHJ?Z2uMco10IQ)$pMINARDDpVD9Pbn_DOwV=Ja)W=8hh@T7nAsJ>Gn zxvj0Oxuu1f2ccnIV>nB-4%`$-OIp{7Ybk*3q24G|zUHdWR?3m;SQ#`gAW-+DakCus z1*Te6yg1bq3Uyh}1o^EN@6c?{UuE^OP67M-hkXty_Be1Um;yYK1E$-8aow0BuNqkl ze;yJR6Js_jz6gv3&hh&DIx#LT?qI29Y!VL|jZOy<&D7NN8Xlg5^TD^bm+d#_)sS|? zyl$#Go(Cl@7-V<1R0m{9OiBu7IufblL6w0toyGUNbLY-*z@;-DxOI90$BsRTN?I%R z6X1+@Z2Q7(i-<1^+9dzD`UBC(#9VP=PsqpJr;52Dc)~;1I7vWS;P$v zr1uFIrIk`gb6jdjZ{J>9TWc~W<8fgD<~Q7_c}xud&a+OM_K3dnjaigmy(smXOnCn+}m0TdVBfi z1Gei!lj~@tc*VxXMpR6U@2Bdqx1XM^U&KA$YaAt?s3K%_&i!zI=M9CknXc|XG)F+Y z07FIxRSL8NK=}G@&OyWdLMOmcY5WDB+S}W6-MWNVMyec(RzU_vYZCG)FfedxzTxA|>#>v3ac|fr z&%@{o)i@MqMy@Q(L(wi3lnkK-=8h%-wH!*2&Js7P3~c zHe+ILF4;aEF%6IYmcsYiNRphb{^Mh%HibY#LP?`17)ZmXFLVfsbA3lEk7Z!N@OfCO$eo>Dems{JIWpi;35KYaKQVms53aFmchOH2E+#JoRbE4SCI zN$nV^#5hnUQj{@^=HcTz`rd*6;6cci+fvO|NYN>`O6J!g5JTI8-HqR6);jVIz}1hN znh2?=s8kM+>?RJmoJB& zk^|h)xlDgxKpYFUDCTk> zKbIo~^&RSedkBeUkWsOje*mxi_Zkpm#4A9K^AnU$Q{46V4xA| zI2-k{H5ovkgP2Ge8ymarpd#Nd*^SqGf8Y9z2N*A20b(=)R8xxjYMoZ}p3~BjleNX~ z(m9+!$I7Eh(az4!ZTZ_}E*=rn)QCPmKR?{Oy7KaeWFFhP@};MchWa=NU0F?DSBUbj-Je@+gop$ht8U!9MYA>&O>X zr88eY1xW>i9A7ZNp$qHi03`+|_#~d=-~!wPBOBYMeBf^^l8GDuJN=_?V~B6@d_TPx z`9vhm2Wbu;Ki*`iyF&uM(ACAc+)&bhIJ`rI8AEJI9({6kJS1(|IwXiC3i5qO;)Me= z@ApP~9A{`8h-(%mL0MT@x%*iP8GuA^RrO%uCdKn10PG*}(<-I*3)2x7x<>ZIVOt(| z01&;MMf}%W?PC**3w2f)UI@!HA9(LL6_5f=OT{bn;~mAtY3z$C|Wv0UZX@_G`liZ+PEt_J4U!W|Bfk(yaR#uMEIlk;=NCw zynp&sAeG|cMc8d=olusU-&?kBbYmU6mydMZZvWcZi^s5Qf%=SkxCD9H@Zey~aHV}t zHUZGmuZ4w4ar6)Tl-f?tGG0`?;-z)u?+b}+ske;9OB3weoDb{vy(vsB_u|)a54C-D z_A~>DV{fe#R-S`|Zqwd?e2Jg?y1To9VQuT$*>&U9K|aUw1_B|DQ*9v%p^=ps8z(!$ z{MvZib4$FXX&w!1jLQBDIyh9p>xD5ac>y zVkB)V*L~vX+#gG~nPm`H<}t=ZO8p5j(5a$s^XZrFKj$U*@o3;OM3pvJOw8K4K;A)D zHz{2W=y3CZ2}`zH@rji~@M2^uT|B8PR#Shdk1}XYzmK_FAh}tR)9pUjKe6`l7|>hb z&+E-5CnxV*xfu2pICj=HkYaZ?N4B71rcc;Y@BB?pe%H@4ynTJ|@dUHhOn6}0XgD%W zmi*o#gVrSo-up!Kd5G8C{Nke7$Zf4W)#nrk2M75bS$)gPU*z-UvwrRGyGa)TT}_kE z5@R7$YQBk!+slHT8wXdrng4WJ-7@}F(MoBRu5MzoNX;GQ9XB-X4x-UG9Ru;oCC z?3lSJxs`PjrMM{DN7qp|we~U1c>D>PVEfmu=XFRA%>!T(=I?{t! z+39I$69*Ii35;I!+zx+WnukT@hm=9+XvCrdSxo*pNc;~l`0*;ww&FR6#}Wh4(FCGK zGP1JfC`4l;arB8QK-`;3i@LiG;?acpCKxx-K`u{|N*st2-gOwns{};*Fk3Dzu0Yz( zjP!H^kfCtuP>270mq)ryuse2Rv*>#jb6v2|>QK5l;N=&Taab5=b&5I*F3W( zfpx1~P{-G=UvkE_porloxuw@p$C7oJ)Su*cL^4tNwvWz#duOnUPku`(D*rqrzW$hK z3$~mvq?%JsR6>GEd%8RodUnQ&x< zU$A|?k&h-6DYmbDvQ@X8$33)Lg{FqQVg(XhCTmaNqX&^fVqjy#{=qCxc#C1$@Zn*t zl#C1%>S}8%@8IC@^xicplw{f+z~%oYLH%j?7}%b#Nd5Ke*JP~M32Z+}k?K`gg}QBvCoPez{t~4Lp^YY^FkHwN zLacDZSDj1P&x2IX#;~>>eW60LJsj_ZKig$JO*)@C@ENok%I~_@F=?Fx8)CUi-kY{Q zF_BEOHZwC5UgYlX-rQk+vXNKq^pAe>0grZAK;>sK56L@C%vfD0Q3c6ZF0l9~3kwSp zAPM4Bfws1`J~O`H?d2a3P|2Yry{d{&<(o;`SJjZpgl-#!;3;gL-S$f z4hu2B^r>e)e*CBfqA>Qd`7IoS^R#aj7s)y&q0d5J7{M;p514XE8dwFtN|vI0v!#WF z79G<#hgt*db4Jj72Mnjy%M8s%cjVs|3(v-J`m9O`^hy zn+{a@5wY*wc&#f>QbjMyLVnM}z7*?bd{5UgEGzLu%K?R8kRNA3jK_oGe(r zkwE{nFv2<}|7Cyj%FC-4C$Z8{QcZOI0dx+1jkU0*F4oC%4!^(AUm)pKr9kiMPBj|) zK{2@EZ=25RbM8y<`ZPADOP;>g-X5sTd+SPhPiiGW<$?XI+tOPntvBBil|5WthteoE zzD;A+<_DPZW5*3Gv;}5tIg$5Ipm9HWXvM7c?OM_@Be+#h{k)S08 z8X6@La4R|=`-nJjD$U>6mVf>L*^JTF80s0?=!*WcVXhFA+?8p-M1W+?eh`FEOFq#V zj;9ZyW|eR5zxjt38~~wM(x#`=l}8sYxiCfdG^g|NDOf}t&jOn=y z$JpJvz7FD7oc-F&19~68xMmA89RFyi`0bS(l&g0v7Z@x4&tMiJ-T^(=FDSYX2F;GpeBN!Dd3*`Zhl@{W&?CKr?^w8><|+heAk zM8#zs<@+<))M@Cx(*-#LbN0=ljmC{T>Ko15o!p`Eus_D&b|W3WA2BfFS_lCm-k zI8#JmqE8#)<76*cI%$29!Ji@~iI;sgHyuDPQ#HS=%G+vpADlYCmZ0`0vH^OtY~;aK zH5HRR{}y zxKXHRzB?i1Ny6jOlR4Use>}g5KZNS)} zB)W1oTXFW#^N<_VSTqo1o)(IH=6%lth}hc;2td~a+ZHM|0wqt6w&f*m(jZ?yKiYoa z-N*e?6^VwR%9c=ZPjOt zdAu2Pq-guW!F4cdY6vt9rq*_z_h+P~4MbrFvmua2EKb8S@IZ!Voq@+ZIx!@03D5Y$vriQ${o_>h6 zxs{bNX!N6YdwLX!kjH32-r)pQHKDJ(6_8bdBX2`q$t8J{3RPVNaX6(s~!WMpx{EtY8KWN^ZmO( z-8|1ji{D?q&8}NXN%{BXs-;9lMR_bO*j8X0rq3Qf{=OPRt5Le{suNYT+bsl2<+b~^ z<1;$Z)ryRu6HHD)F?hzj?JoYZ4>ZH)>Tlq#KPs{sfljg()E&Ci%_IR9n}Qm5Bkl|k z?WVa-&Kw4#q^M|6O!4H2AeY1Ztm9qxjk#X+FR#42VoFO(dEF!5Dj6E4tw^najyh<( zQA44X9ea(9jb>ojAO>_lx+?4TQS|Pkciox6tO2%OEx!QT@)j~{BY~tmW)*DQ+?rT( zO+WE!S3Il;XCrKSc_2}Mbp{Sgqj{i6Ntoy3>ns1_g|~8^dKx(X6E0PlbUk(llC1pm zfylql-oarm$a6ns#lyYCsEu%vwQR&87TYWaeW4+zg?Cr)7K8Bw-ZqQ}=qAEeCWI&L z-wt4_=+&S^APQQ;?v+-2pn2iCIo}0(AiBk1zQn1%;POZRm^O+43r{9^6|Z6Qz1Wa5 zDHAsI*?^$k+R1-YfJP;sMf)!3oPnbY373PE=XL`bi+-a&ON`&QL zu{fX$+ihR*08sKBi+R zr9F^74t&?}Rw5O7Zrl?j@BIZH8#I&VS>&dmjI3S>%fdECzyL;eQql{2I?YbdHAmT2 zghfSVVhZZC^$~#!Dvjys6hI$ybvkDK!0f=5X{nQ{Y804D2@4HF*$>9Qy#-A|!G!*lB{1G#QPBq@_Nv2nRY0v2 z+Z|2A+HCCI6P3Hjp3speDXqcEjTX;HxtQB0S%q4#w1eWDc)K3Zp36&4ehfw{)^`Y= zi1>Gc7T$i;T!I^K02eDPEox!tkU%m%E4!E_m6tDdE4+Dusr+ng z%q}WIPAi`1+&7FKa?B)A=i7TbB6uJK@)l2}{4#cg;nKPi5)`ggfOULtxO_+nPD%yo z>FMXj`m?d40N|`6gRp5$7WV7u?M1=-t<^yLZ*zWN66XIoEG#S)w0qUE^D;BPaJGd5 zFRqav;Ojewe0ahd$i4z5O>1tW7)tWw(M~l_rPCo#hCcWtSMUPQC!V!dW@_Wf-XQtb5YGry~w} z^%2k2CIA}tn7lIed{)y}L*HPByEi?z1^;7c31y7$*WPc`!~i z0gGF@Peyh`Za&90)nqn+<$ zGB+NLBpmGD)j;MusSMZpNrRE^BUX?%crL zG5UZ0c`?}-P^74#rbZQn(6>v@VUBJf#m7gmf)h3N@~|n@Eg9x9h6moh<-(J;;lb|5f4LSWAkrsE`f=X zBEz8ke7YLY0RSUmD8-^A;Mkt=@MwX#rcN*hU@?$VR;B{l2=PFENx&D{(Y)~b)!M2k zB=jy#J+!d|E;aK-7No#`Ri**6Ag|wcEkpm}yCU42sl~#XrA)}JYbJ>w6G7&Du%5xE zBq8DJu{Sg}SMPn{fcO4LcXzUMEE_uMBdI(XNgMUpv&_?}(b8H9*LJ$c{cT z(08a@C>;VJ_6BzBgp*S(MKq`nB;^?(A+k2YuNtw!f-Nag0>82|GdsIdL_yG{Q&Lv; zm?3QMWR7)AZDPnghznDHDq0<1&#y#KYZ?n$HnEQraskjT__pbw@NFJ zjVuF45fD4fOibkL?Mv{8ITFFp#~g?@cpfJe1u)RbGQX=CQ0EK>>`-ttD{N=Wb--%A z8B6DDC4e(XiIE@=exZT>pqt&T0om69%=E1nDHx z{1cc1;;EUbDSC}{36v*dxE%964%ZT}=zGY_no*zza zxb${+3#<_zixzT)u{Yd#b<=@^M4x}h%}R;P(|?*1vV9I0b|Z>?)Jc$viVDnYsECXE z=uE3zehYM#XSx0YR?-QF>lulzO($R=5FB{~szb?6FA2$rqd3xZq^!}x3(SyTZv2`f zhtcmZy1Oeup1HABmEvHY$F@S#Hy}U>Sj{20JNq7A+XkAfpDqK>rcy|MMouma;tTx4 z4T5BFqQnl|g84C!Kg)=oj=6YwvAGN%p+8I=y@q~RXlJj@in;5D&0}7?#W^h%0j=fL z{UZ^*9)I|R5rKYMYHB`^(cI02T6;Z71KGX~j(Vq#^Ie>N77nE3RO zb+c|%QPrs!`7*zG2BS36IrWXtKgK@~)^ex9O`hNSN&RmU-ELp-ew8}t_d3GYo46S zVyULIjjxH4Z`-nVjC~wJ7ryKnK@VA ze0#;f%KoHvT}#iwSit}s;>LRV(Z;x^+m{vBfoc@eVsLlOtr(3kH8azyX~4%{o!CiO zUGE}W9cW0}H{WcZgX7lwo_*;j`TPGW?>&R6%C@%AZQCuaqNvy^*)|asl$;GHf&`Hq z1XMBzNDdNga{weL8C0?)B`R4#5hX{-AW1Sx&fl{(VE6l0eRZqukNf9R=bSp{^xk{T zIp&xnJmVQ-RaQTh)pW(b#mb6@#mnwI_0pMrbh|>QyV0<3eBwu=h|5ATPcKvMjVMxr z-)i&L*d`5)=9=m(-ouHSHBC)FK6FtDRad77hVzH%yDnX#9rvnsm>tXa>SIzBrE}L` z>z^d;m9nl|)7OrxYu#<6<7?e)q_g|hRQ33dwQdxW&)Q;1kJmaJN$v;My6{K`wy&w< z-utWTBjwyd;ePUWHCsBLg{*;9Hgmz;Ym`_3|LTMKWXwbR)_K~T*5gcFs_us%9}X4K zRl}$zE*ByIGGy4YNO)LSIqII$tZ%Q+THXGltG~8T-A1qq7)2Z&Ya~uRN%r#cipkFx zM_~vUzFL>%2__wm$=r=NcvJ;SN8+~P-I+Oe|iOoFP}k|M6~u4E;ZqsklNec zm#gNMGv8j^J?P+|s)*$1QsWsN`CyXeUEPQZ>Ss2s8*7toxv zMwJIqqt69tP!f*u^Jfm&7g(){zJMRmsUbqosgI+9zoY`5=C0j6GWDL}maEUsQ?RfY z4Qi>WuYa1Cm#39w!%9s}EeKDVm6cWBZ9cYTwZ&Ce3YFcr_M_TDLPBiKTx8j7pr9BB zeD>LH%$$1lNZk((x95uk5KD48xmkPPhq&z5dMhjOYvK@$*>-b@@qkiKOmT(`0GR>5oz=y zkm(;8DF+{|rlmz|GBP>+)vL#FjD8o6iHL*%19-{&+Vj`*QxIhK0#_rxf@66ZmKuqV zP;S{0<>!OsLhqUat-kTv1I9EmEZV~$A$k!9u1Tld0l{-YAt9xp?kY-`5X})`g_;BH zxc+32ROyL+(}pqA_4_EFU4Hau(H@pRyB#DHDJkw;IjCRgaNLagkf26VT2b_wt5rr3 zK93Sh6eMqb-N3>k&Bk&+D0++0rTY{j+Y6-%bVatk>Ka@8k*Fbis(ij+{--%#!|3R| zZb>9LF!)_poZ}MjR4yf>~l++0m6E<2} z+FH+38*crt@A)8^D5H|hnV*@7W$3#x;Uo%D`k$e4cOl2`lTPXTy*~>aXa4s&_;05{ zS?_q;7gh-V_1*3J&k)~3KJyxP6imoxpfU~%MDEyHdH>#j{?1;52rEs}19(k$ZJ^xq z%r1l?>-y|58zBaS{R-OJNp|!DC+MG1Z?~;M_}@7=`0z(pabaU44T3hVp`jsNW8>LE z22La+MkXdFPn}xN#>RGDLE#3@s{`Q&3me;oS?0a}u5&3TQ`y1cl(;VxrDacqAiDJhRpCDAF$^;!B|ciw(lrdClex| zVC8c8@;;R$Emlo81%x;=-yU+h?s+Bl38^^+_&&$*EY&t!C{muw4v6Ifu z*)puzF(4RM&!@8Ixw)mKr9FH5mL}7p{fw;a7Gm34T92&z$~yrrKbY8c)bXd@Pf8CU5D5}EnvIH=cG&~9;EOw`U@1GcqHBJHFbDJ$(!32;^YSV_MI{<($FZ*t)~fS$F=HXfTKALl<~D%@ zNA2YPXzjXvI**pJD@e{P?d(4K%8T8%5SetDIe zswljx#C^TZFSUgAr8 zw6(QOif5^i?Fcweai9^zDIw4+*_bhw}|OCc)%Z}c=30?>VkQ_!}3B%mPw=cY~Xahe+v zRpRY-uBpksM%41C;FQLur;C9pj@4^gbb@|kQas=X>O{1BxaXrsl*9^0-Ij%kj=Jv*S@X0&~2w!-ZjXmq%nyvxz8=Pcdj z&M+T7EE_AfZ{Nx12gKXu2N?Ds4-!CZgO&N-zrPWf(kmqP%5(X!fW)0<%N3^Hx$_pX z+4L)Wc5-rZ(hFMs4pkv9>d>J>gvJsH|9|B70arj5;H0IcWkG^^s^wV6iL{K2mkjZW z06&jwY0-nSZ@DmYO+r$#5Jk;lwFz!`p9o(+zgthA?kbst%8j8L??|QmJ^IA?&bv%( z0Yzho4~vS6_w(?iNzQxH@ozp_e@2YSW(dcpXY7Y1LM zNj`BV2A1PAV3$@ zk5?HPBi^nr&P_b}w7T?)I$7NE>l73}7WVWB^Yx%RTBH-xy^&2S9 zA1d`KDk>@fG=1T!ogE)KT_P-Bw7n}S*+}qey(TnL^fsacsB+i^6sD@mN_PPB5s{I< zBL_~QXSkxTfBV+0Tjrxz%zzFJGdh3zCzk)3wRIt&^jgmokTm(Pwkt_H_rYK6Dh2S{ z=*O$jKsO=REiN)zUpcwvMH z?}uiZ?A(S5Sp4^BF{|pCBwU-E+*Z^t33}&pgL6w!(eJ1se1Lb_PEY>`Fuz?~#q{9# z&!1T#%I07JGr+?hXdt+smCsJRdj0ywnTUQnH~2vgojiKT4P1WZ9vHfGBx^wyAT;crUI57B&?v$ zv)<5%WE0EqPxp^L@nss+iu|lHP zQc@d5L_~Ie`I#H$R3RWBK&W~C;;_Z`9XoylcePWLw3nTIb7yDgMGf{3VABKH=FCaT z%VVVDGb=#D=IuQFC3M7G0+pHYZMU5nMV)q{Kl%Nj#P#{%Odl`TBZm*)j*pMGqyii8 zO;*;vy?aSD(XywEjEwx{Z{j}-uob9TWz?kT-~9Z$(Szn=(%jUf=Qx9gfOYEx&`;7m z2c%Hq%o(Yx}An*W1`At zP_WXwL@GhVM;@>a0WDD!VkpMs9Q$J$xMkqd`)Y}!^7e>PRO7V>0ZDf^;fWutVu_dt zT=#qT@4x%xz5lgI-OD3e1jAkDjNfEuvp*hN&V>PdZ))C_b02+Sgb?0KrZEZG{0Y4} z4fko;?(R+^`hp!opH~e|65bC1!K21h!$J_&muHH_vusY@PE0%u71()JD$*0C8M)sc zDAyiwR}sSJqruqTnDdJBy&G%3F*X|&yw|9wIygFB`q^FbHAR0jg6=xcy0~@gH|PRO z7I6G|09oH1bYvHP_;7ky?aV{?bPZ&b#rLV14>Xwa(B_Q2-~2%D8e#xT%U6*ax!PVR zDzf+#ZsXzQy@WP7xHU->0V*|+OTYxP`+R9{XMztoX_~h*XZkw@1*A@QaRdkT0HvNz zq);aZd1$x1c<~~urQtk~|4W_kZwCn5oBMl4Y=3*`Mr>xur%wfuQofJF!#Q)Nyd@dE z7o692bT2lUm4&4MD&N`B;f^+8=-j!g_ulZ^K2A<5T*4GXE*_9V4*qlV=I#9ZVt{Z@|Acjv zjLpOSg5?>iA6sxndkuPO-%nGE&kv=xj$DnM8fkq9nEgd=?nR?kKUPIu&_&L(?VD`P zcVmJ_n3<@Nzuh2*YUqtMC6kc&cnTgvSeOnMWRH*y3rbD@ARdon7&M$kaF2r2fBePG z9NM(ZPEzQjCY;TAO-=TgFp_)hELpA3l8en&*;T8HDJ! z5X_qA|B>t;3%Y+1u=W_%G>{EDNNm;oQ5;|!rA}acq^eH{H9n{pf2V`YskD9j# z+^+r=GSK8BbLsBu+t0?f3EK|fw_rqh=T`FmVOA99-{F!dK%NlUUL+C7L!rA4(A_G` zlsJhjr+W^~m>aj`G8NU=?}C!S^ffnwxUf}yZi)}Mk%XpZaJLfzD)sq?j92zKy}WS` zj{CV&TnycW0kWKvlOrP|n-2bcj6=s|@-Kolnr48uS_pUT)CV+a>FB(Xtvr6bO+Zkv z(}TaQ0FVr<1p)F97rBs1TmtDNuni(;G2)?3kDxiQwK7irD@vQ^U4WBwxJ0~op?me} z;kYSWqc;veUU0ZL2P3%ZK*Z}ah(gLG1A{}FZcy6ib1<$Y_46_Czt^TmE|yFtB_*A4 zJ1`Df&~3(7mNqs@TwA;^&7#Zn)<9@z=wH$&*Ur4wzBS8sGw#eyfB)TxK-^KLph9s4 zA$6_$y7jM6I}8zV+yOX4E!AKP)S}i?|GdEQ7_m#ZKqpU0fg<($h7ESvqv-fO(C}dE ztcS-Z@9&U#itD(B-ZSK@EDIj;3j308$HlRpd9>%bF6|^QAKy15lRkqW2nb5Vh()}4 zqZS)x&#)G9ZD$<>AEE$BXeV;I70a|4C?)bIT0XNqs6vQ$;`;(Fb3EwP{8JMZuPbq$ z>um<8LDG8&0>tLT`<|X7D)K-FWCFNOTo}$@UbKjein5K@v0$DXp1;fFs+Q3P&G9ik zCoR1*|4TQXb#l@wZ`U2^iE>9ALoxu39WOJHo3U*CwX8)oG@c++0l~}@OqFjaXRim8 z5Mh@P-qY1}lW5;TTkqFwd(JS&UK(}z^&_%`q)tt^{)h>8rA3}FWR)Kfu%{#)k!T^- zncebbkCKYYraEB(flt*9rKOZ0#5~o4LU5jIr@(v%i(3Qp?_a$^3w*5hK;1@(+|W<` zlq6s=ap!le>Ds3=nK`9P3UGyxR|{Qz1l=l1U9ehbawkCw5`S_Bosx<26LfX zDE-PL3-(w6j}=*&83K6md7Vji?U(B69WbdrgT6jKS+&Ih$hG8ST@gN|OfU#o?v;^= zbldr$cW6@;qst{Vjyd=H$WKm6-J?7QG^nYm=|IEuXuHlko;A&qn8^2g`t&k*PL|iM zy-PE$c^nYHVzcF%Jnb>PKeZiu{<;q42YL&4EWsmhy7;hJ^P0Pyi!>^r=W{)3}^^&@@>feD^g<2gn(gJT%c`CB0vD0rJuWDkO@Eqy@Ge( z%9>v9e78<$k`<|kk-7O(Eu!@P2nE$QUZ40I=I2`b+7(URk7RjiC;LZ@-n^G1c_Z&A z$1a9vaPhZ&w$U(puM=9f_}5y6p`8t*0`?s^@VlIxoWpQ>!x&g>MAk*?u>r;qgKJL0 z79jQ9wr$&9o2KOF++pFj&i)e=MBCllyD8DJ^Y-pr1EL}#9fO1HaTx%kkOH5auUX~> z&mq>OR*zDA{VyfmzI|P$c`IfC{DwIJ4}pI*IG}nB&lhi(AP4#&0Z^h_b2VY<7#T^j zOEB*uh0cXw^vm&BSCB1;a9JxKH@`%_5es+`9esv{>OT+IPLq$0VUCMgJBjUpsr4Eq zScEWyO9Nzc&%@(1B3uH_LR&BmSo2UtpJh}t$Plf}Q(LCuA5?!n*!T3=Ha7jRfbyDc zMTxz))EWOV`1ZH_jR4IHNl_2`O@z#zeCg*oYu)vXowiKRq zN0oL8in)o@IbA=WOgH*)@agy;Fu$L;c0P($-85^QtbwK+;JWFtPRp(Vpsu=#Hr_r>I9ftnhb+U2xy+5h>Y$x zf_lQ2pJ2kf;k%_Y97S4O+xO63*k0vuF&D}@xN{TY6FvCBl|T3KEZ4;>M4t&UVNTMB zREeP$nCf%cHenZV7FHztIFc-Igpgvy5+gFMwC-MSBM=2R1y+(a3C3L&vBs$8ZU%;{ zZww6#qQRP=Y*?!dkw|V>MhHQj!}J1^p|7u{<>cy75l`@}+Xw5186{^i3x=4&qLQHY z9C2~{mGpb-ghHwj4L=DEHht4pRwlc77snezS*(0=D7AuipyBJ+Sb$!I^Z!1@-q+th z2G$Y(n_>P)>c;PVi#NXa2*Gsk?ru|I*{5TX2<)!;BP=}Sr_uxA6)DLF*2)f z8iApH;WR(dJ&y2$Zw03D5wOYzrs?3;u*~ z8j-IeOv4eKyacp)n^Z?iS=pxB-6|t7$g$%)m=MB8#&$5n;73w>I7myK+US4f*V`xR1H~jJY??y-;h;lmo1&-+j8mapMG}WS_p{%AB3ubF9j_hiNIP@SLdJycH z3TE76FRzMa7j&7{TY3Rb6dV#i>mFf+bBznbs_q{S_ky*c_Fdhbr4526A&0SN24z79 z#l^)zI2%B=pi-O_kAi^_U^tK`&YwMdHh8E65*!9Gf*2vtId|k@7K@P3oLgS+@Ngpd zs)eE`4{T}pZ6+D(Z&a!vF_cFl&AMYsvsQhaX+~xYGDF@fo3gTVBI9baf#PDQeW;|QO!sukq13&D(6dp=4SALCD&v%7WPxQ6gR3BK zjGi$CK8F!1M&tCwUQ8CLUCjqKe0M*w3>0Dk$eQKa)e38ot4$q^!4-v25hk8`1|j_@0b!WsiNYQ%e1S9S58!j9(Z}F9zX8>(Tgb=T?92t zqpwkrDoZ|V;J$U?Lp@bHF{-Pp>q_htaMK#Fh1!DDKL0=9u1@&h};8D&!^$x;TWex%y{GF=Wo911?7o- z@#4Mt;v-n6g@e4j%5as(v_^-AufDMZh@W`BTM0P2*~?{^ep%_?B;hLP3)3;F>UF<0 zrAR5}$Xv~8(?wzDm!5Q#nwXd%&LOM+k0NS<3+am9V~UaH`r^!Ul$AH!kckES>@~p{ zBHX3nw#?j(l$2?4OCYx%I(TD0jj&w;5*EWZV#31BIb7~H?|XUiYiVK>47!fr74fXf za7+gwod!@}f+KVE%10x>IY1#(-g>F0X3t;3b{^8mzlOIS0KjKA+T4aj_Os+4f!JU+y{;706=pnJl}n>K_YQ??s!VEluEB3?dR?9SlB2M=(q zBs=>1H{S`>T)cXv0;Qz)%iWO#kx| zoeJ2+sh#l8KmRxRz08t|Af)5%1L80Hv+PHcP*5V+=7&pS{P))EOynkpH9^qTKa)V zxy+>zWtz9y*>u{dpImBG3|f_or8qFeD$v*xnZvJN0FtWet<{d}&zU##zDR=07kwMouV1gtb+(^Gu#OD57H)wT z)a=>0xu%w6khU=%U=EW4WzTL-I}Eev=JAlC5=B9jlJlnDd{{gS`0{ozr?bXG@6!59(S6)mV+`IRypX10`> zCW@r2x9>wN+t4|)*plO@_;A!NVKVR5Zm|9Z?sC;e(@5zM93k`h+x!mK$5s zbdh;b@rd|G_Jui@%NaL$0Zd^>0S7)Lkv0rE$-aMo;VnIf0HSudY3o)4hpjE0uyIaZ z9{fB&Gw$@QEm%5d?dMvq6u7So;<|J)J1be3ho{yu5#>2Xv;>KwA!rjNCHSn5ohH8j zI6iOD@=W{5OKonejU!f1jED-7I$T|nUL zkz5$X<~oun1});&JRQ2Iq7n@!u9?E0fS52IK*b;`Vk(mB=sy#_9j!Eg0>>>^xk03_ z=uTU2uZEdrtxjOhe=066HeU>BM7*Yqldz>!+l#&rOb#MUVNPytqUU-6N`NUqtXN=i zfw!m>uSG?6H`WP_aU!5oQBv}2ox^a?(|`x<2Ky3_48bK5zh?C5@!w8JVU#co6Sa*` zg@uI!XWdVYO-$6Hh(IhDR0$6n?vDd!S4BE@zUxnW<|&L6i%UzZop3==qc_Jmbqs`< z?EIr*u<3BnHUQee&k*-P+TM^S|z<*QZ;uF$i7XMrq4 zI1GYT62QFc&|ngUCW!d`t2-$;b$4ABbu(Z!v89GmlOZf-?bzHe>TEreABLuumuW==JRKP&~{ z{%cVgsRJ`9K4Mi&1*dNFSKi16zI#3?>0=lxZ0-eExz08ebBHmKxU`A&AFXMjNg5eu zfjALN8l)pyEg<*lglqQps&Jp(Jv~w8tyv|lt&2CNhcU>Ms233fr7%zP#{4=10pure zR3qv&NK4$r=ui(t*y`Bff^&T0=T~%UWxNuJqsMb)y~5{!~*} zP_%xPwqxTa78TPsx8uLEZ9CEJ&XJjML?hi+Su?#ZD&vqfg&?cMmuDf*)o-YC>vx1J zr^>R|*pdrR%`aYw-!&xe_P<8Yj3!Ko$Y+Lbrd z*2aQai~)=MpV3Nhf>aO#U&D<)1Wd#7vTpnHdwPR*_Q11^H5Gx}hCmGTLqKO!RZ)2g zyPF1r7y1)v#O0TJLE8aOqNlstvX47D3TaWzz-K>TFT(T9S z)AWVb{(I=?6b%hyh&&S)mM}v=@PzD!zTRr%1gH-svGLRVO)(-rUk3ypr~W8^KXj6m zmdYTQJ^lQ=(aF*2k8~EFx;mF-R+)Y78xeFyBJ%1oL$}!hb*7`Ph>6(I8et;K0SL4T z1N_i%_#B1-k@*Gih9jWdL{z?I>sC42(=yLHJ1-HVT0zD40@(9pporT)qghqVB1SRK-y2X|tpQ4E=Ep!wdjY}Ifw=hia|p#0 zkOWsXA(c{ARUL$<3OA}_&BTLkZEUo7ci)%bId<&Dd6vlM&;LfO(vL{8*Ws#G-S_XY z+Q>mc7?a0ny=qX(4RiR=_=YIqjo8B2DGIvZ6#IZZX955G>(Ad4l~rfK2L9 zRi-!lU51Ki{YAQ47X%pY({sL6L2MJ=^Q^EASNc&ZoNJeB`;=Uxa=eN$N>3`Lq)Z{_ zsRRuTjfDEI^rl-%^$1@pK+jhW=mOIs1i${d`YQ$qoh%QBSHz(GDq;wygXFx}1yH0c zF`4;C9-^+F^NSxH3x)~zAHQ(XzP0B(wXV}FNC$-0giJe#VD6ONt2M7wdL3D&hHHNN zOj0-8pcdGq%d<|_)~R)EeA7wm&8LANvhCbN)3^JuMz<~%xQpb@Oe^3%rqanPHvqT#lIv}lIZRc{K7k6!8%hxN} zad$0LJ8p*DLX{ZBi^8o(3NllocPS|V52`|&dZN(Rmi(=6T2c6Zl!~V5tS(Psdb9O1 zOzp1oiOONrYdKTLXGr-8Vwp#&qxs8uaA7ueEg(xK2#HL6y&^3o^;%>78)Y4xaAjAN zTCjy^fA))<$HGjLbL#{5tsT{V{D>`jR1wX4q6Ck>5dj!YJ15* zabK-Rl+o*ytb2cP?<`Oa%U2s$pG{si8_MP&?S&|0z`WnQd9&UbV5NFbxYGc#>VxW| zYc~bG$5~h^FF=zh#M#vX7S8nZ;h3NRzO2W%ShzesP2IFQlm!nsKEZYTt|>b0KJ|^$ z=;zOm2m>eAJvkpD>Wt9-s){M3%l|x=%dyka;U&9F)7P(Gl~$)e;M?~$LSbq7PMI`k zS1c!D06E(-6oqIV^lhT1wnisw{PoM1U!0O%GH{N}AI@J@Qhc+Ekjoo%EGeHquj{wv z_R0G7V{Xl;u3@yE$2ib9ARuA27>q~M?WF7q_vhP2Jq5dOAHTm6^pKlZ4`rY|e4+FF+R2hg_Ltl} z19qeFuDbd{*LAKvk3Q!Sm&SD%12<(sDfax_#$}JbJpzRl5C@Jx(JW);m6s>>>%Ndk zZ#J*DDat{}&}#MN@R>#e7J`X>@Hr0y;A#PDCe$bBM9=ZOAfyp$JxE3|KKq~8VJdFE^+w4J@xr4y zHShR{>8jWG=(iGM2)1VY60RlHhVS=``CDy#QA_hk!9e31$M?RR$Khtv?%Bgtju8Oo zkPaveyT{L?XD{(5BDk%*-cJu}Mx#d9bsh62?bo0&5=mksww4BD4w?fE0dFDJ&Bxxs zK@Elu>XCGju5Rm_EQ`F^k8I0D)bawgMN55d--g zDow4T#^B}a6F&lfL`2RJW~!?GAkfnf*R;1MXJqij^TK9rO@HSAUR9#!HUA^+s>@&? z%Q)TBR#lBbfD_LPyZBDm^1^)i0q?U#Y)rd%_rcN3!HPMoxje!c?>~f?n$8gZp26&A zCR!bf*}{=~py5=oc0(6plT__gOt%HKU1PC)(HKC&!1wRxk>O7^p)hZ2Z?Bmg9QVHS zm9p5;qgBrj6-;pR@fn2lG_DH6oqe7O|CI=T&)xG_W}+CY;5uL>taq=-PwH#CgUkHlob-KT5pempTPi- zzyq}io}j~QY!6V+F9Pl4GO{TJwHa%jLShtU+~ChA>qOl=n8d{ZK-lDkcH8t_ZJQ z15%y&*>mne;*2EBu32-&VNBefRcxSQ5)%Pv!3dhQ2j-c6Ku6p+uycN351;god6($D zz(1)<(f^_|;D~e{T+0Q{*8S~hrat%?dlZT~bN~Yqb|R(65>Xu#6rPu#9qT+*ufFm= zq=IS3Tf$}1Fjki_1z5cgBS9{hjT1u+4$VdB0yw>l;o7Wx-BBly^o^=*9J9?c56{M8`QvrgmJtHK>Br=7X#W+6!LWPU5{S-R^-<-CJLVa1H{1}a4)Q^ zT#@I2f+p6M=MwBY4yr>#vSDbM-m#-N$DPf`Yp%h~OU%OeM*T-hyAU=}BeSSrn+HgX z>b%}(kGxS-46k8j5)GiKvPAJMe8N4rj7r}%#GvAKz36Mb0_A<}@Z(#E$F!_0VB!}G zAW~uRvgaUy7cnoEO;lYDcordVV6gTyTGQprQU zQgK_vtuP8?51*M@g4*kShz$Ij1Ua>H?g5b~LVbZKAjgIJdLbU&g=p-PkT|Am5ZkiC zJxE9mlsQzTs7W?#ONTbt{M1IAi}LxAc(YTdPGMrr-kvj!M2|Yph71&rLE#9{{@XPc zLBZ5$I}>OTUP!OcI6BT+IHt{^1_Bz)J~aC?qHy@yal9AguGuXf!Tu=ueFSqXrJQOH zSHam&IuRfb!h58YLp6lfu1fsgeJRVP?4;!gu&a&&Jli8^r3c_dzzO~6n92VLf%WzA zd)=^yl5mA0pp=Ax?g9^g4Mj4r6MWvSw9P)-h!=@PMxK7Ss|ukzMrq$eRgv$$>T1?L zHoEqbEz-8OUmcp;Fa3&T@2$OujC5RL?Hx{}z5D+C*7mg*RFUrex%Rp>lEm6O$VsGq z@7Fr^NdMpdti%HL2*1>LgRDKz7UOoadi*X#K3)>(z*`^W>Lsq}L**;|Kr$^mEEcJP3#X&NO&>-Nzq&PdFOAuSz9Pu)U(E{dAD> z<1;_=M;_Ic7lQQe_bfRIk7W3UK7Dn>|NT~mX8j2N6I3^cAA3qLR8pH99YvPnECwuS)&fA^NZK-T!~{e;(LuOIG>g7ir7noNi7ls+g)9dHkF; z%p8z|IX2NFAF6H=N7{z|E#^`5iBYO~{%2Chzxk?b3^R4>E@sNH_vBIa#nS?74J>Upd^ z#CAn2<;gF+>)K$|!WPY&&Z*}2^)#`@UputCFnMkhQi^iwWOAowhZZaC!~Q9YNP3SN zrZ%Z~m)R)oc2f*$({&Xjt8zhLgHckXcur`F#yK|!Zjav2?AiVeXf`icI7ZfM%R}i+ z{xx)M(&`>H?Cr8uY9deOcX`-3-3)GYod{NZi@49yZbcpf+)d|_qY~=5_aqud&8J2h z)mAI0*l)qj;<~$HDryI+e^MzD5_1xYPi_eHJLzc$uc4-u5`Fi%?Fc*4i;(yFgl1ROGsmb?0 zho|he_FSPRYkc~|2L2i&y8$^N@8qc^zka>_AFc7;G|m<2=lR!vdopo16I-$QLdW_* zrB$k!FPK5kVooaBw`|C)wc=gsU0Y_P9lR@+)6(prf091e++O|ku%40N*{Sf~D*Ph~ zV;V!nK27|{9WrzAE9}c&`35&x?qhEK2@3-FxD`)%R1Z^KsP(?P751sAw&FDA|JP~u)}Nt%5!dDlg^klW8$xaNRpc#cVhb`%7TqOnOunV$dr`iQEbM|1Qy$f7 zx*UJ9t_VX^tx~8wei1hmYW!rA5}2GnXWA+Vy%$`$M**icC!UzAYsGqUf41)Q_yDn6 z*!eL#yM3DK@{nPsYh3PSvL)W7*thaZLS4Buf9-Q_Ghcq)D1758pYW%)311oJ;Ksm~ zFYlHj$a~maMBantjl;G{hAVO{9~iXl3;9&l={sb$$hRt`HL33QBTK2{A9Zb){XIsa z`n=`1kgbBuKNob_g*B(SL(L3u9}Ofc$UE*`c{OK82>SyA6(@1rMnx6-8tp6%`|A5c zZsXH3%uEK@v~R0CcQ8YJC~jP7pc^(q7({uCkUE-=oz5a*0@oG{wVm-=r?Y0~58jes z*LbcHdvDV^<8McqcFO;@Zl{96>66O0H4YTr*|LKpr675+yvUQddRG=d?{bL4}MBs zVCA^>zYxt2s!QiroaC#y=*Dh6991pFE!eUwBAXkU8)|PoIu>7z<038Vlg*w$p-^9# zbY^DY%Z3W+Nezuex&BDSU^(VSrZyQxs$(rK;?tupnnD1lI^0%lL&(v-a&h06nNQL^ z5;+oceA0TQnM0c+K6yGA9)=QKQvoui;}B-f`a8=!n+`GX&;SISM~IFhdw<9LQe!a3E!pA@hiv29avV364lr1hKJ)# zEn=F!mYPhS>1}!#DYI zR}-LKS-*G`uYXUS_@ZUlqI7wvTZT=Q%ty&gvUF|^QCS!WG5C`|ZoT^PYABoZbh;89 zp=>eMBXNZGWX(@bF9(jA^>s{%`^uan#2I3|BF+O)Fnls<;A(XCl1gvWIf%zH` zZiDf_Fg@dS1tB=F4)4okZ3|Ny4^6jmR=qGer=GavQ{Qs3Ke8q`tQ)_Zl9?LXIpU0G z+4k>)I!LZ)kHd=);bobuK{wgDLBqH6CK`!$WCbmL+j`w6no&aPx4xytTdi*O{I)yaW>6}wmdEX6turYfvj-FL1cY);J~SJ*e{2?Y>=_Y@0qo0*o0@vW93t|TS-N{)RBGUYUgauQWe4j-9|Dz&dCbhNEg)p+1UQ0Q<= z3Dc4oyWka=zEpZ=hgi4mi41%1n@AaUGvmZafjauW|~Xs{A#V<(ifKz_xM8GiiVItMk=dozD2XwC!|+4`6u z3;E;AC1i>E)dYR1{yD1n?;Sq<-#hF)BZX&?k1XH@dBZcx=^CL4O35)z-(#{}1w*an zpJ2m_&h3$L$;34ipW_J(^CR@MWB1Cnp=#A~K8kA@P3WB~>+ozH^z^NB-xnU?n>>?t zTdDF`LJ?pUtT!O*9pi)9at)P+Ho5fIfpr5OrRlqI1iC@Ond0XZEetLCTIpLCvuw8E z%&1mg?(Co|v{^o8U{Vo^I+7Qp5`U49P>s~oUH+O{JYjEFgeG|hGf&82kh@Oz!ng4_ zCx6As46AEBJqHqT@9mBWmd3vIHs~`sLF)ums})Qz#J@#U1KNzLu7ER#TtW z3ey%_#^N6h-I6ttQU-Z}_wO?9bn^aVyWV#1ztj~TG^K~m+zm3$R^A@9k0T}f*GQFD=VF z$1K~^QMJgl#64b`LKtJrYxw_w=Rp{=_Naz`%<~LV2D@Qsv9G zjz%$>8MuuGlig9s4ze-}^!0Lz_!`W&DutQ{)oB`+a`?#!y)6-N7S=I{&kw8`v)jtp z%6XB{O;&Qe;cor?{RcxuTfUTxJnx=5Ro{;?{gKqcGM*`l?XsfZ^?BX|Mif$zS0vCw zmb|4LhxVKzmalBM{~SB#lk&jNGY*^)osw}B{HeK<*F3|`$R@Fal5B7b0_#5g9AJ;D z=f7Z^rLLB9LCAuHKX-jim=f(#Eb>3(R z8M8@Jw8t>ENMkwn6OQnHE1zF^F||OCfdOKx+`yMLvhgm5t;gXp6diNiI47U^XXOXp z&DJ>>xT5+YE9+t95pIx)U(|j%NoTC0%FUgvgeS~r>%>5s@yjO|N88!SsxN3rsJ^@6 zW!FwCjbYO`q1FTyi^!?~&QP1nz9rY}Bz3TXW3)@6cfIn-x1A)u@&HA%OFyLR>)Yk) z8b3wV`TO#~o_y82{&;w2QI=`hFr!PN!SyLuG zILSks8s#fzc1*B*IC0=)9dZ|6iE&*icKe4_G|kP&3DK!*IL6tOD}5}U zZ%h)GYRwNEPNW*|Hi{>F5@|t)d~0;2{Z*DiOkDOuN)OLf_(Mm3_zpk1V}F6l+`xIT z>yg(qdB?|(l67vz13JgSzw84Ys(Tx@d3QKFE*bQmW{XA4$aIRL+tZS6veP4p{Iwz@ zc3SSu#z_}t>-wW{Ip5C0tH#lbWGxrE^)8kT4KJ~;$y9O0ONw(t>#T=|$uey(DIK!H z7et(9t`4|zLQM0{?c}h@JtmB7(S1sF91bOxd*B+Y#Pe4hmHPi*t}!X*0r3TQYf~tl z#_;t2^EK|MCA$F2B#y)R0WVX%O>%=joa6K(Ut{s+N#?kDes0>fa>$E(%`6X*4^5zp z7D>Rty16sB`I{fAN>j-8lvHs=bcW)@>;@{1iM4SOK8$2Y4!hmCW2yowaJbmO%ks%m zPA$T0Mcf=!q%x1fy%(Mn!5t!;Hc6m{EYXmS+^Oe`lX64JhAzFdoR=_+KbCR{jaV{Z z9C3Z3cY$yKr1C9fy^g7e+69@8Mr-7I8`$#V+flyJDf#oZ1Mdtn=9s$iRkCHYG$*Ts zfO7BdBg+m!xxr8B2aS0Q&M}9!=!N8la;Da`%Rs9;yGEtSZ>T3KO_!Hc8u25 z&&iS-Qo{crhtZ^bGN@Nj^ZcmqlU`HtFvEPZzFqvUzHw&-m&`mPEA$xMB3_)Zygb}` z(KMtlwD?E+BN3m&gO|+94l{K>yB%`tB&(kP$PPPJx*hK`K2F7QrqP}jIDF}p^z+{} zvNlICi*2-_vg)AeXB2hdFc#q!J==eR^(yac>)Fn6$3NP~@v0iKHk_A<&wZLWG9PC* z-#;}HmFg5@Q+*-n$Wnm8#I>L**K#@6P>x=*7jQpF)`uCDbXC6U;Z|8?@w1uJU1_R( z(#(f@F8^dHGkR?BCY;$2HW2{?NrZxY!?fREvYOentIx|3!|XY(#3_yb$b%d|yC*V@ z&&(U2x%)OigpGkv9}&Zq&)=&HZkJMqJ+tSe(;PiWM6ITp90r1P%L9Yw#;f+$d`VuB zC+B0b`1^W$=Y$&@7mZ&!aSjHLJ}p=Oe4VdEYTP)e^MstIcu8viineuVk|XI3I-{Pc z-u%d>)^p0!*4B%m)hEsciVBy^e7_V_g)g{y&zh}h8%qHZlNF?=X|OjAEvdfD3V)dw zUfzW0<>|3pr;s02mx4O?#*`VVb40Jm=RH}S7fua7nKV;oihM~3wD(Mms*o|OzkL3} zocMUBYk7sM@0vXQ$w8%izH!e-yR2O2I6Ef2>^T;EyISg;<6JkD@tvDI5sNbAZaPFJ zKytWGR_B?A5U9-{McjaE#>?p%B51wXemQKshSN%CSB7a zra9X`v^CevEZ0A+DatqL>g#~sv{@0H*^lpgYvPMncA!cYX~&kdydO7}CbeI-x|+8x zx%P)dl^Xhn@PtRxFzQ{m3201RiP8($%LxgM9WqK!eLZUtXE%MJZZV>l3od?KSMuF?bz7_IxC5 zMM5du8_qiG?{5}nih9Wu*_<8xgsvHfV0F~d&w%GFcT0tD>auLg%AL$vM?Qq)H*}#8 z(mWxgc&Zwcas~=5Si!7)gIpjr7{Ye&k35!y|9-BI)LLBgjVd{JY4jdA) zI9f5=zgv)TB!*_VZ{XYVIkJtgAcF-3?BAetaRbhA1Gz12bC-Nn2qi7$w60!|531VB zEE%rnH2H>njSFC*pI4;nnzm+LCp^3tbDwk9Gp4a zC$#SCl9#x6g{ycxe?G!iRvZ%z6GJr3Rx6<;k%zPTWSP3VkzjJ+pcg1px%d>KokEAoVT z`)}3wJk%)lQIZPsw6a=w2!$m(F;Y7l`Gj`>pW(|EpBs;wU9_ohW5Z_q>ktiWt)Zk4 zyejtP^3lWO-#<^b=^f#<+UgZk#8~tt?EUN zY)Cm5h$~P)XX?@gBay3=z1WhqxcEOL3M-Ln$V|TTByDwKK^PN|TET zZ42i8FQP<(8TGGEaKT8_^rrrjX!0L~M8`Bt(}fyeik(ewmd~{346PF;+r 5: # 新消息区域 + if current_area: + message_areas.append(current_area) + current_area = [x, y, x, y] + else: + current_area[2] = max(current_area[2], x) + current_area[3] = y + break + if not found_color and current_area: + message_areas.append(current_area) + current_area = None + if current_area: + message_areas.append(current_area) + logger.info(f"找到 {len(message_areas)} 个消息区域") + return message_areas + + +def copy_message(area, message_color): + """复制消息内容并返回消息及其类型""" + x, y, _, _ = area + + if is_click_safe((x + 5, y + 5)): + try: + # 再次检查 + if is_click_safe((x + 5, y + 5)): + # 点击消息区域 + logger.info(f"点击消息区域: ({x + 5}, {y + 5})") + + pyautogui.click(x + 5, y + 5) + time.sleep(0.5) + pyautogui.hotkey('ctrl', 'a') + time.sleep(0.5) + pyautogui.hotkey('ctrl', 'c') + time.sleep(0.5) + + win32clipboard.OpenClipboard() + try: + message = win32clipboard.GetClipboardData(win32con.CF_UNICODETEXT) + except TypeError: + logger.warning("剪贴板中没有文本数据") + message = "" + finally: + win32clipboard.CloseClipboard() + + # 根据颜色判断消息来源 + message_type = "my_message" if message_color == my_message_color else "other_message" + return {"text": message, "type": message_type} + except Exception as e: + logger.error(f"复制消息时发生错误: {str(e)}") + return {"text": "", "type": ""} + else: + logger.info("取消操作以避免点击危险区域") + return {"text": "", "type": ""} + + +def process_visible_messages(): + """处理当前可见的所有消息并加入类型标识""" + screenshot = capture_screen() + message_areas = find_message_areas(screenshot) + processed_messages = [] + + for area in message_areas: + x, y, _, _ = area + pixel_color = screenshot.getpixel((x, y)) # 获取消息颜色 + message_data = copy_message(area, pixel_color[:3]) # 传递消息颜色 + if message_data["text"] and message_data not in processed_messages: + save_to_json(message_data) + processed_messages.append(message_data) + logger.info(f"已处理消息: {message_data['text'][:30]}...") + else: + logger.info("跳过重复或空消息") + + return len(processed_messages), message_areas[-1] if message_areas else None + +def save_to_json(message_data): + """保存消息到JSON文件,按格式区分消息类型""" + data = [] + try: + if os.path.exists(training_data_file): + with open(training_data_file, 'r', encoding='utf-8') as f: + data = json.load(f) + except json.JSONDecodeError: + logger.warning("JSON文件解码错误,创建新的数据列表") + + data.append(message_data) + + with open(training_data_file, 'w', encoding='utf-8') as f: + json.dump(data, f, ensure_ascii=False, indent=4) + + +def locate_avoid_button(): + """定位需要避免点击的按钮""" + screenshot_pil = capture_screen() + screenshot = cv2.cvtColor(np.array(screenshot_pil), cv2.COLOR_RGB2BGR) + template = cv2.imread('dist/fuck_down.png', cv2.IMREAD_GRAYSCALE) + if screenshot is None or template is None: + logger.error("未能加载截图或模板") + return None + screenshot_gray = cv2.cvtColor(screenshot, cv2.COLOR_BGR2GRAY) + res = cv2.matchTemplate(screenshot_gray, template, cv2.TM_CCOEFF_NORMED) + min_val, max_val, min_loc, max_loc = cv2.minMaxLoc(res) + if max_val > 0.5: # 匹配阈值0.5 + button_x, button_y = max_loc + button_width, button_height = template.shape[::-1] + return (button_x, button_y, button_x + button_width, button_y + button_height) + return None + +def is_click_safe(click_position): + """检查点击位置是否安全""" + ax1, ay1 = avoid_area[0] + ax2, ay2 = avoid_area[3] + px, py = click_position + if ax1 <= px <= ax2 and ay1 <= py <= ay2: + return False + return True + +def scroll_chat_window(): + """滚动聊天窗口并处理消息""" + total_processed = 0 + no_new_message_count = 0 + last_message_area = None + + while no_new_message_count < 10: # 连续10次没有新消息时退出 + processed_count, last_area = process_visible_messages() + total_processed += processed_count + + if processed_count == 0: + no_new_message_count += 1 + logger.info(f"未找到新消息,继续滚动 (尝试 {no_new_message_count}/10)") + else: + no_new_message_count = 0 + logger.info(f"本次处理了 {processed_count} 条消息") + + if last_area: + last_message_area = last_area + + if last_message_area: + print("移动 - 位置:", last_message_area) + # 移动到屏幕中央偏上的位置进行滚动 + pyautogui.moveTo(pyautogui.size()[0] // 2, pyautogui.size()[1] // 4) + else: + # 如果没有找到消息区域,移动到屏幕的中间位置 + pyautogui.moveTo(pyautogui.size()[0] // 2, pyautogui.size()[1] // 2) + + pyautogui.scroll(-450) # 向下滚动 + time.sleep(scroll_delay) + + logger.info(f"总共处理了 {total_processed} 条消息") + + +def main(): + hwnd = get_qq_window_position() + if hwnd: + win32gui.SetForegroundWindow(hwnd) # 将QQ窗口置于前端 + time.sleep(1) + scroll_chat_window() + else: + logger.error("无法找到QQ窗口,请确保QQ正在运行。") + + +if __name__ == "__main__": + main() diff --git a/qq_gundong.py b/qq_gundong.py new file mode 100644 index 0000000..27cdb60 --- /dev/null +++ b/qq_gundong.py @@ -0,0 +1,53 @@ +import pyautogui +import time +import win32gui +from tqdm import tqdm + + +def get_qq_window_position(): + """获取QQ窗口位置并置于前台""" + hwnd = win32gui.FindWindow(None, "QQ") + if hwnd: + win32gui.SetForegroundWindow(hwnd) + rect = win32gui.GetWindowRect(hwnd) + return rect + else: + print("未找到QQ窗口") + return None + + +def scroll_chat(): + """自动持续滚动QQ聊天记录""" + rect = get_qq_window_position() + if rect: + # 定位到QQ聊天窗口中间 + center_x = (rect[0] + rect[2]) // 2 + center_y = (rect[1] + rect[3]) // 2 + pyautogui.moveTo(center_x, center_y) + + total_time = 360 # 6分钟 + start_time = time.monotonic() + end_time = start_time + total_time + + # 创建进度条 + with tqdm(total=total_time, desc="Scrolling QQ chat", + bar_format="{l_bar}{bar}| {n:.1f}/{total:.1f}s [{elapsed}<{remaining}, {rate_fmt}{postfix}]") as pbar: + last_update = start_time + while time.monotonic() < end_time: + pyautogui.scroll(1000) # 正数表示向上滚动 + time.sleep(0.05) # 短暂休息以避免CPU占用过高 + + current_time = time.monotonic() + elapsed = current_time - last_update + if elapsed >= 0.1: # 每0.1秒更新一次进度条 + pbar.update(elapsed) + last_update = current_time + + # 更新预计完成时间 + remaining = end_time - current_time + eta = time.strftime("%H:%M:%S", time.localtime(time.time() + remaining)) + pbar.set_postfix(ETA=eta) + + +if __name__ == "__main__": + scroll_chat() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1f67f38 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +pyautogui==0.9.53 +opencv-python==4.5.5.64 +pillow==8.4.0 +pywin32==301 +numpy==1.21.5 \ No newline at end of file