├── .github └── workflows │ ├── docker-image.yml │ └── docker-jcr.yml ├── Dockerfile ├── LICENSE ├── README.md ├── VERSION ├── app ├── androiddevice.py ├── app.py ├── devices.yaml ├── dist │ └── mibox │ │ ├── bottom.png │ │ ├── bottom_over.png │ │ ├── clickleft.png │ │ ├── clickleft_over.png │ │ ├── clickright.png │ │ ├── clickright_over.png │ │ ├── home.png │ │ ├── home_over.png │ │ ├── left.png │ │ ├── left_over.png │ │ ├── microphone.png │ │ ├── microphone_over.png │ │ ├── ok.png │ │ ├── ok_over.png │ │ ├── power.png │ │ ├── power_over.png │ │ ├── remote-back.png │ │ ├── top.png │ │ ├── top_over.png │ │ ├── volume.png │ │ ├── volume_over.png │ │ ├── volumedown.png │ │ └── volumedown_over.png ├── keys │ ├── adb │ └── adb.pub ├── sqliteconnector.py └── templates │ └── mibox.html └── requirements.txt /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: "Docker Build" 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | 7 | jobs: 8 | docker: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | 14 | - name: Set up QEMU 15 | uses: docker/setup-qemu-action@v1 16 | 17 | - name: Set up Docker Buildx 18 | uses: docker/setup-buildx-action@v1 19 | 20 | - name: Login to DockerHub 21 | uses: docker/login-action@v1 22 | with: 23 | username: ${{ secrets.DOCKERHUB_USERNAME }} 24 | password: ${{ secrets.DOCKERHUB_TOKEN }} 25 | 26 | - name: Get current date 27 | id: getDate 28 | run: echo "::set-output name=date::$(date +'%Y-%m-%d')" 29 | 30 | - name: Get semantic version from file 31 | id: getSemver 32 | run: echo "::set-output name=semver::$(cat VERSION | tr -d ' \t\n\r' )" 33 | 34 | - name: Build and push 35 | uses: docker/build-push-action@v2 36 | with: 37 | context: . 38 | platforms: linux/amd64,linux/arm64,linux/arm/v7 39 | push: true 40 | tags: | 41 | techblog/adb-api:latest 42 | techblog/adb-api:${{ steps.getSemver.outputs.semver }} 43 | -------------------------------------------------------------------------------- /.github/workflows/docker-jcr.yml: -------------------------------------------------------------------------------- 1 | name: "JCR Docker Build" 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | 7 | 8 | jobs: 9 | docker: 10 | runs-on: ubuntu-latest 11 | steps: 12 | 13 | - name: Checkout 14 | uses: actions/checkout@v2 15 | 16 | - name: Set up QEMU 17 | uses: docker/setup-qemu-action@v1 18 | 19 | - name: Set up Docker Buildx 20 | uses: docker/setup-buildx-action@v1 21 | 22 | - name: Get current date 23 | id: getDate 24 | run: echo "::set-output name=date::$(date +'%Y-%m-%d')" 25 | 26 | - name: Get semantic version from file 27 | id: getSemver 28 | run: echo "::set-output name=semver::$(cat VERSION | tr -d ' \t\n\r' )" 29 | 30 | 31 | - name: Login to JFrog JCR 32 | run: | 33 | echo ${{ secrets.JCR_USERNAME }} | docker login ${{ secrets.JCR_URL }} --username ${{ secrets.JCR_USERNAME }} --password ${{ secrets.JCR_TOKEN }} 34 | 35 | 36 | - name: Build and push Docker image 37 | run: | 38 | IMAGE_NAME=${{ secrets.JCR_URL }}/docker/adb-api 39 | 40 | # Build and push latest 41 | docker buildx create --use 42 | docker buildx build --platform linux/amd64,linux/arm64 --push -t $IMAGE_NAME:latest -t $IMAGE_NAME:${{ steps.getSemver.outputs.semver }} . 43 | 44 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM techblog/fastapi:latest 2 | 3 | LABEL maintainer="tomer.klein@gmail.com" 4 | 5 | 6 | ENV PYTHONIOENCODING=utf-8 7 | ENV LANG=C.UTF-8 8 | ENV LOG_LEVEL "DEBUG" 9 | RUN apt update -yqq 10 | 11 | RUN apt install -yqq python3-pip && \ 12 | apt install -yqq libffi-dev && \ 13 | apt install -yqq libssl-dev 14 | 15 | RUN mkdir -p /app/config 16 | 17 | COPY requirements.txt /tmp 18 | 19 | RUN pip3 install -r /tmp/requirements.txt 20 | 21 | EXPOSE 80 22 | 23 | COPY app /app 24 | 25 | WORKDIR /app 26 | 27 | ENTRYPOINT ["/usr/bin/python3", "/app/app.py"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Tomer Klein 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ADB-API 2 | 3 | ADB-API is a python-based application to help us control android-based streamers like Xiaomi and more. 4 | With ADB-API, you can easily do the following things: 5 | 6 | * Create a virtual remote. 7 | * List all devices. 8 | * Get device properties (Version, OS, and more). 9 | * Get device memory usage. 10 | * Get device CPU information. 11 | * Get the list of installed appliances (system and 3rd party). 12 | * Open or Close any installed app. 13 | * Take a screenshot from the device. 14 | * Execute commands. 15 | * Send key events (Single key or key chain). 16 | 17 | ## Components and Libraries used in ADB-API 18 | 19 | * [FastAPI](https://fastapi.tiangolo.com/) - FastAPI framework, high performance, easy to learn, fast to code, ready for production. 20 | * [Uvicorn](https://www.uvicorn.org/) - lightning-fast ASGI server implementation, using uvloop and httptools. 21 | * [jinja](https://jinja.palletsprojects.com/en/3.0.x/) - fast, expressive, extensible templating engine. 22 | * [aiofile](https://pypi.org/project/aiofile/) - Real asynchronous file operations with asyncio support. 23 | * [loguru](https://loguru.readthedocs.io/en/stable/api/logger.html) - An object to dispatch logging messages to configured handlers. 24 | * [python-multipart](https://pypi.org/project/python-multipart/) - A streaming multipart parser for Python. 25 | * [requests](https://docs.python-requests.org/en/latest/) - elegant and simple HTTP library for Python, built for human beings. 26 | * [google-play-scraper](https://pypi.org/project/google-play-scraper/) - Google-Play-Scraper provides APIs to easily crawl the Google Play Store for Python without any external dependencies! 27 | * [dataclasses-json](https://pypi.org/project/dataclasses-json/) - This library provides a simple API for encoding and decoding dataclasses to and from JSON. 28 | * [adb-shell](https://pypi.org/project/adb-shell/) - A Python implementation of ADB with shell and FileSync functionality. 29 | * [pyyaml](https://pypi.org/project/PyYAML/) - YAML parser and emitter for Python 30 | 31 | 32 | ## Installation and Configuration 33 | In order to use the ADB-API, you need to enable "Develpoer mode" on the Android tv based streamer. to do so, follow the next steps: 34 | 1. On your TV device, navigate to Settings. 35 | 2. In the Device row, select About. 36 | 3. Scroll down to Build and select Build several times until you get the message "You are now a developer!" 37 | 4. Return to Settings. In the Preferences row, select Developer options. 38 | 5. Select Debugging > USB debugging and select On. 39 | 6. Navigate back to the TV home screen. 40 | 41 | Now, after you enabled the Developer mode it's time to install the ADB-API docker. to do so, create a file named "docker-compose.yaml" and add the following code: 42 | 43 | ```yaml 44 | version: "3.7" 45 | 46 | services: 47 | 48 | adb_api: 49 | image: techblog/adb-api 50 | container_name: adb-api 51 | privileged: true 52 | restart: always 53 | ports: 54 | - "80:80" 55 | volumes: 56 | - ./adb-api/config:/app/config 57 | ``` 58 | create a folder named "adb-api/config" on the same level with the yaml file and create a new file named "devices.yaml" with the following content: 59 | 60 | ```yaml 61 | devices: 62 | 63 | - id: 1 64 | name: 65 | ip: 66 | port: 67 | 68 | - id: 2 69 | name: 70 | ip: 71 | port: 72 | ``` 73 | And update the devices list according to your devices details. 74 | 75 | ***You can set the path for the config directory. The one in the code sample is just an example.*** 76 | 77 | Now, run the following command to install and start the container: 78 | ```bash 79 | docker-compose up -d 80 | ``` 81 | 82 | After the container starts, more file will be added to the config directory: 83 | 1. 2 key files for connecting the devices (Certificates). 84 | 2. sqlite db to speed up the performence of part of the api calls. 85 | 86 | You will also see the following message pops up on each of your streamers: 87 | 88 | [![Allow adb usb debugging](https://techblog.co.il/wp-content/uploads/2023/04/usb-debug.png)](https://techblog.co.il/wp-content/uploads/2023/04/usb-debug.png) 89 | 90 | Make sure to check the "Always allow from this computer" checkbox and click on the OK button. you will also need to restart the container. 91 | 92 | ## Working with ADB-API 93 | ADB-API also includes OpenAPI swagger documentation to help you working with the system. with the swagger you will be able to test the api calls very easely. 94 | 95 | To access the swagger documentation, add "docs" at the end of the server url. for example: http://[]server-address]:[port]/docs 96 | 97 | [![adb-api swagger](https://techblog.co.il/wp-content/uploads/2023/04/adb-api-swagger.png)](https://techblog.co.il/wp-content/uploads/2023/04/adb-api-swagger.png) 98 | 99 | 100 | ## API Endpoints 101 | 102 | * Get devices list: "/api/devices". returns json with registered devices. 103 | 104 | ```json 105 | 106 | "devices": [ 107 | { 108 | "id": 1, 109 | "name": "Mibox Work-Room", 110 | "ip": "192.168.0.12", 111 | "port": 5555 112 | }, 113 | { 114 | "id": 2, 115 | "name": "Mibox Parents", 116 | "ip": "192.168.0.235", 117 | "port": 5555 118 | } 119 | ] 120 | } 121 | ``` 122 | 123 | * Get device properties: "/api/{device}/properties. returns json with device properties. the device parameter is the ip address of the streamer. 124 | 125 | ```json 126 | { 127 | "net_bt_name": " Android", 128 | "persist_sys_locale": " he-IL", 129 | "persist_sys_media_avsync": " true", 130 | "persist_sys_timezone": " Asia/Jerusalem", 131 | "persist_sys_usb_config": " adb", 132 | "persist_sys_webview_vmsize": " 139176216", 133 | "pm_dexopt_ab-ota": " speed-profile", 134 | "pm_dexopt_bg-dexopt": " speed-profile", 135 | "pm_dexopt_boot": " verify", 136 | "pm_dexopt_first-boot": " quicken", 137 | "pm_dexopt_inactive": " verify", 138 | "pm_dexopt_install": " speed-profile", 139 | "pm_dexopt_priv-apps-oob": " false", 140 | "pm_dexopt_priv-apps-oob-list": " ALL", 141 | "pm_dexopt_shared": " speed", 142 | "ro_actionable_compatible_property_enabled": " true", 143 | "ro_adb_secure": " 1", 144 | "ro_allow_mock_location": " 0", 145 | "ro_boot_hardware": " amlogic", 146 | "ro_boot_oemkey1": " ATV00100021M19", 147 | "ro_boot_reboot_mode": " cold_boot", 148 | "ro_boot_rpmb_state": " 0", 149 | "ro_boot_selinux": " enforcing", 150 | "ro_boot_serialno": " 18554284042375", 151 | "ro_boot_vbmeta_avb_version": " 1.1", 152 | "ro_boot_vbmeta_device": " /dev/block/vbmeta", 153 | "ro_boot_vbmeta_device_state": " locked", 154 | "ro_bootimage_build_date": " Tue Sep 28 18", 155 | "ro_bootimage_build_date_utc": " 1632823795", 156 | "ro_bootimage_build_fingerprint": " Xiaomi/oneday/oneday", 157 | "ro_bootloader": " unknown", 158 | "ro_bootmode": " unknown", 159 | "ro_build_characteristics": " default", 160 | "ro_build_date": " Tue Sep 28 18", 161 | "ro_build_date_utc": " 1632823795", 162 | "ro_build_description": " oneday-user 9 PI 3933 release-keys", 163 | "ro_build_display_id": " PI.3933 release-keys", 164 | "ro_build_expect_bootloader": " 01.01.180822.145544", 165 | "ro_build_fingerprint": " Xiaomi/oneday/oneday", 166 | "ro_build_flavor": " oneday-user", 167 | "ro_build_host": " c5-mitv-bsp-build04.bj", 168 | "ro_build_id": " PI", 169 | "ro_build_software_version": " 21.9.28.3933", 170 | "ro_build_system_root_image": " true", 171 | "ro_build_user": " jenkins", 172 | "ro_build_version_preview_sdk": " 0", 173 | "ro_build_version_release": " 9", 174 | "ro_build_version_sdk": " 28", 175 | "ro_com_google_clientidbase": " android-xiaomi-tv", 176 | "ro_com_google_gmsversion": " Android_9_Pie", 177 | "ro_config_notification_sound": " pixiedust.ogg", 178 | "ro_product_brand": " Xiaomi", 179 | "ro_product_build_date": " Tue Sep 28 18", 180 | "ro_product_build_date_utc": " 1632823795", 181 | "ro_product_build_fingerprint": " Xiaomi/oneday/oneday", 182 | "ro_product_cpu_abi": " armeabi-v7a", 183 | "ro_product_cpu_abi2": " armeabi", 184 | "ro_product_cpu_abilist": " armeabi-v7a,armeabi", 185 | "ro_product_cpu_abilist32": " armeabi-v7a,armeabi", 186 | "ro_product_cpu_abilist64": " ", 187 | "ro_product_device": " oneday", 188 | "ro_product_first_api_level": " 28", 189 | "ro_product_locale": " en-US", 190 | "ro_product_manufacturer": " Xiaomi", 191 | "ro_product_model": " MIBOX4", 192 | "ro_product_name": " oneday", 193 | "ro_product_vendor_brand": " Xiaomi", 194 | "ro_product_vendor_device": " oneday", 195 | "ro_product_vendor_manufacturer": " Xiaomi", 196 | "ro_product_vendor_model": " MIBOX4", 197 | "ro_product_vendor_name": " oneday", 198 | 199 | } 200 | ``` 201 | 202 | * Get device memory info: "/api/device/memory". returns the device memory info. 203 | 204 | ```json 205 | { 206 | "MemTotal": "2034840 kB", 207 | "MemFree": "169388 kB", 208 | "MemAvailable": "1076044 kB", 209 | "Buffers": "30816 kB", 210 | "Cached": "996968 kB", 211 | "SwapCached": "0 kB", 212 | "Active": "911756 kB", 213 | "Inactive": "622644 kB", 214 | "Active(anon)": "508824 kB", 215 | "Inactive(anon)": "2156 kB", 216 | "Active(file)": "402932 kB", 217 | "Inactive(file)": "620488 kB", 218 | "Unevictable": "2340 kB", 219 | "Mlocked": "2340 kB", 220 | "SwapTotal": "262140 kB", 221 | "SwapFree": "262140 kB", 222 | "Dirty": "0 kB", 223 | "Writeback": "0 kB", 224 | "AnonPages": "508964 kB", 225 | "Mapped": "532488 kB", 226 | "Shmem": "2508 kB", 227 | "Slab": "106952 kB", 228 | "SReclaimable": "49936 kB", 229 | "SUnreclaim": "57016 kB", 230 | "KernelStack": "21504 kB", 231 | "PageTables": "27004 kB", 232 | "NFS_Unstable": "0 kB", 233 | "Bounce": "0 kB", 234 | "WritebackTmp": "0 kB", 235 | "CommitLimit": "1279560 kB", 236 | "Committed_AS": "23159644 kB", 237 | "VmallocTotal": "263061440 kB", 238 | "VmallocUsed": "0 kB", 239 | "VmallocChunk": "0 kB", 240 | "CmaTotal": "544768 kB", 241 | "CmaFree": "0 kB", 242 | "VmapStack": "5496 kB" 243 | } 244 | ``` 245 | 246 | * Get the list of installed applications: "/api/{device}/app/3rd". returns list of installd application qith basic info. 247 | 248 | ```json 249 | { 250 | "com.plexapp.android": { 251 | "appname": "Plex: Stream Movies & TV", 252 | "appurl": "https://play.google.com/store/apps/details?id=com.plexapp.android&hl=en&gl=il", 253 | "appimage": "https://play-lh.googleusercontent.com/slZYN_wnlAZ4BmyTZZakwfwAGm8JE5btL7u7AifhqCtUuxhtVVxQ1mcgpGOYC7MsAaU" 254 | }, 255 | "il.co.yes.yesgo": { 256 | "appname": "yes+", 257 | "appurl": "https://play.google.com/store/apps/details?id=il.co.yes.yesgo&hl=en&gl=il", 258 | "appimage": "https://play-lh.googleusercontent.com/8AgNls4adb1Wsp4ZxGGoaSecwbiBT1wmY1cgRLEwjhltrlS2lNcanpXLT_5IidJpbA" 259 | }, 260 | "il.co.stingtv.atv": { 261 | "appname": "STINGTV", 262 | "appurl": "https://play.google.com/store/apps/details?id=il.co.stingtv.atv&hl=en&gl=il", 263 | "appimage": "https://play-lh.googleusercontent.com/NrUvKI1NcsLk6_hNxZtxWENvDyuQNvTDvoJqZFmuuFrKcml-5bygxM_oJNyYyTFXBpo" 264 | }, 265 | "miada.tv.webbrowser": { 266 | "appname": "Internet Web Browser", 267 | "appurl": "https://play.google.com/store/apps/details?id=miada.tv.webbrowser&hl=en&gl=il", 268 | "appimage": "https://play-lh.googleusercontent.com/wui_0K9RipIlFKLsSbAPFaI9-f6PA4INZ0GKZDThsi57Jm-Olw04T_pqtufhNaTKLw" 269 | }, 270 | "com.spotify.tv.android": { 271 | "appname": "Spotify - Music and Podcasts", 272 | "appurl": "https://play.google.com/store/apps/details?id=com.spotify.tv.android&hl=en&gl=il", 273 | "appimage": "https://play-lh.googleusercontent.com/eN0IexSzxpUDMfFtm-OyM-nNs44Y74Q3k51bxAMhTvrTnuA4OGnTi_fodN4cl-XxDQc" 274 | }, 275 | "com.greenshpits.RLive": { 276 | "appname": "Radio Live Israel radio online", 277 | "appurl": "https://play.google.com/store/apps/details?id=com.greenshpits.RLive&hl=en&gl=il", 278 | "appimage": "https://play-lh.googleusercontent.com/c5hWYKQ0BJioIyoPegJiibjz93PBYVGT0BUrRCoHvkx_bnqkBCQf91752R7BTKEzIro" 279 | }, 280 | "com.android.chrome": { 281 | "appname": "Google Chrome: Fast & Secure", 282 | "appurl": "https://play.google.com/store/apps/details?id=com.android.chrome&hl=en&gl=il", 283 | "appimage": "https://play-lh.googleusercontent.com/KwUBNPbMTk9jDXYS2AeX3illtVRTkrKVh5xR1Mg4WHd0CG2tV4mrh1z3kXi5z_warlk" 284 | } 285 | } 286 | ``` 287 | 288 | * Execute command: "/api/{device}/execute/{command}. returns the command output. for example, getting the current system volume. 289 | The following command "settings get system volume_system" will result the following output: 290 | 291 | ```json 292 | [ 293 | "7" 294 | ] 295 | ``` 296 | 297 | * Send key events: "/api/{device}/{keyevent}". this command simulates one or more key press. 298 | for example, running the sollowing command **"input keyevent 3"** will simulate clicking the **"Home"** button and **"input keyevent 25 25"** will simulate two clicks on the **"Volume Down"** button. 299 | 300 | * Open Application: "/api/{device}/{app}/open". this command will open the requested application. for example, the following command **"/api/192.168.0.12/com.plexapp.android/open"** will open the "Plex" application. 301 | 302 | 303 | ## Usefull list of ADB Commands 304 | I have published a list of usefull adb command [Here](https://gist.github.com/t0mer/37b384a37941c25d1e7206849b10967f). -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 2.2.0 2 | -------------------------------------------------------------------------------- /app/androiddevice.py: -------------------------------------------------------------------------------- 1 | from adb_shell.adb_device import AdbDeviceTcp 2 | 3 | 4 | class AndriodDevice(object): 5 | id: int 6 | port: int 7 | name: str 8 | ip: str 9 | device: AdbDeviceTcp 10 | 11 | def __init__(self, id: int, port: int, name: str, ip: str, device: AdbDeviceTcp): 12 | self.id = id 13 | self.port = port 14 | self.name = name 15 | self.ip = ip 16 | self.device = device 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -* 2 | 3 | import os 4 | import yaml 5 | import json 6 | import uuid 7 | import shutil 8 | import uvicorn 9 | import requests 10 | from os import path 11 | from loguru import logger 12 | # from device import Device 13 | from datetime import datetime 14 | from google_play_scraper import app as app_scrap 15 | from fastapi import FastAPI, Request 16 | from androiddevice import AndriodDevice 17 | from adb_shell.auth.keygen import keygen 18 | from sqliteconnector import SqliteConnector 19 | from fastapi.staticfiles import StaticFiles 20 | from fastapi.encoders import jsonable_encoder 21 | from fastapi.templating import Jinja2Templates 22 | from adb_shell.auth.sign_pythonrsa import PythonRSASigner 23 | from adb_shell.adb_device import AdbDeviceTcp, AdbDeviceUsb 24 | from fastapi.responses import HTMLResponse, JSONResponse, FileResponse 25 | 26 | 27 | KEYS_PATH = './config/adb' 28 | 29 | def generate_keys(): 30 | """ 31 | Generates keys if not exists 32 | """ 33 | if not path.exists(KEYS_PATH) or not path.exists((KEYS_PATH + '.pub')): 34 | keygen(KEYS_PATH) 35 | 36 | def load_keys(): 37 | """ 38 | Load keys from files 39 | """ 40 | generate_keys() 41 | with open(KEYS_PATH) as f: 42 | priv = f.read() 43 | with open(KEYS_PATH + '.pub') as f: 44 | pub = f.read() 45 | return PythonRSASigner(pub, priv) 46 | 47 | def read_devices_list(): 48 | if not path.exists("config/devices.yaml"): 49 | shutil.copy("devices.yaml","config/devices.yaml") 50 | 51 | with open("config/devices.yaml",'r',encoding='utf-8') as stream: 52 | try: 53 | return yaml.safe_load(stream) 54 | except yaml.YAMLError as exc: 55 | logger.error(exc) 56 | return [] 57 | 58 | 59 | signer = load_keys() 60 | adb_devices=[] 61 | devices = read_devices_list() 62 | app = FastAPI(title="Virtual Remote for android tv", description="Virtualy control you android tv devices", version="1.0.0", contact={"name": "Tomer Klein", "email": "tomer.klein@gmail.com", "url": "https://github.com/t0mer/virtual-remote"}) 63 | app.mount("/dist", StaticFiles(directory="dist"), name="dist") 64 | templates = Jinja2Templates(directory="templates/") 65 | connector = SqliteConnector() 66 | 67 | 68 | if not os.path.exists('dist/screenshots'): 69 | os.makedirs('dist/screenshots') 70 | 71 | 72 | def connect_to_device(adb_device): 73 | try: 74 | logger.info("Reconnecting to device") 75 | adb_device.device.connect(rsa_keys=[signer], auth_timeout_s=0.1) 76 | return adb_device 77 | except Exception as e: 78 | logger.error("Unable to connect: " + str(e)) 79 | return adb_device 80 | 81 | for device in devices["devices"]: 82 | try: 83 | 84 | logger.info("Adding device " + device["name"] + " With IP: " + device["ip"]) 85 | adb_device = AdbDeviceTcp(device["ip"], device["port"], default_transport_timeout_s=9.) 86 | adb_device.connect(rsa_keys=[signer], auth_timeout_s=0.1) 87 | adb_devices.append(AndriodDevice(id=device["id"],port=device["port"],name=device["name"],ip=device["ip"],device=adb_device)) 88 | except Exception as e: 89 | logger.error("Error adding ADB Device with IP: " + device["ip"]) 90 | logger.error(str(e)) 91 | 92 | 93 | 94 | @app.get('/remotes/{remote}') 95 | def index(remote: str, request: Request): 96 | return templates.TemplateResponse(remote + '.html', context={'request': request}) 97 | 98 | @app.get('/api/devices') 99 | def devices_lis(request: Request): 100 | json_devices = jsonable_encoder(devices) 101 | return JSONResponse(content=json_devices) 102 | 103 | @app.get('/api/{device}/properties') 104 | def properties(device:str, request: Request): 105 | properties = {} 106 | adb_device = next(d for d in adb_devices if d.ip == device) 107 | adb_device = connect_to_device(adb_device) 108 | device_props = adb_device.device.shell("getprop") 109 | for prop in device_props.splitlines(): 110 | k= prop.split(':')[0].replace('[','').replace(']','') 111 | v= prop.split(':')[1].replace('[','').replace(']','') 112 | properties[k.replace(".","_")] = v 113 | json_devices = jsonable_encoder(properties) 114 | return JSONResponse(content=json_devices) 115 | 116 | @app.get('/api/{device}/memory') 117 | def memory(device:str, request: Request): 118 | device_memory = {} 119 | adb_device = next(d for d in adb_devices if d.ip == device) 120 | adb_device = connect_to_device(adb_device) 121 | meminfo = adb_device.device.shell("cat /proc/meminfo") 122 | for prop in meminfo.splitlines(): 123 | k= prop.split(':')[0].strip() 124 | v= prop.split(':')[1].strip() 125 | device_memory[k] = v 126 | memory_json = jsonable_encoder(device_memory) 127 | return JSONResponse(content=memory_json) 128 | 129 | @app.get('/api/{device}/apps/system') 130 | def sysapps(device:str, request: Request): 131 | sysapps = {} 132 | adb_device = next(d for d in adb_devices if d.ip == device) 133 | adb_device = connect_to_device(adb_device) 134 | systemapps = adb_device.device.shell("pm list packages -s").splitlines() 135 | for sysap in systemapps: 136 | sysapp_id = sysap.split(':')[1] 137 | sysapps[sysapp_id]=get_app_details(sysapp_id) 138 | sysapps_json = jsonable_encoder(sysapps) 139 | return JSONResponse(content=sysapps_json) 140 | 141 | @app.get('/api/{device}/apps/3rd') 142 | def sysapps(device:str, request: Request): 143 | apps = {} 144 | adb_device = next(d for d in adb_devices if d.ip == device) 145 | adb_device = connect_to_device(adb_device) 146 | apps3rd = adb_device.device.shell("pm list packages -3").splitlines() 147 | for app3rd in apps3rd: 148 | app_id = app3rd.split(':')[1] 149 | apps[app_id] = get_app_details(app_id) 150 | apps3rd_json = jsonable_encoder(apps) 151 | return JSONResponse(content=apps3rd_json) 152 | 153 | 154 | @app.get('/api/{device}/cpu') 155 | def cpu(device:str, request: Request): 156 | device_cpu = {} 157 | adb_device = next(d for d in adb_devices if d.ip == device) 158 | adb_device = connect_to_device(adb_device) 159 | cores =adb_devices[0].device.shell("grep -c processor /proc/cpuinfo") 160 | cpuinfo = adb_device.device.shell("cat /proc/cpuinfo") 161 | cpu_num = 0 162 | device_cpu["cores"] = cores.replace("\n","") 163 | for prop in cpuinfo.splitlines(): 164 | if ":" in prop and not "processor" in prop: 165 | k= prop.split(':')[0].strip() 166 | v= prop.split(':')[1].strip() 167 | device_cpu[k] = v 168 | 169 | cpu_json = jsonable_encoder(device_cpu) 170 | return JSONResponse(content=cpu_json) 171 | 172 | @app.get("/api/{device}/{keyevent}") 173 | def command(device:str,keyevent: str,request: Request): 174 | response = {} 175 | try: 176 | adb_device = next(d for d in adb_devices if d.ip == device) 177 | adb_device = connect_to_device(adb_device) 178 | logger.info(adb_device.device.shell("input keyevent " + keyevent)) 179 | response["success"] = True 180 | response["message"] = "keyevent command executed successfuly" 181 | return JSONResponse(content=jsonable_encoder(response)) 182 | except Exception as e: 183 | response["success"] = False 184 | response["message"] = str(e) 185 | return JSONResponse(content=jsonable_encoder(response)) 186 | 187 | @app.get('/api/{device}/{app}/open') 188 | def open_app(device: str, app: str, request: Request): 189 | adb_device = next(d for d in adb_devices if d.ip == device) 190 | adb_device = connect_to_device(adb_device) 191 | adb_device.device.shell("monkey -p "+ app +" -c android.intent.category.LAUNCHER 1") 192 | 193 | 194 | @app.get('/api/{device}/start') 195 | def open_app(device: str, activity: str, request: Request): 196 | adb_device = next(d for d in adb_devices if d.ip == device) 197 | adb_device = connect_to_device(adb_device) 198 | adb_device.device.shell("am start -n " + activity) 199 | 200 | 201 | 202 | 203 | @app.get('/api/{device}/{app}/close') 204 | def close_app(device: str, app: str, request: Request): 205 | adb_device = next(d for d in adb_devices if d.ip == device) 206 | adb_device = connect_to_device(adb_device) 207 | adb_device.device.shell("am force-stop "+ app ) 208 | 209 | 210 | @app.get('/api/{device}/execute/{command}') 211 | def execute_command(device: str, command: str, request: Request): 212 | adb_device = next(d for d in adb_devices if d.ip == device) 213 | adb_device = connect_to_device(adb_device) 214 | return(adb_device.device.shell(command.replace('/',"/")).splitlines()) 215 | 216 | @app.get('/api/screenshot/get/{device}/{image}', response_class=FileResponse) 217 | def screenshot(device: str, request: Request, image: str): 218 | try: 219 | adb_device = next(d for d in adb_devices if d.ip == device) 220 | adb_device = connect_to_device(adb_device) 221 | img_name = str(uuid.uuid4()) 222 | adb_device.device.shell('screencap -p "/sdcard/' + image + '.png"') 223 | adb_device.device.pull("/sdcard/" + image + ".png", "dist/screenshots/" + image + ".png") 224 | try: 225 | adb_device.device.shell('rm -f "/sdcard/' + image + '.png"') 226 | except Exception as e: 227 | logger.error("Error delete image from the device. " + str(e)) 228 | return "dist/screenshots/" + image + ".png" 229 | except Exception as e: 230 | logger.error("Error taking screenshot. " + str(e)) 231 | 232 | 233 | 234 | def get_app_details(app:str): 235 | try: 236 | app_details={} 237 | if connector.is_app_info_exists(app): 238 | data = connector.get_app_info_by_id(app)[0] 239 | app_details["appname"] = data[1] 240 | app_details["appurl"] = data[2] 241 | app_details["appimage"] = data[3] 242 | 243 | else: 244 | appinfo = app_scrap( 245 | app, 246 | lang='en', # defaults to 'en' 247 | country='il', # defaults to 'us' 248 | ) 249 | app_details["appname"] = appinfo["title"] 250 | app_details["appurl"] = appinfo["url"] 251 | app_details["appimage"] = appinfo["icon"] 252 | connector.add_app_info(app, app_details["appname"],app_details["appurl"],app_details["appimage"],datetime.now()) 253 | return app_details 254 | except Exception as e: 255 | logger.error("Error getting app info. " + str(e)) 256 | app_details["appname"] = "" 257 | app_details["appurl"] = "" 258 | app_details["appimage"] = "" 259 | connector.add_app_info(app, app_details["appname"],app_details["appurl"],app_details["appimage"],datetime.now()) 260 | return app_details 261 | 262 | 263 | 264 | if __name__ == "__main__": 265 | load_keys() 266 | logger.info("Virtual remote is up and running") 267 | adb_device = adb_devices[0] 268 | uvicorn.run(app, host="0.0.0.0", port=80) 269 | 270 | 271 | 272 | 273 | 274 | -------------------------------------------------------------------------------- /app/devices.yaml: -------------------------------------------------------------------------------- 1 | devices: 2 | 3 | - id: 1 4 | name: 5 | ip: 6 | port: 7 | 8 | - id: 2 9 | name: 10 | ip: 11 | port: 12 | -------------------------------------------------------------------------------- /app/dist/mibox/bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/bottom.png -------------------------------------------------------------------------------- /app/dist/mibox/bottom_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/bottom_over.png -------------------------------------------------------------------------------- /app/dist/mibox/clickleft.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/clickleft.png -------------------------------------------------------------------------------- /app/dist/mibox/clickleft_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/clickleft_over.png -------------------------------------------------------------------------------- /app/dist/mibox/clickright.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/clickright.png -------------------------------------------------------------------------------- /app/dist/mibox/clickright_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/clickright_over.png -------------------------------------------------------------------------------- /app/dist/mibox/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/home.png -------------------------------------------------------------------------------- /app/dist/mibox/home_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/home_over.png -------------------------------------------------------------------------------- /app/dist/mibox/left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/left.png -------------------------------------------------------------------------------- /app/dist/mibox/left_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/left_over.png -------------------------------------------------------------------------------- /app/dist/mibox/microphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/microphone.png -------------------------------------------------------------------------------- /app/dist/mibox/microphone_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/microphone_over.png -------------------------------------------------------------------------------- /app/dist/mibox/ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/ok.png -------------------------------------------------------------------------------- /app/dist/mibox/ok_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/ok_over.png -------------------------------------------------------------------------------- /app/dist/mibox/power.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/power.png -------------------------------------------------------------------------------- /app/dist/mibox/power_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/power_over.png -------------------------------------------------------------------------------- /app/dist/mibox/remote-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/remote-back.png -------------------------------------------------------------------------------- /app/dist/mibox/top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/top.png -------------------------------------------------------------------------------- /app/dist/mibox/top_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/top_over.png -------------------------------------------------------------------------------- /app/dist/mibox/volume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/volume.png -------------------------------------------------------------------------------- /app/dist/mibox/volume_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/volume_over.png -------------------------------------------------------------------------------- /app/dist/mibox/volumedown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/volumedown.png -------------------------------------------------------------------------------- /app/dist/mibox/volumedown_over.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/t0mer/adb-api/1d0d40a12d898db643fe3e577f2ee37f2cd5e5cf/app/dist/mibox/volumedown_over.png -------------------------------------------------------------------------------- /app/keys/adb: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDMTjkfvQXIY6i8 3 | HrxbTOG2GwQSETVRRW5EO3qAiIbpi6UQPEQHbBmrSBHH1qycNMs96sKaTq2AFAQs 4 | ZxmnmaEVl3YxpNA1YRppmpwWJp/F+tKdRPC06iw/TqCrxsIWG4JR6NXn84gh8F/0 5 | qbRQ7dA9RKO4/nGCT7ik5mONo00zpizoka9anGbFI1KzvZYUjM+35w4QyId4Qqwm 6 | 1UG6eEa6UUI3V6UjXboWe60Q1FkZ7+jvQtAjXWEGA5idQ/dWRB0Fl4A1WNEVyPTf 7 | IAhH0/C/qfc+PWY37Bqew1mm1kjDGANTMk9ynI6nUuv3GIikX0jevKVx8QICTZKQ 8 | fAXUj+hPAgMBAAECggEAGNlOw1h3MpdEd5T5bdVdAowoo5XqOTdLoQrm7Gkul4s/ 9 | iUj7MTfpq1Z5qyemGN2m9qHuB1yYZ6KHm2ryyNq7p+ejHYihNkzYLI38fAk4l5jZ 10 | Mv2mlSqCNTT3/8yN4Kh54kKCedK59P7KfPSDFPDHyhvmGeMApUvS9mN7Kkf5ZNWS 11 | dNoqMxVPxMHJTmRLkyN0j+iBTc3QR0s+rFFg46LVinKrX/PByy295F25OVsUoVWZ 12 | p1qWsEXU3sFRJXuS7GU9PP0TGRi/fxp5dluvVMsx+lV2rlHhoYyga0BGc76VQB7Z 13 | 5l73Akzj3vEXAAJzf8xF9RExFIemJdPkBFMPs18oWQKBgQD6s9RFkL4+eykZT/iD 14 | p7mxysWK6BemUiruVHfy1r0I3v54u2eaBHO5bybxI1lwV7Bh4rHnvA631ThIIm92 15 | Znj6wKP0xq+ZnV3+Ty4JXWR33JMCq4UtzERDkY2D8yD2rad1ujbZnoWDPRYKGJff 16 | 9NXnsiGwTiQhoutjxY2SMygTJQKBgQDQn2kjWWlrP+AD4MecOQUZg8E4cjvYN4wZ 17 | sFHFjy2BlMn6DmqbnVbXxBZoxpPMxaUk71US1XMsNO5jf8j1+EyTJrhBRRt3xbDc 18 | xzaCk+Waj2mGtbVdFWotct5b8q/9TlCISo7tOpOmm2CQxaFF2v9Z33T1iMA7odTH 19 | d55y6PctYwKBgEEeetHDAhd6qDdB9XxFEc/jqqIlHRd/mlUjrbBvJNTsD09O5l80 20 | LtcIzyWtl+q9bDxQxQM7bttDUAY0o50uitgfN7c58JzHKoYHDR8zmggkTC9SXBqE 21 | R2sBoK+YdIR+oNPMlbi7G1T2hgYDU1fB4dn4BBYmhe4Swm+8/cPu88bdAoGAR5HY 22 | jNL4sl1oaYwsjnCVSkqD9+xgkW2E968gbmkPEHxNyiY19orEpyLTGUC+papMXF5q 23 | zhpB97F08td8xr1W78iBv4qIwM8mPKdvEN7SDvMSHa+qt3NISK02kU6xh3HdeNQz 24 | Mjk9PBPjzeauy/s0TM23HyC56fs7tu5Xgk5ftccCgYEA7BMjzpyyjzMSLhh5xYVP 25 | Z/FEO7S5MWU7JK8lSMQAnkNdt3cnIwH3jnrXCpVBYn6O/mAMPpjZ5mzsghs61iVI 26 | AEbn7dHj0NjTiKPL70A/Gc82bBsnzliRHFhutR8UjOI1/s78ZaivravkEQTHnFwB 27 | BKikRmfbi+UhT9XGDdtvE7E= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /app/keys/adb.pub: -------------------------------------------------------------------------------- 1 | QAAAAFHRgS5P6I/UBXyQkk0CAvFxpbzeSF+kiBj361KnjpxyTzJTAxjDSNamWcOeGuw3Zj0+96m/8NNHCCDf9MgV0Vg1gJcFHURW90OdmAMGYV0j0ELv6O8ZWdQQrXsWul0jpVc3QlG6Rni6QdUmrEJ4h8gQDue3z4wUlr2zUiPFZpxar5HoLKYzTaONY+akuE+Ccf64o0Q90O1QtKn0X/AhiPPn1ehRghsWwsaroE4/LOq08ESd0vrFnyYWnJppGmE10KQxdpcVoZmnGWcsBBSArU6awuo9yzScrNbHEUirGWwHRDwQpYvphoiAejtEbkVRNRESBBu24UxbvB68qGPIBb0fOU7Mk4vtG1PU3hEVpLoIYDxfYe0vc4b0yYN0/X9CPjEMCsgIU9X/J4vUS0if5uiu4SMOI47w2m+FrSTZu/rKvrgAh84Umy5ejQnLoiPCOx3rB/SGJJjj8iol+ffCaSKnXYk3GTChLtqQ9vYSRNzs3c5TLph3//aStE87Qkke96kcLqFQ5QqwNwuG+aQch3aDqeqnUrrcXb/xBdmDZAt6sjowwvZdv4cmR2jYfrB5xGnjX0/6OtwShMprK2PhQFOYQZIDAz9nEogOdfhra2JRcL3sqPjcjCx3oNHVki4NW6OJrcKnm3pgpPLMG9FkOCstYGDM0AMtmfV+9G2AG7d4hPdvOwEAAQA= root@dev-vm -------------------------------------------------------------------------------- /app/sqliteconnector.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | from datetime import datetime 3 | from sqlite3 import Error 4 | from loguru import logger 5 | class SqliteConnector: 6 | def __init__(self): 7 | self.db_file = "config/appinfo.db" 8 | self.conn = None 9 | self.create_tables() 10 | 11 | def open_connection(self): 12 | try: 13 | self.conn = sqlite3.connect(self.db_file) 14 | except Error as e: 15 | logger.error(str(e)) 16 | 17 | def close_connection(self): 18 | try: 19 | self.conn.close() 20 | except Error as e: 21 | logger.error(str(e)) 22 | 23 | def create_tables(self): 24 | self.open_connection() 25 | create_apps_table = """ CREATE TABLE IF NOT EXISTS apps ( 26 | AppId text PRIMARY KEY, 27 | AppName text NOT NULL, 28 | AppURL text NOT NULL, 29 | AppImage text NOT NULL, 30 | LastChanged text NOT NULL 31 | ); """ 32 | 33 | 34 | try: 35 | c = self.conn.cursor() 36 | c.execute(create_apps_table) 37 | c.close() 38 | self.conn.close() 39 | except Error as e: 40 | logger.error(str(e)) 41 | 42 | def add_app_info(self,AppId,AppName,AppURL,AppImage,LastChanged): 43 | try: 44 | Tunnel = (AppId,AppName,AppURL,AppImage,LastChanged,) 45 | self.open_connection() 46 | sql = """ INSERT INTO apps(AppId,AppName,AppURL,AppImage,LastChanged) VALUES (?,?,?,?,?)""" 47 | cur = self.conn.cursor() 48 | cur.execute(sql,Tunnel) 49 | self.conn.commit() 50 | self.conn.close() 51 | return str(cur.lastrowid>0), "App info addedd successfully" 52 | except Error as e: 53 | logger.error(str(e)) 54 | return False, str(e) 55 | 56 | def update_app_info(self,AppId,AppName,AppURL,AppImage,LastChanged): 57 | try: 58 | Tunnel = (AppName,AppURL,AppImage,LastChanged,AppId) 59 | self.open_connection() 60 | sql = ''' UPDATE apps 61 | SET AppName = ?, 62 | AppURL = ?, 63 | AppImage = ?, 64 | LastChanged = ? 65 | WHERE AppId = ?''' 66 | cur = self.conn.cursor() 67 | cur.execute(sql,Tunnel) 68 | self.conn.commit() 69 | self.conn.close() 70 | return str(cur.lastrowid>0), "App info updated successfully" 71 | except Error as e: 72 | logger.error(str(e)) 73 | return False, str(e) 74 | 75 | def get_app_info_by_id(self, AppId): 76 | """ 77 | Query tasks by priority 78 | :param conn: the Connection object 79 | :param priority: 80 | :return: 81 | """ 82 | try: 83 | self.open_connection() 84 | cur = self.conn.cursor() 85 | cur.execute("SELECT * FROM apps WHERE AppId=? Limit 1", (AppId,)) 86 | rows = cur.fetchall() 87 | self.conn.close() 88 | return rows 89 | except Error as e: 90 | logger.error(str(e)) 91 | return False, str(e) 92 | 93 | def is_app_info_exists(self,AppId): 94 | rows = self.get_app_info_by_id(AppId=AppId) 95 | return (True if rows else False) 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /app/templates/mibox.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 |
295 |
296 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 |
313 | 314 | 323 | 324 | 325 | 335 |
336 |
337 | 338 |
339 | 340 | 341 | 342 | 344 | 345 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pyyaml 2 | adb-shell 3 | adb-shell[usb] 4 | dataclasses-json 5 | google-play-scraper==1.2.2 --------------------------------------------------------------------------------