[
  {
    "path": ".gitignore",
    "content": ".idea/\n.DS_Store\n# Byte-compiled / optimized / DLL files\n__pycache__/\n*.py[cod]\n*$py.class\n\n# C extensions\n*.so\n\n# Distribution / packaging\n.Python\nbuild/\ndevelop-eggs/\ndist/\ndownloads/\neggs/\n.eggs/\nlib/\nlib64/\nparts/\nsdist/\nvar/\nwheels/\n*.egg-info/\n.installed.cfg\n*.egg\nMANIFEST\n\n# PyInstaller\n#  Usually these files are written by a python script from a template\n#  before PyInstaller builds the exe, so as to inject date/other infos into it.\n*.manifest\n*.spec\n\n# Installer logs\npip-log.txt\npip-delete-this-directory.txt\n\n# Unit test / coverage reports\nhtmlcov/\n.tox/\n.coverage\n.coverage.*\n.cache\nnosetests.xml\ncoverage.xml\n*.cover\n.hypothesis/\n.pytest_cache/\n\n# Translations\n*.mo\n*.pot\n\n# Django stuff:\n*.log\nlocal_settings.py\ndb.sqlite3\n\n# Flask stuff:\ninstance/\n.webassets-cache\n\n# Scrapy stuff:\n.scrapy\n\n# Sphinx documentation\ndocs/_build/\n\n# PyBuilder\ntarget/\n\n# Jupyter Notebook\n.ipynb_checkpoints\n\n# pyenv\n.python-version\n\n# celery beat schedule file\ncelerybeat-schedule\n\n# SageMath parsed files\n*.sage.py\n\n# Environments\n.env\n.venv\nenv/\nvenv/\nENV/\nenv.bak/\nvenv.bak/\n\n# Spyder project settings\n.spyderproject\n.spyproject\n\n# Rope project settings\n.ropeproject\n\n# mkdocs documentation\n/site\n\n# mypy\n.mypy_cache/\n\nface/\nautojump.png\noptimized.png"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 神奇的战士-王松\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# 如何在抖音上找到漂亮小姐姐----抖音机器人\n\n[![Open Source Love](https://badges.frapsoft.com/os/v1/open-source.svg?v=103)](https://github.com/ellerbrock/open-source-badge/) [![MIT Licence](https://badges.frapsoft.com/os/mit/mit.svg?v=103)](https://opensource.org/licenses/mit-license.php)      \n\n最近沉迷于抖音无法自拔，常常花好几个小时在抖音**漂亮小姐姐**身上。\n\n本着**高效、直接**地找到漂亮小姐姐的核心思想，我用 Python + ADB 做了一个 Python 抖音机器人 Douyin-Bot。\n\n<img src=\"./screenshot/demo.gif\" title=\"Logo\"  width=\"300\"> <img src=\"./screenshot/auto_reply.gif\" title=\"Logo\"  width=\"300\">\n    \n##  特性\n\n- [x] **自动翻页**\n- [x] **颜值检测**\n- [x] **人脸识别**\n- [x] **自动点赞**\n- [x] **自动关注**\n- [x] 随机防 Ban\n- [x] **自动评论**\n\n## 原理\n\n- 打开《抖音短视频》APP，进入主界面\n- 获取手机截图，并对截图进行压缩 (Size < 1MB)；\n- 请求 [人脸识别 API](http://ai.qq.com/)；\n- 解析返回的人脸 Json 信息，对人脸检测切割；\n- 当颜值大于门限值 `BEAUTY_THRESHOLD`时，点赞并关注；\n- 下一页，返回第一步；\n\n## 使用教程\n\n- Python版本：3.0及以上\n- 相关软件工具安装和使用步骤请参考 [wechat_jump_game](https://github.com/wangshub/wechat_jump_game) 和 [Android 操作步骤](https://github.com/wangshub/wechat_jump_game/wiki/Android-%E5%92%8C-iOS-%E6%93%8D%E4%BD%9C%E6%AD%A5%E9%AA%A4)\n- 在 [ai.qq.com](https://ai.qq.com) 免费申请 `AppKey` 和 `AppID`\n1. 获取源码：`git clone https://github.com/wangshub/Douyin-Bot.git`\n2. 进入源码目录： `cd Douyin-Bot`\n3. 安装依赖： `pip install -r requirements.txt`\n4. 运行程序：`python douyin-bot.py`\n5. [自动评论](https://zhuanlan.zhihu.com/p/57242891)(可选)：`python3 douyin-bot.py --reply`\n\n## 注意\n\n- 目前暂时只适配了 一加5(1920x1080 分辨率)，如果手机不是该分辨率，请修改 `config/` 文件夹下面的配置文件；\n- `config.json`配置文件参考：\n    - `center_point`: 屏幕中心点`(x, y)`，区域范围 `rx, ry`\n    - `follow_bottom`: 关注按钮位置`(x, y)`，区域范围 `rx, ry`\n    - `star_bottom`: 点赞按钮位置`(x, y)`，区域范围 `rx, ry`\n    \n\n## 脸部截取\n\n![](./screenshot/faces.png)\n\n## LICENSE\n\nMIT\n\n欢迎 Star 和 Fork ~\n\n如果你有什么问题请提 Issue，或者关注我的微信公众号留言，我都会一一解答\n\n<p align=\"center\">\n<img src=\"screenshot/qrcode.jpg\" title=\"Logo\" width=\"150\">\n</>\n"
  },
  {
    "path": "Tools/README.md",
    "content": "## 所有实验都在该文件夹下运行即可，已放上测试用的代码。免去配置 adb 的麻烦\n\n- 复制 wechat_jump_game 根目录下的 config 文件夹以及 wechat_jump_py3.py 文件到本目录下\n- 按住 `shift` + 右键  选择在该文件夹下打开命令窗口（Windows 用户请自行 cmd）\n- 打开安卓手机的 usb 调试，并连接电脑，在终端输入 `adb devices` 进行测试，如果有连接设备号则表示成功\n- 打开微信小游戏，然后运行代码 `python wechat_jump_py3.py`，点击出现的图形起点和终点，棋子自动跳转\n\n**注意：这里使用的是不需要配置的 adb 方式，需要在该文件下操作，至于如何自动跳转，只需改变执行脚本即可，这里只做演示**\n"
  },
  {
    "path": "common/UnicodeStreamFilter.py",
    "content": "# -*- coding: utf-8 -*-\nimport sys\n\nif sys.version_info.major != 3:\n    class UnicodeStreamFilter:\n\n        def __init__(self, target):\n            self.target = target\n            self.encoding = 'utf-8'\n            self.errors = 'replace'\n            self.encode_to = self.target.encoding\n\n\n    def write(self, s):\n        if type(s) == str:\n            s = s.decode(\"utf-8\")\n        s = s.encode(self.encode_to, self.errors).decode(self.encode_to)\n        self.target.write(s)\n\n\n    if sys.stdout.encoding == 'cp936':\n        sys.stdout = UnicodeStreamFilter(sys.stdout)\nelse:\n    pass\n"
  },
  {
    "path": "common/__init__.py",
    "content": ""
  },
  {
    "path": "common/apiutil.py",
    "content": "#-*- coding: UTF-8 -*-\r\nimport hashlib\r\nimport urllib\r\nfrom urllib import parse\r\nimport urllib.request\r\nimport base64\r\nimport json\r\nimport time\r\n\r\nurl_preffix='https://api.ai.qq.com/fcgi-bin/'\r\n\r\n\r\ndef setParams(array, key, value):\r\n    array[key] = value\r\n\r\n\r\ndef genSignString(parser):\r\n    uri_str = ''\r\n    for key in sorted(parser.keys()):\r\n        if key == 'app_key':\r\n            continue\r\n        uri_str += \"%s=%s&\" % (key, parse.quote(str(parser[key]), safe=''))\r\n    sign_str = uri_str + 'app_key=' + parser['app_key']\r\n\r\n    hash_md5 = hashlib.md5(sign_str.encode('utf-8'))\r\n    return hash_md5.hexdigest().upper()\r\n\r\n\r\nclass AiPlat(object):\r\n    def __init__(self, app_id, app_key):\r\n        self.app_id = app_id\r\n        self.app_key = app_key\r\n        self.data = {}\r\n        self.url_data = ''\r\n\r\n    def invoke(self, params):\r\n        self.url_data = urllib.parse.urlencode(params).encode(\"utf-8\")\r\n        req = urllib.request.Request(self.url, self.url_data)\r\n        try:\r\n            rsp = urllib.request.urlopen(req)\r\n            str_rsp = rsp.read().decode('utf-8')\r\n            dict_rsp = json.loads(str_rsp)\r\n            return dict_rsp\r\n        except Exception as e:\r\n            print(e)\r\n            return {'ret': -1}\r\n\r\n    def face_detectface(self, image, mode):\r\n        self.url = url_preffix + 'face/face_detectface'\r\n        setParams(self.data, 'app_id', self.app_id)\r\n        setParams(self.data, 'app_key', self.app_key)\r\n        setParams(self.data, 'mode', mode)\r\n        setParams(self.data, 'time_stamp', int(time.time()))\r\n        setParams(self.data, 'nonce_str', int(time.time()))\r\n        image_data = base64.b64encode(image)\r\n        setParams(self.data, 'image', image_data.decode(\"utf-8\"))\r\n        sign_str = genSignString(self.data)\r\n        setParams(self.data, 'sign', sign_str)\r\n        return self.invoke(self.data)\r\n\r\n"
  },
  {
    "path": "common/auto_adb.py",
    "content": "# -*- coding: utf-8 -*-\nimport os\nimport subprocess\nimport platform\n\n\nclass auto_adb():\n    def __init__(self):\n        try:\n            adb_path = 'adb'\n            subprocess.Popen([adb_path], stdout=subprocess.PIPE,\n                             stderr=subprocess.PIPE)\n            self.adb_path = adb_path\n        except OSError:\n            if platform.system() == 'Windows':\n                adb_path = os.path.join('Tools', \"adb\", 'adb.exe')\n                try:\n                    subprocess.Popen(\n                        [adb_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n                    self.adb_path = adb_path\n                except OSError:\n                    pass\n            else:\n                try:\n                    subprocess.Popen(\n                        [adb_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n                except OSError:\n                    pass\n            print('请安装 ADB 及驱动并配置环境变量')\n            exit(1)\n\n    def get_screen(self):\n        process = os.popen(self.adb_path + ' shell wm size')\n        output = process.read()\n        return output\n\n    def run(self, raw_command):\n        print(raw_command)\n        command = '{} {}'.format(self.adb_path, raw_command)\n        process = os.popen(command)\n        output = process.read()\n        return output\n\n    def test_device(self):\n        print('检查设备是否连接...')\n        command_list = [self.adb_path, 'devices']\n        process = subprocess.Popen(command_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE)\n        output = process.communicate()\n        if output[0].decode('utf8') == 'List of devices attached\\n\\n':\n            print('未找到设备')\n            print('adb 输出:')\n            for each in output:\n                print(each.decode('utf8'))\n            exit(1)\n        print('设备已连接')\n        print('adb 输出:')\n        for each in output:\n            print(each.decode('utf8'))\n\n    def test_density(self):\n        process = os.popen(self.adb_path + ' shell wm density')\n        output = process.read()\n        return output\n\n    def test_device_detail(self):\n        process = os.popen(self.adb_path + ' shell getprop ro.product.device')\n        output = process.read()\n        return output\n\n    def test_device_os(self):\n        process = os.popen(self.adb_path + ' shell getprop ro.build.version.release')\n        output = process.read()\n        return output\n\n    def adb_path(self):\n        return self.adb_path\n"
  },
  {
    "path": "common/compression.py",
    "content": "from PIL import Image\nimport math\nimport os\n\n\ndef resize_image(origin_img, optimize_img, threshold):\n    \"\"\"\n    shrink image by size\n    :param origin_img:\n    :param optimize_img:\n    :param threshold:\n    :return:\n    \"\"\"\n    file_size = os.path.getsize(origin_img)\n    with Image.open(origin_img) as im:\n        if file_size > threshold:\n            width, height = im.size\n\n            if width >= height:\n                new_width = int(math.sqrt(threshold / 2))\n                new_height = int(new_width * height * 1.0 / width)\n            else:\n                new_height = int(math.sqrt(threshold / 2))\n                new_width = int(new_height * width * 1.0 / height)\n\n            resized_im = im.resize((new_width, new_height))\n            resized_im.save(optimize_img)\n        else:\n            im.save(optimize_img)\n\n\n"
  },
  {
    "path": "common/config.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\n调取配置文件和屏幕分辨率的代码\n\"\"\"\nimport os\nimport sys\nimport json\nimport re\n\nfrom common.auto_adb import auto_adb\n\nadb = auto_adb()\n\n\ndef open_accordant_config():\n    \"\"\"\n    调用配置文件\n    \"\"\"\n    screen_size = _get_screen_size()\n    config_file = \"{path}/config/{screen_size}/config.json\".format(\n        path=sys.path[0],\n        screen_size=screen_size\n    )\n\n    # 优先获取执行文件目录的配置文件\n    here = sys.path[0]\n    for file in os.listdir(here):\n        if re.match(r'(.+)\\.json', file):\n            file_name = os.path.join(here, file)\n            with open(file_name, 'r') as f:\n                print(\"Load config file from {}\".format(file_name))\n                return json.load(f)\n\n    # 根据分辨率查找配置文件\n    if os.path.exists(config_file):\n        with open(config_file, 'r') as f:\n            print(\"正在从 {} 加载配置文件\".format(config_file))\n            return json.load(f)\n    else:\n        with open('{}/config/default.json'.format(sys.path[0]), 'r') as f:\n            print(\"Load default config\")\n            return json.load(f)\n\n\ndef _get_screen_size():\n    \"\"\"\n    获取手机屏幕大小\n    \"\"\"\n    size_str = adb.get_screen()\n    m = re.search(r'(\\d+)x(\\d+)', size_str)\n    if m:\n        return \"{height}x{width}\".format(height=m.group(2), width=m.group(1))\n    return \"1920x1080\"\n"
  },
  {
    "path": "common/debug.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\n这是debug的代码，当DEBUG_SWITCH开关开启的时候，会将各种信息存在本地，方便检查故障\n\"\"\"\nimport os\nimport sys\nimport shutil\nimport math\nfrom PIL import ImageDraw\nimport platform\nif platform.system() == 'Windows':\n    os.chdir(os.getcwd().replace('\\\\common', ''))\n    path_split = \"\\\\\"\nelse:\n    os.chdir(os.getcwd().replace('/common', ''))\n    path_split = '/'\n# from common import ai\ntry:\n    from common.auto_adb import auto_adb\nexcept ImportError as ex:\n    print(ex)\n    print('请将脚本放在项目根目录中运行')\n    print('请检查项目根目录中的 common 文件夹是否存在')\n    exit(1)\nscreenshot_backup_dir = 'screenshot_backups'\nadb = auto_adb()\n\n\ndef make_debug_dir(screenshot_backup_dir):\n    \"\"\"\n    创建备份文件夹\n    \"\"\"\n    if not os.path.isdir(screenshot_backup_dir):\n        os.mkdir(screenshot_backup_dir)\n\n\ndef backup_screenshot(ts):\n    \"\"\"\n    为了方便失败的时候 debug\n    \"\"\"\n    make_debug_dir(screenshot_backup_dir)\n    shutil.copy('{}{}autojump.png'.format(os.getcwd(), path_split),\n                os.path.join(os.getcwd(), screenshot_backup_dir,\n                             str(ts) + '.png'))\n\n\ndef save_debug_screenshot(ts, im, piece_x, piece_y, board_x, board_y):\n    \"\"\"\n    对 debug 图片加上详细的注释\n    \n    \"\"\"\n    make_debug_dir(screenshot_backup_dir)\n    draw = ImageDraw.Draw(im)\n    draw.line((piece_x, piece_y) + (board_x, board_y), fill=2, width=3)\n    draw.line((piece_x, 0, piece_x, im.size[1]), fill=(255, 0, 0))\n    draw.line((0, piece_y, im.size[0], piece_y), fill=(255, 0, 0))\n    draw.line((board_x, 0, board_x, im.size[1]), fill=(0, 0, 255))\n    draw.line((0, board_y, im.size[0], board_y), fill=(0, 0, 255))\n    draw.ellipse((piece_x - 10, piece_y - 10, piece_x + 10, piece_y + 10), fill=(255, 0, 0))\n    draw.ellipse((board_x - 10, board_y - 10, board_x + 10, board_y + 10), fill=(0, 0, 255))\n    del draw\n    im.save(os.path.join(os.getcwd(), screenshot_backup_dir,\n                         '#' + str(ts) + '.png'))\n\n\ndef computing_error(last_press_time, target_board_x, target_board_y, last_piece_x, last_piece_y, temp_piece_x,\n                    temp_piece_y):\n    \"\"\"\n    计算跳跃实际误差\n    \"\"\"\n    target_distance = math.sqrt(\n        (target_board_x - last_piece_x) ** 2 + (target_board_y - last_piece_y) ** 2)  # 上一轮目标跳跃距离\n    actual_distance = math.sqrt((temp_piece_x - last_piece_x) ** 2 + (temp_piece_y - last_piece_y) ** 2)  # 上一轮实际跳跃距离\n    jump_error_value = math.sqrt((target_board_x - temp_piece_x) ** 2 + (target_board_y - temp_piece_y) ** 2)  # 跳跃误差\n\n    print(round(target_distance), round(jump_error_value), round(actual_distance), round(last_press_time))\n    ''''# 将结果采集进学习字典\n    if last_piece_x > 0 and last_press_time > 0:\n        ai.add_data(round(actual_distance, 2), round(last_press_time))\n        # print(round(actual_distance), round(last_press_time))'''\n\n\ndef dump_device_info():\n    \"\"\"\n    显示设备信息\n    \"\"\"\n    size_str = adb.get_screen()\n    device_str = adb.test_device_detail()\n    phone_os_str = adb.test_device_os()\n    density_str = adb.test_density()\n    print(\"\"\"**********\nScreen: {size}\nDensity: {dpi}\nDevice: {device}\nPhone OS: {phone_os}\nHost OS: {host_os}\nPython: {python}\n**********\"\"\".format(\n        size=size_str.replace('\\n', ''),\n        dpi=density_str.replace('\\n', ''),\n        device=device_str.replace('\\n', ''),\n        phone_os=phone_os_str.replace('\\n', ''),\n        host_os=sys.platform,\n        python=sys.version\n    ))\n"
  },
  {
    "path": "common/excel_keyword.py",
    "content": "import xlrd\nimport random\n\n\ndef get_random_keyword(filename):\n    \"\"\"\n    get random row of filename\n    :param filename:\n    :return:\n    \"\"\"\n    try:\n        with xlrd.open_workbook(filename) as data:\n            table = data.sheets()[0]\n            data_list = []\n            data_list.extend(table.col_values(0))\n            return data_list[random.randint(0, len(data_list))]\n    except Exception as error:\n        print(Exception)\n        return 'BRAVO'\n\n\nif __name__ == '__main__':\n    reply = get_random_keyword('../reply/keyword.xlsx')\n    print(reply)"
  },
  {
    "path": "common/screenshot.py",
    "content": "# -*- coding: utf-8 -*-\n\"\"\"\n手机屏幕截图的代码\n\"\"\"\nimport subprocess\nimport os\nimport sys\nfrom PIL import Image\nfrom io import StringIO\n\ntry:\n    from common.auto_adb import auto_adb\nexcept Exception as ex:\n    print(ex)\n    print('请将脚本放在项目根目录中运行')\n    print('请检查项目根目录中的 common 文件夹是否存在')\n    exit(1)\nadb = auto_adb()\n# SCREENSHOT_WAY 是截图方法，经过 check_screenshot 后，会自动递减，不需手动修改\nSCREENSHOT_WAY = 3\n\n\ndef pull_screenshot():\n    \"\"\"\n    获取屏幕截图，目前有 0 1 2 3 四种方法，未来添加新的平台监测方法时，\n    可根据效率及适用性由高到低排序\n    \"\"\"\n    global SCREENSHOT_WAY\n    if 1 <= SCREENSHOT_WAY <= 3:\n        process = subprocess.Popen(\n            adb.adb_path + ' shell screencap -p',\n            shell=True, stdout=subprocess.PIPE)\n        binary_screenshot = process.stdout.read()\n        if SCREENSHOT_WAY == 2:\n            binary_screenshot = binary_screenshot.replace(b'\\r\\n', b'\\n')\n        elif SCREENSHOT_WAY == 1:\n            binary_screenshot = binary_screenshot.replace(b'\\r\\r\\n', b'\\n')\n        return Image.open(StringIO(binary_screenshot))\n    elif SCREENSHOT_WAY == 0:\n        adb.run('shell screencap -p /sdcard/autojump.png')\n        adb.run('pull /sdcard/autojump.png .')\n        return Image.open('./autojump.png')\n\n\ndef check_screenshot():\n    \"\"\"\n    检查获取截图的方式\n    \"\"\"\n    global SCREENSHOT_WAY\n    if os.path.isfile('autojump.png'):\n        try:\n            os.remove('autojump.png')\n        except Exception:\n            pass\n    if SCREENSHOT_WAY < 0:\n        print('暂不支持当前设备')\n        sys.exit()\n    try:\n        im = pull_screenshot()\n        im.load()\n        im.close()\n        print('采用方式 {} 获取截图'.format(SCREENSHOT_WAY))\n    except Exception:\n        SCREENSHOT_WAY -= 1\n        check_screenshot()\n"
  },
  {
    "path": "config/1280x720/config.json",
    "content": "{\n  \"center_point\":{\n    \"x\": 333,\n    \"y\": 700,\n    \"rx\": 10,\n    \"ry\": 300\n  },\n  \"follow_bottom\":{\n    \"x\": 648,\n    \"y\": 534,\n    \"rx\": 10,\n    \"ry\": 10\n  },\n  \"star_bottom\":{\n    \"x\": 641,\n    \"y\": 642,\n    \"rx\": 10,\n    \"ry\": 10\n  }\n\n}\n"
  },
  {
    "path": "config/1920x1080/config.json",
    "content": "{\n  \"center_point\":{\n    \"x\": 540,\n    \"y\": 965,\n    \"rx\": 10,\n    \"ry\": 300\n  },\n  \"follow_bottom\":{\n    \"x\": 983,\n    \"y\": 854,\n    \"rx\": 10,\n    \"ry\": 10\n  },\n  \"star_bottom\":{\n    \"x\": 986,\n    \"y\": 994,\n    \"rx\": 10,\n    \"ry\": 10\n  },\n  \"comment_bottom\":{\n    \"x\": 1000,\n    \"y\": 1240,\n    \"rx\": 10,\n    \"ry\": 10\n  },\n    \"comment_text\":{\n    \"x\": 300,\n    \"y\": 1855,\n    \"rx\": 10,\n    \"ry\": 10\n  },\n   \"comment_send\":{\n    \"x\": 1010,\n    \"y\": 1690,\n    \"rx\": 10,\n    \"ry\": 10\n  }\n\n}\n"
  },
  {
    "path": "config/default.json",
    "content": "{\n  \"center_point\":{\n    \"x\": 540,\n    \"y\": 965,\n    \"rx\": 10,\n    \"ry\": 300\n  },\n  \"follow_bottom\":{\n    \"x\": 990,\n    \"y\": 950,\n    \"rx\": 10,\n    \"ry\": 10\n  },\n  \"star_bottom\":{\n    \"x\": 1000,\n    \"y\": 1083,\n    \"rx\": 10,\n    \"ry\": 10\n  },\n  \"comment_bottom\":{\n    \"x\": 1000,\n    \"y\": 1240,\n    \"rx\": 10,\n    \"ry\": 10\n  },\n    \"comment_text\":{\n    \"x\": 300,\n    \"y\": 1855,\n    \"rx\": 10,\n    \"ry\": 10\n  },\n   \"comment_send\":{\n    \"x\": 1010,\n    \"y\": 1690,\n    \"rx\": 10,\n    \"ry\": 10\n  }\n\n}\n"
  },
  {
    "path": "douyin-bot.py",
    "content": "# -*- coding: utf-8 -*-\nimport sys\nimport random\nimport time\nfrom PIL import Image\nimport argparse\n\nif sys.version_info.major != 3:\n    print('Please run under Python3')\n    exit(1)\ntry:\n    from common import debug, config, screenshot, UnicodeStreamFilter\n    from common.auto_adb import auto_adb\n    from common import apiutil\n    from common.compression import resize_image\nexcept Exception as ex:\n    print(ex)\n    print('请将脚本放在项目根目录中运行')\n    print('请检查项目根目录中的 common 文件夹是否存在')\n    exit(1)\n\nVERSION = \"0.0.1\"\n\n# 我申请的 Key，随便用，嘻嘻嘻\n# 申请地址 http://ai.qq.com\nAppID = '1106858595'\nAppKey = 'bNUNgOpY6AeeJjFu'\n\nDEBUG_SWITCH = True\nFACE_PATH = 'face/'\n\nadb = auto_adb()\nadb.test_device()\nconfig = config.open_accordant_config()\n\n# 审美标准\nBEAUTY_THRESHOLD = 80\n\n# 最小年龄\nGIRL_MIN_AGE = 14\n\n\ndef yes_or_no():\n    \"\"\"\n    检查是否已经为启动程序做好了准备\n    \"\"\"\n    while True:\n        yes_or_no = str(input('请确保手机打开了 ADB 并连接了电脑，'\n                              '然后打开手机软件，确定开始？[y/n]:'))\n        if yes_or_no == 'y':\n            break\n        elif yes_or_no == 'n':\n            print('谢谢使用')\n            exit(0)\n        else:\n            print('请重新输入')\n\n\ndef _random_bias(num):\n    \"\"\"\n    random bias\n    :param num:\n    :return:\n    \"\"\"\n    return random.randint(-num, num)\n\n\ndef next_page():\n    \"\"\"\n    翻到下一页\n    :return:\n    \"\"\"\n    cmd = 'shell input swipe {x1} {y1} {x2} {y2} {duration}'.format(\n        x1=config['center_point']['x'],\n        y1=config['center_point']['y']+config['center_point']['ry'],\n        x2=config['center_point']['x'],\n        y2=config['center_point']['y'],\n        duration=200\n    )\n    adb.run(cmd)\n    time.sleep(1.5)\n\n\ndef follow_user():\n    \"\"\"\n    关注用户\n    :return:\n    \"\"\"\n    cmd = 'shell input tap {x} {y}'.format(\n        x=config['follow_bottom']['x'] + _random_bias(10),\n        y=config['follow_bottom']['y'] + _random_bias(10)\n    )\n    adb.run(cmd)\n    time.sleep(0.5)\n\n\ndef thumbs_up():\n    \"\"\"\n    点赞\n    :return:\n    \"\"\"\n    cmd = 'shell input tap {x} {y}'.format(\n        x=config['star_bottom']['x'] + _random_bias(10),\n        y=config['star_bottom']['y'] + _random_bias(10)\n    )\n    adb.run(cmd)\n    time.sleep(0.5)\n\n\ndef tap(x, y):\n    cmd = 'shell input tap {x} {y}'.format(\n        x=x + _random_bias(10),\n        y=y + _random_bias(10)\n    )\n    adb.run(cmd)\n\n\ndef auto_reply():\n\n    msg = \"垆边人似月，皓腕凝霜雪。就在刚刚，我的心动了一下，小姐姐你好可爱呀_Powered_By_Python\"\n\n    # 点击右侧评论按钮\n    tap(config['comment_bottom']['x'], config['comment_bottom']['y'])\n    time.sleep(1)\n    #弹出评论列表后点击输入评论框\n    tap(config['comment_text']['x'], config['comment_text']['y'])\n    time.sleep(1)\n    #输入上面msg内容 ，注意要使用ADB keyboard  否则不能自动输入，参考： https://www.jianshu.com/p/2267adf15595\n    cmd = 'shell am broadcast -a ADB_INPUT_TEXT --es msg {text}'.format(text=msg)\n    adb.run(cmd)\n    time.sleep(1)\n    # 点击发送按钮\n    tap(config['comment_send']['x'], config['comment_send']['y'])\n    time.sleep(0.5)\n\n    # 触发返回按钮, keyevent 4 对应安卓系统的返回键，参考KEY 对应按钮操作：  https://www.cnblogs.com/chengchengla1990/p/4515108.html\n    cmd = 'shell input keyevent 4'\n    adb.run(cmd)\n\n\ndef parser():\n    ap = argparse.ArgumentParser()\n    ap.add_argument(\"-r\", \"--reply\", action='store_true',\n                    help=\"auto reply\")\n    args = vars(ap.parse_args())\n    return args\n\n\ndef main():\n    \"\"\"\n    main\n    :return:\n    \"\"\"\n    print('程序版本号：{}'.format(VERSION))\n    print('激活窗口并按 CONTROL + C 组合键退出')\n    debug.dump_device_info()\n    screenshot.check_screenshot()\n\n    cmd_args = parser()\n\n    while True:\n        next_page()\n\n        time.sleep(1)\n        screenshot.pull_screenshot()\n\n        resize_image('autojump.png', 'optimized.png', 1024*1024)\n\n        with open('optimized.png', 'rb') as bin_data:\n            image_data = bin_data.read()\n\n        ai_obj = apiutil.AiPlat(AppID, AppKey)\n        rsp = ai_obj.face_detectface(image_data, 0)\n\n        major_total = 0\n        minor_total = 0\n\n        if rsp['ret'] == 0:\n            beauty = 0\n            for face in rsp['data']['face_list']:\n\n                msg_log = '[INFO] gender: {gender} age: {age} expression: {expression} beauty: {beauty}'.format(\n                    gender=face['gender'],\n                    age=face['age'],\n                    expression=face['expression'],\n                    beauty=face['beauty'],\n                )\n                print(msg_log)\n                face_area = (face['x'], face['y'], face['x']+face['width'], face['y']+face['height'])\n                img = Image.open(\"optimized.png\")\n                cropped_img = img.crop(face_area).convert('RGB')\n                cropped_img.save(FACE_PATH + face['face_id'] + '.png')\n                # 性别判断\n                if face['beauty'] > beauty and face['gender'] < 50:\n                    beauty = face['beauty']\n\n                if face['age'] > GIRL_MIN_AGE:\n                    major_total += 1\n                else:\n                    minor_total += 1\n\n            # 是个美人儿~关注点赞走一波\n            if beauty > BEAUTY_THRESHOLD and major_total > minor_total:\n                print('发现漂亮妹子！！！')\n                thumbs_up()\n                follow_user()\n\n                if cmd_args['reply']:\n                    auto_reply()\n\n        else:\n            print(rsp)\n            continue\n\n\nif __name__ == '__main__':\n    try:\n        # yes_or_no()\n        main()\n    except KeyboardInterrupt:\n        adb.run('kill-server')\n        print('谢谢使用')\n        exit(0)\n"
  },
  {
    "path": "example/test_crop.py",
    "content": "from PIL import Image\n\nim = Image.open(\"../autojump.png\")\nw, h = im.size\n\narea = (0, 0, 50, 50)\n\nim_croped = im.crop(area)\n\nim_croped.show()\n"
  },
  {
    "path": "example/test_plot.py",
    "content": "import matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\nimport numpy as np\n\n# img = mpimg.imread('../screenshot/main_page.jpeg')\n# img = mpimg.imread('../screenshot/main_page.jpeg')\n# img = mpimg.imread('../screenshot/news_normal.jpeg')\n# img = mpimg.imread('../screenshot/news_comment.jpeg')\n# img = mpimg.imread('../screenshot/comment_comment.jpeg')\n# img = mpimg.imread('../screenshot/add_comment.jpeg')\n\nimg = mpimg.imread('../autojump.png')\n\nimgplot = plt.imshow(img)\nplt.show()"
  },
  {
    "path": "example/test_readExcel.py",
    "content": "import xlrd\nimport random\n#打开excel文件\ndata=xlrd.open_workbook('../reply/keyword.xlsx')\n#获取第一张工作表（通过索引的方式）\ntable=data.sheets()[0]\n#data_list用来存放数据\ndata_list=[]\n#将table中第一行的数据读取并添加到data_list中\ndata_list.extend(table.col_values(0))\n#打印出第一行的全部数据\nfor item in data_list:\n    print(item)\n\n\n"
  },
  {
    "path": "example/test_textInput.py",
    "content": "import os\n\n\ndef adb_keyboard_input(text):\n    \"\"\"\n    adb keyboard app input unicode text\n    :param text:\n    :return:\n    \"\"\"\n    cmd = 'adb shell am broadcast -a ADB_INPUT_TEXT --es msg {text}'.format(text=text.replace(' ', '%s'))\n    os.system(cmd)\n\n\nadb_keyboard_input('hello world')"
  },
  {
    "path": "reply/data.json",
    "content": "{\n  \"data\":[\n          \"你好，陌生人\"\n  ]\n}"
  },
  {
    "path": "requirements.txt",
    "content": "matplotlib==2.2.0\nxlrd==1.1.0\npandas==0.22.0\nnumpy==1.14.1\nPillow==5.1.0\nscikit-learn==0.19.1"
  }
]