damon-devtools 0.1.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- damon_devtools-0.3.0/PKG-INFO +126 -0
- damon_devtools-0.3.0/README.md +115 -0
- damon_devtools-0.3.0/damon_devtools.egg-info/PKG-INFO +126 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/__main__.py +12 -2
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/launcher.py +2 -1
- damon_devtools-0.3.0/devtools/tools/apkpull/core.py +303 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/tools/apkpull/ui.py +27 -14
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/pyproject.toml +1 -1
- damon_devtools-0.1.0/PKG-INFO +0 -46
- damon_devtools-0.1.0/README.md +0 -35
- damon_devtools-0.1.0/damon_devtools.egg-info/PKG-INFO +0 -46
- damon_devtools-0.1.0/devtools/tools/apkpull/core.py +0 -123
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/damon_devtools.egg-info/SOURCES.txt +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/damon_devtools.egg-info/dependency_links.txt +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/damon_devtools.egg-info/entry_points.txt +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/damon_devtools.egg-info/requires.txt +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/damon_devtools.egg-info/top_level.txt +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/__init__.py +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/tools/__init__.py +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/tools/apkpull/__init__.py +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/tools/apkpull/__main__.py +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/tools/apkpull/models.py +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/tools/portkill/__init__.py +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/tools/portkill/__main__.py +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/tools/portkill/core.py +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/tools/portkill/models.py +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/tools/portkill/ui.py +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/tools/removebg/__init__.py +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/tools/removebg/__main__.py +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/tools/removebg/cli.py +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/tools/removebg/core.py +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/devtools/types.py +0 -0
- {damon_devtools-0.1.0 → damon_devtools-0.3.0}/setup.cfg +0 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: damon-devtools
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Developer CLI tools — portkill, removebg, apkpull
|
|
5
|
+
Author: damon
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: cli,devtools,android,apk,adb
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: psutil>=5.9
|
|
11
|
+
|
|
12
|
+
# devtools
|
|
13
|
+
|
|
14
|
+
Developer CLI tools: portkill, removebg, apkpull.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Tools
|
|
19
|
+
|
|
20
|
+
### portkill
|
|
21
|
+
|
|
22
|
+
Kill Node.js processes by port or scan all node processes.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
python -m devtools portkill
|
|
26
|
+
python -m devtools portkill --listen-only
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### apkpull
|
|
30
|
+
|
|
31
|
+
Pull APK files from Android device via ADB.
|
|
32
|
+
|
|
33
|
+
Features:
|
|
34
|
+
- List and search third-party apps
|
|
35
|
+
- Fast app name lookup via aapt2
|
|
36
|
+
- Auto-download aapt2 if not found locally
|
|
37
|
+
- Multi-device support
|
|
38
|
+
- Label caching for faster subsequent runs
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Interactive mode
|
|
42
|
+
python -m devtools apkpull
|
|
43
|
+
|
|
44
|
+
# Specify target directory
|
|
45
|
+
python -m devtools apkpull /path/to/output
|
|
46
|
+
|
|
47
|
+
# Search apps by keyword
|
|
48
|
+
python -m devtools apkpull -s wechat
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### removebg
|
|
52
|
+
|
|
53
|
+
Remove image backgrounds using the remove.bg API.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
python -m devtools removebg input.jpg
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install damon-devtools
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Requires: Python >= 3.10
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
# devtools
|
|
72
|
+
|
|
73
|
+
开发者命令行工具集:portkill、removebg、apkpull。
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 工具
|
|
78
|
+
|
|
79
|
+
### portkill
|
|
80
|
+
|
|
81
|
+
根据端口查找并终止 Node.js 进程,或扫描所有 node 进程。
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
python -m devtools portkill
|
|
85
|
+
python -m devtools portkill --listen-only
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### apkpull
|
|
89
|
+
|
|
90
|
+
通过 ADB 从 Android 设备拉取 APK 安装包。
|
|
91
|
+
|
|
92
|
+
功能特点:
|
|
93
|
+
- 列出和搜索第三方应用
|
|
94
|
+
- 通过 aapt2 快速获取应用名
|
|
95
|
+
- 本地未找到 aapt2 时自动下载
|
|
96
|
+
- 多设备支持
|
|
97
|
+
- 应用名缓存,加速后续运行
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# 交互模式
|
|
101
|
+
python -m devtools apkpull
|
|
102
|
+
|
|
103
|
+
# 指定目标目录
|
|
104
|
+
python -m devtools apkpull /path/to/output
|
|
105
|
+
|
|
106
|
+
# 搜索应用
|
|
107
|
+
python -m devtools apkpull -s wechat
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### removebg
|
|
111
|
+
|
|
112
|
+
使用 remove.bg API 去除图片背景。
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
python -m devtools removebg input.jpg
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 安装
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pip install damon-devtools
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
要求:Python >= 3.10
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# devtools
|
|
2
|
+
|
|
3
|
+
Developer CLI tools: portkill, removebg, apkpull.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
### portkill
|
|
10
|
+
|
|
11
|
+
Kill Node.js processes by port or scan all node processes.
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
python -m devtools portkill
|
|
15
|
+
python -m devtools portkill --listen-only
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### apkpull
|
|
19
|
+
|
|
20
|
+
Pull APK files from Android device via ADB.
|
|
21
|
+
|
|
22
|
+
Features:
|
|
23
|
+
- List and search third-party apps
|
|
24
|
+
- Fast app name lookup via aapt2
|
|
25
|
+
- Auto-download aapt2 if not found locally
|
|
26
|
+
- Multi-device support
|
|
27
|
+
- Label caching for faster subsequent runs
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
# Interactive mode
|
|
31
|
+
python -m devtools apkpull
|
|
32
|
+
|
|
33
|
+
# Specify target directory
|
|
34
|
+
python -m devtools apkpull /path/to/output
|
|
35
|
+
|
|
36
|
+
# Search apps by keyword
|
|
37
|
+
python -m devtools apkpull -s wechat
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### removebg
|
|
41
|
+
|
|
42
|
+
Remove image backgrounds using the remove.bg API.
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python -m devtools removebg input.jpg
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Install
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install damon-devtools
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Requires: Python >= 3.10
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
# devtools
|
|
61
|
+
|
|
62
|
+
开发者命令行工具集:portkill、removebg、apkpull。
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 工具
|
|
67
|
+
|
|
68
|
+
### portkill
|
|
69
|
+
|
|
70
|
+
根据端口查找并终止 Node.js 进程,或扫描所有 node 进程。
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
python -m devtools portkill
|
|
74
|
+
python -m devtools portkill --listen-only
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### apkpull
|
|
78
|
+
|
|
79
|
+
通过 ADB 从 Android 设备拉取 APK 安装包。
|
|
80
|
+
|
|
81
|
+
功能特点:
|
|
82
|
+
- 列出和搜索第三方应用
|
|
83
|
+
- 通过 aapt2 快速获取应用名
|
|
84
|
+
- 本地未找到 aapt2 时自动下载
|
|
85
|
+
- 多设备支持
|
|
86
|
+
- 应用名缓存,加速后续运行
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
# 交互模式
|
|
90
|
+
python -m devtools apkpull
|
|
91
|
+
|
|
92
|
+
# 指定目标目录
|
|
93
|
+
python -m devtools apkpull /path/to/output
|
|
94
|
+
|
|
95
|
+
# 搜索应用
|
|
96
|
+
python -m devtools apkpull -s wechat
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### removebg
|
|
100
|
+
|
|
101
|
+
使用 remove.bg API 去除图片背景。
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
python -m devtools removebg input.jpg
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## 安装
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
pip install damon-devtools
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
要求:Python >= 3.10
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: damon-devtools
|
|
3
|
+
Version: 0.3.0
|
|
4
|
+
Summary: Developer CLI tools — portkill, removebg, apkpull
|
|
5
|
+
Author: damon
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: cli,devtools,android,apk,adb
|
|
8
|
+
Requires-Python: >=3.10
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: psutil>=5.9
|
|
11
|
+
|
|
12
|
+
# devtools
|
|
13
|
+
|
|
14
|
+
Developer CLI tools: portkill, removebg, apkpull.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## Tools
|
|
19
|
+
|
|
20
|
+
### portkill
|
|
21
|
+
|
|
22
|
+
Kill Node.js processes by port or scan all node processes.
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
python -m devtools portkill
|
|
26
|
+
python -m devtools portkill --listen-only
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### apkpull
|
|
30
|
+
|
|
31
|
+
Pull APK files from Android device via ADB.
|
|
32
|
+
|
|
33
|
+
Features:
|
|
34
|
+
- List and search third-party apps
|
|
35
|
+
- Fast app name lookup via aapt2
|
|
36
|
+
- Auto-download aapt2 if not found locally
|
|
37
|
+
- Multi-device support
|
|
38
|
+
- Label caching for faster subsequent runs
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Interactive mode
|
|
42
|
+
python -m devtools apkpull
|
|
43
|
+
|
|
44
|
+
# Specify target directory
|
|
45
|
+
python -m devtools apkpull /path/to/output
|
|
46
|
+
|
|
47
|
+
# Search apps by keyword
|
|
48
|
+
python -m devtools apkpull -s wechat
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### removebg
|
|
52
|
+
|
|
53
|
+
Remove image backgrounds using the remove.bg API.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
python -m devtools removebg input.jpg
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Install
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install damon-devtools
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Requires: Python >= 3.10
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
# devtools
|
|
72
|
+
|
|
73
|
+
开发者命令行工具集:portkill、removebg、apkpull。
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 工具
|
|
78
|
+
|
|
79
|
+
### portkill
|
|
80
|
+
|
|
81
|
+
根据端口查找并终止 Node.js 进程,或扫描所有 node 进程。
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
python -m devtools portkill
|
|
85
|
+
python -m devtools portkill --listen-only
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### apkpull
|
|
89
|
+
|
|
90
|
+
通过 ADB 从 Android 设备拉取 APK 安装包。
|
|
91
|
+
|
|
92
|
+
功能特点:
|
|
93
|
+
- 列出和搜索第三方应用
|
|
94
|
+
- 通过 aapt2 快速获取应用名
|
|
95
|
+
- 本地未找到 aapt2 时自动下载
|
|
96
|
+
- 多设备支持
|
|
97
|
+
- 应用名缓存,加速后续运行
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# 交互模式
|
|
101
|
+
python -m devtools apkpull
|
|
102
|
+
|
|
103
|
+
# 指定目标目录
|
|
104
|
+
python -m devtools apkpull /path/to/output
|
|
105
|
+
|
|
106
|
+
# 搜索应用
|
|
107
|
+
python -m devtools apkpull -s wechat
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### removebg
|
|
111
|
+
|
|
112
|
+
使用 remove.bg API 去除图片背景。
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
python -m devtools removebg input.jpg
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## 安装
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pip install damon-devtools
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
要求:Python >= 3.10
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import sys
|
|
2
|
+
import argparse
|
|
2
3
|
from importlib import import_module
|
|
4
|
+
from importlib.metadata import version as get_version
|
|
3
5
|
from devtools.tools import get_tool
|
|
4
6
|
|
|
7
|
+
VERSION = get_version("damon-devtools")
|
|
8
|
+
|
|
5
9
|
|
|
6
10
|
def run_tool(tool_name: str, args: list[str]):
|
|
7
11
|
tool = get_tool(tool_name)
|
|
@@ -41,8 +45,14 @@ def run_tool(tool_name: str, args: list[str]):
|
|
|
41
45
|
|
|
42
46
|
|
|
43
47
|
def main():
|
|
44
|
-
|
|
45
|
-
|
|
48
|
+
parser = argparse.ArgumentParser(prog="devtools", description="Developer CLI tools")
|
|
49
|
+
parser.add_argument("-v", "--version", action="version", version=f"%(prog)s {VERSION}")
|
|
50
|
+
parser.add_argument("tool", nargs="?", help="Tool name to run")
|
|
51
|
+
parser.add_argument("args", nargs="*", help="Arguments for the tool")
|
|
52
|
+
parsed = parser.parse_args()
|
|
53
|
+
|
|
54
|
+
if parsed.tool:
|
|
55
|
+
run_tool(parsed.tool, parsed.args)
|
|
46
56
|
else:
|
|
47
57
|
from devtools.launcher import render_launcher
|
|
48
58
|
result = render_launcher()
|
|
@@ -3,6 +3,7 @@ if sys.platform == "win32":
|
|
|
3
3
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
4
4
|
|
|
5
5
|
from devtools.tools import TOOLS
|
|
6
|
+
from devtools.__main__ import VERSION
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
def render_launcher():
|
|
@@ -16,7 +17,7 @@ def render_launcher():
|
|
|
16
17
|
|
|
17
18
|
print()
|
|
18
19
|
print("=" * 50)
|
|
19
|
-
print(" devtools — 开发者工具箱")
|
|
20
|
+
print(f" devtools {VERSION} — 开发者工具箱")
|
|
20
21
|
print("=" * 50)
|
|
21
22
|
print()
|
|
22
23
|
for i, tool in enumerate(TOOLS):
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import subprocess
|
|
3
|
+
import re
|
|
4
|
+
import tempfile
|
|
5
|
+
import urllib.request
|
|
6
|
+
import json
|
|
7
|
+
|
|
8
|
+
# Local aapt2 binary directory
|
|
9
|
+
AAPT2_LOCAL_DIR = os.path.join(
|
|
10
|
+
os.path.dirname(os.path.dirname(os.path.dirname(__file__))),
|
|
11
|
+
"deps", "android-tools", "build"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
# Remote aapt2 download base URL (android-11.0.0_r33)
|
|
15
|
+
AAPT2_REMOTE_URL = "https://github.com/JonForShort/android-tools/raw/refs/heads/master/build/android-11.0.0_r33/aapt2/{arch}/bin/aapt2"
|
|
16
|
+
|
|
17
|
+
# Device-side aapt2 path
|
|
18
|
+
AAPT2_DEVICE_PATH = "/data/local/tmp/aapt2"
|
|
19
|
+
|
|
20
|
+
# App name cache
|
|
21
|
+
LABEL_CACHE_FILE = os.path.expanduser("~/.devtools/cache/apkpull/label_cache.json")
|
|
22
|
+
|
|
23
|
+
def load_label_cache() -> dict[str, str]:
|
|
24
|
+
"""Load cached app labels from file"""
|
|
25
|
+
if os.path.isfile(LABEL_CACHE_FILE):
|
|
26
|
+
try:
|
|
27
|
+
with open(LABEL_CACHE_FILE, "r", encoding="utf-8") as f:
|
|
28
|
+
return json.load(f)
|
|
29
|
+
except Exception:
|
|
30
|
+
return {}
|
|
31
|
+
return {}
|
|
32
|
+
|
|
33
|
+
def save_label_cache(cache: dict[str, str]) -> None:
|
|
34
|
+
"""Save app labels cache to file"""
|
|
35
|
+
try:
|
|
36
|
+
os.makedirs(os.path.dirname(LABEL_CACHE_FILE), exist_ok=True)
|
|
37
|
+
with open(LABEL_CACHE_FILE, "w", encoding="utf-8") as f:
|
|
38
|
+
json.dump(cache, f, ensure_ascii=False, indent=2)
|
|
39
|
+
except Exception:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
# Global cache
|
|
43
|
+
_label_cache: dict[str, str] | None = None
|
|
44
|
+
|
|
45
|
+
def get_label_cache() -> dict[str, str]:
|
|
46
|
+
"""Get or initialize the global label cache"""
|
|
47
|
+
global _label_cache
|
|
48
|
+
if _label_cache is None:
|
|
49
|
+
_label_cache = load_label_cache()
|
|
50
|
+
return _label_cache
|
|
51
|
+
|
|
52
|
+
def check_adb() -> bool:
|
|
53
|
+
"""检查 ADB 是否可用"""
|
|
54
|
+
try:
|
|
55
|
+
subprocess.run(["adb", "version"], capture_output=True, check=True)
|
|
56
|
+
return True
|
|
57
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
58
|
+
return False
|
|
59
|
+
|
|
60
|
+
def get_device_serial() -> str | None:
|
|
61
|
+
"""获取第一个已连接的设备序列号,多设备时返回第一个"""
|
|
62
|
+
try:
|
|
63
|
+
result = subprocess.run(["adb", "devices"], capture_output=True, text=True, encoding="utf-8", errors="replace", check=True)
|
|
64
|
+
lines = result.stdout.strip().split("\n")
|
|
65
|
+
for line in lines[1:]:
|
|
66
|
+
if line.strip() and not line.startswith("*"):
|
|
67
|
+
parts = line.split()
|
|
68
|
+
if len(parts) >= 2 and parts[1] == "device":
|
|
69
|
+
return parts[0]
|
|
70
|
+
return None
|
|
71
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
72
|
+
return None
|
|
73
|
+
|
|
74
|
+
def check_device() -> bool:
|
|
75
|
+
"""检查设备是否连接"""
|
|
76
|
+
return get_device_serial() is not None
|
|
77
|
+
|
|
78
|
+
def adb_cmd(args: list[str], serial: str | None = None) -> list[str]:
|
|
79
|
+
"""构建带 -s 参数的 adb 命令"""
|
|
80
|
+
if serial:
|
|
81
|
+
return ["adb", "-s", serial] + args
|
|
82
|
+
return ["adb"] + args
|
|
83
|
+
|
|
84
|
+
def check_aapt2_on_device() -> bool:
|
|
85
|
+
"""Check if /data/local/tmp/aapt2 exists on device"""
|
|
86
|
+
serial = get_device_serial()
|
|
87
|
+
try:
|
|
88
|
+
result = subprocess.run(
|
|
89
|
+
adb_cmd(["shell", "test", "-f", AAPT2_DEVICE_PATH], serial),
|
|
90
|
+
capture_output=True, check=False
|
|
91
|
+
)
|
|
92
|
+
return result.returncode == 0
|
|
93
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
94
|
+
return False
|
|
95
|
+
|
|
96
|
+
def detect_device_arch() -> str | None:
|
|
97
|
+
"""Get device CPU architecture via getprop"""
|
|
98
|
+
serial = get_device_serial()
|
|
99
|
+
try:
|
|
100
|
+
result = subprocess.run(
|
|
101
|
+
adb_cmd(["shell", "getprop", "ro.product.cpu.abi"], serial),
|
|
102
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace", check=True
|
|
103
|
+
)
|
|
104
|
+
arch = result.stdout.strip()
|
|
105
|
+
return arch if arch else None
|
|
106
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
# Local aapt2 dir name -> device arch values
|
|
110
|
+
ARCH_MAP = {
|
|
111
|
+
"arm64-v8a": ["arm64-v8a", "aarch64", "arm64"],
|
|
112
|
+
"armeabi-v7a": ["armeabi-v7a", "armeabi", "arm"],
|
|
113
|
+
"x86": ["x86", "i386", "i686"],
|
|
114
|
+
"x86_64": ["x86_64", "x64", "amd64"],
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
def find_local_aapt2(arch: str) -> str | None:
|
|
118
|
+
"""Find local aapt2 binary matching device arch"""
|
|
119
|
+
if not os.path.isdir(AAPT2_LOCAL_DIR):
|
|
120
|
+
return None
|
|
121
|
+
for version in sorted(os.listdir(AAPT2_LOCAL_DIR), reverse=True):
|
|
122
|
+
version_path = os.path.join(AAPT2_LOCAL_DIR, version, "aapt2")
|
|
123
|
+
if not os.path.isdir(version_path):
|
|
124
|
+
continue
|
|
125
|
+
for arch_dir, aliases in ARCH_MAP.items():
|
|
126
|
+
if arch in aliases:
|
|
127
|
+
aapt2_path = os.path.join(version_path, arch_dir, "bin", "aapt2")
|
|
128
|
+
if os.path.isfile(aapt2_path):
|
|
129
|
+
return aapt2_path
|
|
130
|
+
return None
|
|
131
|
+
|
|
132
|
+
def download_aapt2(arch: str) -> str | None:
|
|
133
|
+
"""Download aapt2 from GitHub and cache locally. Returns local path or None on failure."""
|
|
134
|
+
# Map device arch to download arch
|
|
135
|
+
arch_to_url_arch = {
|
|
136
|
+
"arm64-v8a": "arm64-v8a",
|
|
137
|
+
"aarch64": "arm64-v8a",
|
|
138
|
+
"arm64": "arm64-v8a",
|
|
139
|
+
"armeabi-v7a": "armeabi-v7a",
|
|
140
|
+
"armeabi": "armeabi-v7a",
|
|
141
|
+
"arm": "armeabi-v7a",
|
|
142
|
+
"x86": "x86",
|
|
143
|
+
"i386": "x86",
|
|
144
|
+
"i686": "x86",
|
|
145
|
+
"x86_64": "x86_64",
|
|
146
|
+
"x64": "x86_64",
|
|
147
|
+
"amd64": "x86_64",
|
|
148
|
+
}
|
|
149
|
+
url_arch = arch_to_url_arch.get(arch, arch)
|
|
150
|
+
url = AAPT2_REMOTE_URL.format(arch=url_arch)
|
|
151
|
+
|
|
152
|
+
# Cache path: AAPT2_LOCAL_DIR/android-11.0.0_r33/aapt2/{arch}/bin/aapt2
|
|
153
|
+
cache_dir = os.path.join(AAPT2_LOCAL_DIR, "android-11.0.0_r33", "aapt2", url_arch, "bin")
|
|
154
|
+
cache_path = os.path.join(cache_dir, "aapt2")
|
|
155
|
+
|
|
156
|
+
if os.path.isfile(cache_path):
|
|
157
|
+
return cache_path
|
|
158
|
+
|
|
159
|
+
try:
|
|
160
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
161
|
+
print(f"正在下载 aapt2 ({arch})...")
|
|
162
|
+
urllib.request.urlretrieve(url, cache_path)
|
|
163
|
+
# Make executable
|
|
164
|
+
os.chmod(cache_path, 0o755)
|
|
165
|
+
return cache_path
|
|
166
|
+
except Exception as e:
|
|
167
|
+
print(f"下载失败: {e}")
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
def ensure_aapt2_on_device() -> bool:
|
|
171
|
+
"""Push local aapt2 to device if missing; return success"""
|
|
172
|
+
if check_aapt2_on_device():
|
|
173
|
+
return True
|
|
174
|
+
|
|
175
|
+
serial = get_device_serial()
|
|
176
|
+
arch = detect_device_arch()
|
|
177
|
+
if not arch:
|
|
178
|
+
print("警告: 无法获取设备架构,使用 arm64-v8a")
|
|
179
|
+
arch = "arm64-v8a"
|
|
180
|
+
|
|
181
|
+
local_aapt2 = find_local_aapt2(arch)
|
|
182
|
+
if not local_aapt2:
|
|
183
|
+
print(f"本地未找到 aapt2 ({arch}),尝试下载...")
|
|
184
|
+
local_aapt2 = download_aapt2(arch)
|
|
185
|
+
if not local_aapt2:
|
|
186
|
+
print(f"警告: 下载失败,回退到 pm dump 方式")
|
|
187
|
+
return False
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
print(f"正在推送 aapt2 ({arch}) 到设备...")
|
|
191
|
+
subprocess.run(
|
|
192
|
+
adb_cmd(["push", local_aapt2, AAPT2_DEVICE_PATH], serial),
|
|
193
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace", check=True
|
|
194
|
+
)
|
|
195
|
+
subprocess.run(
|
|
196
|
+
adb_cmd(["shell", "chmod", "0755", AAPT2_DEVICE_PATH], serial),
|
|
197
|
+
capture_output=True, check=True
|
|
198
|
+
)
|
|
199
|
+
return True
|
|
200
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
201
|
+
return False
|
|
202
|
+
|
|
203
|
+
def scan_packages() -> list[str]:
|
|
204
|
+
"""执行 adb shell pm list packages -3,返回包名列表"""
|
|
205
|
+
serial = get_device_serial()
|
|
206
|
+
result = subprocess.run(
|
|
207
|
+
adb_cmd(["shell", "pm", "list", "packages", "-3"], serial),
|
|
208
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace", check=True
|
|
209
|
+
)
|
|
210
|
+
pkgs = []
|
|
211
|
+
for line in result.stdout.strip().split("\n"):
|
|
212
|
+
line = line.strip()
|
|
213
|
+
if line.startswith("package:"):
|
|
214
|
+
pkgs.append(line[8:])
|
|
215
|
+
return pkgs
|
|
216
|
+
|
|
217
|
+
def get_apk_path(pkg: str) -> str | None:
|
|
218
|
+
"""执行 pm path,返回 APK 物理路径"""
|
|
219
|
+
serial = get_device_serial()
|
|
220
|
+
try:
|
|
221
|
+
result = subprocess.run(
|
|
222
|
+
adb_cmd(["shell", "pm", "path", pkg], serial),
|
|
223
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace", check=True
|
|
224
|
+
)
|
|
225
|
+
for line in result.stdout.strip().split("\n"):
|
|
226
|
+
if line.startswith("package:"):
|
|
227
|
+
return line[8:].strip()
|
|
228
|
+
return None
|
|
229
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
230
|
+
return None
|
|
231
|
+
|
|
232
|
+
def get_app_name_pm_dump(pkg: str) -> str:
|
|
233
|
+
"""通过设备端 pm dump 提取 application label(失败时回退包名)"""
|
|
234
|
+
serial = get_device_serial()
|
|
235
|
+
try:
|
|
236
|
+
result = subprocess.run(
|
|
237
|
+
adb_cmd(["shell", "pm", "dump", pkg], serial),
|
|
238
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace"
|
|
239
|
+
)
|
|
240
|
+
if result.returncode != 0:
|
|
241
|
+
return pkg
|
|
242
|
+
for line in result.stdout.splitlines():
|
|
243
|
+
# application-label:'Facebook'
|
|
244
|
+
# application-label-zh-CN:'微信'
|
|
245
|
+
m = re.search(r"application-label(?:-[^:]+)?:'(.+?)'", line)
|
|
246
|
+
if m:
|
|
247
|
+
return m.group(1)
|
|
248
|
+
return pkg
|
|
249
|
+
except Exception:
|
|
250
|
+
return pkg
|
|
251
|
+
|
|
252
|
+
def get_app_name_aapt2(pkg: str) -> str | None:
|
|
253
|
+
"""Use device-side aapt2 dump badging to get app label. Returns None on failure."""
|
|
254
|
+
apk_path = get_apk_path(pkg)
|
|
255
|
+
if not apk_path:
|
|
256
|
+
return None
|
|
257
|
+
serial = get_device_serial()
|
|
258
|
+
try:
|
|
259
|
+
cmd = f'/data/local/tmp/aapt2 dump badging "{apk_path}"'
|
|
260
|
+
result = subprocess.run(
|
|
261
|
+
adb_cmd(["shell", cmd], serial),
|
|
262
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace"
|
|
263
|
+
)
|
|
264
|
+
if result.returncode != 0:
|
|
265
|
+
return None
|
|
266
|
+
for line in result.stdout.splitlines():
|
|
267
|
+
m = re.search(r"^application-label:'(.+?)'", line)
|
|
268
|
+
if m:
|
|
269
|
+
return m.group(1)
|
|
270
|
+
return None
|
|
271
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
272
|
+
return None
|
|
273
|
+
|
|
274
|
+
def get_app_name(pkg: str) -> str:
|
|
275
|
+
"""Get app label: check cache first, then fetch and cache"""
|
|
276
|
+
cache = get_label_cache()
|
|
277
|
+
if pkg in cache:
|
|
278
|
+
return cache[pkg]
|
|
279
|
+
|
|
280
|
+
label = get_app_name_aapt2(pkg)
|
|
281
|
+
if not label:
|
|
282
|
+
label = get_app_name_pm_dump(pkg)
|
|
283
|
+
|
|
284
|
+
cache[pkg] = label
|
|
285
|
+
save_label_cache(cache)
|
|
286
|
+
return label
|
|
287
|
+
|
|
288
|
+
def sanitize_filename(name: str) -> str:
|
|
289
|
+
"""Sanitize filename for cross-platform compatibility."""
|
|
290
|
+
sanitized = re.sub(r'[\\/:*?"<>|]+', "_", name).strip().rstrip(".")
|
|
291
|
+
return sanitized or "unknown"
|
|
292
|
+
|
|
293
|
+
def pull_apk(apk_path: str, dest_path: str) -> bool:
|
|
294
|
+
"""执行 adb pull,拉取 APK 到目标文件路径"""
|
|
295
|
+
serial = get_device_serial()
|
|
296
|
+
try:
|
|
297
|
+
subprocess.run(
|
|
298
|
+
adb_cmd(["pull", apk_path, dest_path], serial),
|
|
299
|
+
capture_output=True, text=True, encoding="utf-8", errors="replace", check=True
|
|
300
|
+
)
|
|
301
|
+
return True
|
|
302
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
303
|
+
return False
|
|
@@ -3,12 +3,13 @@ if sys.platform == "win32":
|
|
|
3
3
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
+
import argparse
|
|
6
7
|
import threading
|
|
7
8
|
from devtools.tools.apkpull.models import PackageInfo
|
|
8
9
|
|
|
9
10
|
def scan_packages_with_labels() -> list[PackageInfo]:
|
|
10
11
|
"""Scan packages and fetch labels in parallel threads (max 10 concurrent)"""
|
|
11
|
-
from devtools.tools.apkpull.core import scan_packages, check_adb, check_device, get_app_name
|
|
12
|
+
from devtools.tools.apkpull.core import scan_packages, check_adb, check_device, get_app_name, ensure_aapt2_on_device
|
|
12
13
|
|
|
13
14
|
if not check_adb():
|
|
14
15
|
print("错误: 未找到 adb 命令,请确保 Android SDK platform-tools 已安装并加入 PATH")
|
|
@@ -17,6 +18,7 @@ def scan_packages_with_labels() -> list[PackageInfo]:
|
|
|
17
18
|
print("错误: 未检测到设备,请确保手机已通过 USB 连接并开启了 USB 调试模式")
|
|
18
19
|
return []
|
|
19
20
|
|
|
21
|
+
ensure_aapt2_on_device()
|
|
20
22
|
print("正在获取应用列表...")
|
|
21
23
|
pkgs = scan_packages()
|
|
22
24
|
if not pkgs:
|
|
@@ -53,12 +55,12 @@ def scan_packages_with_labels() -> list[PackageInfo]:
|
|
|
53
55
|
|
|
54
56
|
def show_packages(packages: list[PackageInfo], short: bool = True) -> None:
|
|
55
57
|
print()
|
|
56
|
-
print(f"{'#':<5} {'
|
|
58
|
+
print(f"{'#':<5} {'包名':<50} {'应用名'}")
|
|
57
59
|
print("-" * 90)
|
|
58
60
|
for i, p in enumerate(packages):
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
print(f"{i+1:<5} {
|
|
61
|
+
pkg = p.pkg[:48] + ".." if short and len(p.pkg) > 50 else p.pkg
|
|
62
|
+
label = p.label[:38] + ".." if short and len(p.label) > 40 else p.label
|
|
63
|
+
print(f"{i+1:<5} {pkg:<50} {label}")
|
|
62
64
|
|
|
63
65
|
def select_packages(packages: list[PackageInfo], user_input: str) -> list[PackageInfo]:
|
|
64
66
|
"""Parse user input: keyword search or numeric selection (1,3-5, 1,3,5)"""
|
|
@@ -108,17 +110,26 @@ def run(args: list[str] | None = None):
|
|
|
108
110
|
if args is None:
|
|
109
111
|
args = []
|
|
110
112
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
parser = argparse.ArgumentParser(prog="apkpull", description="Pull APK from Android device")
|
|
114
|
+
parser.add_argument("dest_dir", nargs="?", default=os.getcwd(), help="目标目录 (默认当前目录)")
|
|
115
|
+
parser.add_argument("-s", "--search", metavar="KEYWORD", help="模糊搜索应用名/包名并退出")
|
|
116
|
+
|
|
117
|
+
parsed = parser.parse_args(args)
|
|
116
118
|
|
|
117
119
|
packages = scan_packages_with_labels()
|
|
118
120
|
if not packages:
|
|
119
121
|
return
|
|
120
122
|
|
|
121
|
-
|
|
123
|
+
# Fuzzy search mode: pre-filter packages and enter selection loop
|
|
124
|
+
if parsed.search:
|
|
125
|
+
keyword = parsed.search.lower()
|
|
126
|
+
packages = [p for p in packages if keyword in p.label.lower() or keyword in p.pkg.lower()]
|
|
127
|
+
if not packages:
|
|
128
|
+
print(f"未找到包含 '{parsed.search}' 的应用")
|
|
129
|
+
return
|
|
130
|
+
show_packages(packages)
|
|
131
|
+
else:
|
|
132
|
+
show_packages(packages)
|
|
122
133
|
|
|
123
134
|
while True:
|
|
124
135
|
print()
|
|
@@ -142,12 +153,12 @@ def run(args: list[str] | None = None):
|
|
|
142
153
|
|
|
143
154
|
show_packages(selected, short=False)
|
|
144
155
|
|
|
145
|
-
if not confirm_pull(selected, dest_dir):
|
|
156
|
+
if not confirm_pull(selected, parsed.dest_dir):
|
|
146
157
|
print("取消拉取")
|
|
147
158
|
continue
|
|
148
159
|
|
|
149
160
|
# Pull APKs
|
|
150
|
-
from devtools.tools.apkpull.core import get_apk_path, pull_apk
|
|
161
|
+
from devtools.tools.apkpull.core import get_apk_path, pull_apk, sanitize_filename
|
|
151
162
|
succeeded: list[str] = []
|
|
152
163
|
failed: list[tuple] = []
|
|
153
164
|
|
|
@@ -156,7 +167,9 @@ def run(args: list[str] | None = None):
|
|
|
156
167
|
if not apk_path:
|
|
157
168
|
failed.append((p.label, "未找到 APK 路径"))
|
|
158
169
|
continue
|
|
159
|
-
if
|
|
170
|
+
file_base = p.label if p.label and p.label != p.pkg else p.pkg
|
|
171
|
+
dest_apk = os.path.join(parsed.dest_dir, f"{sanitize_filename(file_base)}.apk")
|
|
172
|
+
if pull_apk(apk_path, dest_apk):
|
|
160
173
|
succeeded.append(p.label)
|
|
161
174
|
else:
|
|
162
175
|
failed.append((p.label, "拉取失败"))
|
damon_devtools-0.1.0/PKG-INFO
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: damon-devtools
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Developer CLI tools — portkill, removebg, apkpull
|
|
5
|
-
Author: damon
|
|
6
|
-
License: MIT
|
|
7
|
-
Keywords: cli,devtools,android,apk,adb
|
|
8
|
-
Requires-Python: >=3.10
|
|
9
|
-
Description-Content-Type: text/markdown
|
|
10
|
-
Requires-Dist: psutil>=5.9
|
|
11
|
-
|
|
12
|
-
# devtools
|
|
13
|
-
|
|
14
|
-
Developer CLI tools: portkill, removebg, apkpull.
|
|
15
|
-
|
|
16
|
-
## Tools
|
|
17
|
-
|
|
18
|
-
### portkill
|
|
19
|
-
Kill Node.js processes by port or scan all node processes.
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
python -m devtools portkill
|
|
23
|
-
python -m devtools portkill --listen-only
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### apkpull
|
|
27
|
-
Pull APK files from Android device via ADB.
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
python -m devtools apkpull [target_dir]
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### removebg
|
|
34
|
-
Remove image backgrounds using the remove.bg API.
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
python -m devtools removebg input.jpg
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Install
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
pip install damon-devtools
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Requires: Python >=3.10
|
damon_devtools-0.1.0/README.md
DELETED
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
# devtools
|
|
2
|
-
|
|
3
|
-
Developer CLI tools: portkill, removebg, apkpull.
|
|
4
|
-
|
|
5
|
-
## Tools
|
|
6
|
-
|
|
7
|
-
### portkill
|
|
8
|
-
Kill Node.js processes by port or scan all node processes.
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
python -m devtools portkill
|
|
12
|
-
python -m devtools portkill --listen-only
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
### apkpull
|
|
16
|
-
Pull APK files from Android device via ADB.
|
|
17
|
-
|
|
18
|
-
```bash
|
|
19
|
-
python -m devtools apkpull [target_dir]
|
|
20
|
-
```
|
|
21
|
-
|
|
22
|
-
### removebg
|
|
23
|
-
Remove image backgrounds using the remove.bg API.
|
|
24
|
-
|
|
25
|
-
```bash
|
|
26
|
-
python -m devtools removebg input.jpg
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
## Install
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
pip install damon-devtools
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Requires: Python >=3.10
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: damon-devtools
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Developer CLI tools — portkill, removebg, apkpull
|
|
5
|
-
Author: damon
|
|
6
|
-
License: MIT
|
|
7
|
-
Keywords: cli,devtools,android,apk,adb
|
|
8
|
-
Requires-Python: >=3.10
|
|
9
|
-
Description-Content-Type: text/markdown
|
|
10
|
-
Requires-Dist: psutil>=5.9
|
|
11
|
-
|
|
12
|
-
# devtools
|
|
13
|
-
|
|
14
|
-
Developer CLI tools: portkill, removebg, apkpull.
|
|
15
|
-
|
|
16
|
-
## Tools
|
|
17
|
-
|
|
18
|
-
### portkill
|
|
19
|
-
Kill Node.js processes by port or scan all node processes.
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
python -m devtools portkill
|
|
23
|
-
python -m devtools portkill --listen-only
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
### apkpull
|
|
27
|
-
Pull APK files from Android device via ADB.
|
|
28
|
-
|
|
29
|
-
```bash
|
|
30
|
-
python -m devtools apkpull [target_dir]
|
|
31
|
-
```
|
|
32
|
-
|
|
33
|
-
### removebg
|
|
34
|
-
Remove image backgrounds using the remove.bg API.
|
|
35
|
-
|
|
36
|
-
```bash
|
|
37
|
-
python -m devtools removebg input.jpg
|
|
38
|
-
```
|
|
39
|
-
|
|
40
|
-
## Install
|
|
41
|
-
|
|
42
|
-
```bash
|
|
43
|
-
pip install damon-devtools
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Requires: Python >=3.10
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import subprocess
|
|
2
|
-
import re
|
|
3
|
-
import os
|
|
4
|
-
import tempfile
|
|
5
|
-
|
|
6
|
-
def _find_aapt() -> str | None:
|
|
7
|
-
"""Find aapt.exe — checks PATH first, then Android SDK build-tools"""
|
|
8
|
-
# 1. Check PATH
|
|
9
|
-
import shutil as _shutil
|
|
10
|
-
path_aapt = _shutil.which("aapt")
|
|
11
|
-
if path_aapt and os.path.isfile(path_aapt):
|
|
12
|
-
return path_aapt
|
|
13
|
-
path_aapt2 = _shutil.which("aapt2")
|
|
14
|
-
if path_aapt2 and os.path.isfile(path_aapt2):
|
|
15
|
-
return path_aapt2
|
|
16
|
-
# 2. Check Android SDK build-tools
|
|
17
|
-
sdk_base = os.environ.get("LOCALAPPDATA", "") + "/Android/Sdk"
|
|
18
|
-
build_tools = os.path.join(sdk_base, "build-tools")
|
|
19
|
-
if os.path.isdir(build_tools):
|
|
20
|
-
versions = sorted(os.listdir(build_tools), reverse=True)
|
|
21
|
-
for v in versions:
|
|
22
|
-
for binary in ("aapt.exe", "aapt2.exe"):
|
|
23
|
-
aapt = os.path.join(build_tools, v, binary)
|
|
24
|
-
if os.path.isfile(aapt):
|
|
25
|
-
return aapt
|
|
26
|
-
return None
|
|
27
|
-
|
|
28
|
-
AAPT_PATH = _find_aapt()
|
|
29
|
-
|
|
30
|
-
def check_adb() -> bool:
|
|
31
|
-
"""检查 ADB 是否可用"""
|
|
32
|
-
try:
|
|
33
|
-
subprocess.run(["adb", "version"], capture_output=True, check=True)
|
|
34
|
-
return True
|
|
35
|
-
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
36
|
-
return False
|
|
37
|
-
|
|
38
|
-
def check_device() -> bool:
|
|
39
|
-
"""检查设备是否连接"""
|
|
40
|
-
try:
|
|
41
|
-
result = subprocess.run(["adb", "devices"], capture_output=True, text=True, encoding="utf-8", errors="replace", check=True)
|
|
42
|
-
lines = result.stdout.strip().split("\n")
|
|
43
|
-
for line in lines[1:]:
|
|
44
|
-
if line.strip() and not line.startswith("*"):
|
|
45
|
-
parts = line.split()
|
|
46
|
-
if len(parts) >= 2 and parts[1] == "device":
|
|
47
|
-
return True
|
|
48
|
-
return False
|
|
49
|
-
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
50
|
-
return False
|
|
51
|
-
|
|
52
|
-
def scan_packages() -> list[str]:
|
|
53
|
-
"""执行 adb shell pm list packages -3,返回包名列表"""
|
|
54
|
-
result = subprocess.run(
|
|
55
|
-
["adb", "shell", "pm", "list", "packages", "-3"],
|
|
56
|
-
capture_output=True, text=True, encoding="utf-8", errors="replace", check=True
|
|
57
|
-
)
|
|
58
|
-
pkgs = []
|
|
59
|
-
for line in result.stdout.strip().split("\n"):
|
|
60
|
-
line = line.strip()
|
|
61
|
-
if line.startswith("package:"):
|
|
62
|
-
pkgs.append(line[8:])
|
|
63
|
-
return pkgs
|
|
64
|
-
|
|
65
|
-
def get_apk_path(pkg: str) -> str | None:
|
|
66
|
-
"""执行 pm path,返回 APK 物理路径"""
|
|
67
|
-
try:
|
|
68
|
-
result = subprocess.run(
|
|
69
|
-
["adb", "shell", "pm", "path", pkg],
|
|
70
|
-
capture_output=True, text=True, encoding="utf-8", errors="replace", check=True
|
|
71
|
-
)
|
|
72
|
-
for line in result.stdout.strip().split("\n"):
|
|
73
|
-
if line.startswith("package:"):
|
|
74
|
-
return line[8:].strip()
|
|
75
|
-
return None
|
|
76
|
-
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
77
|
-
return None
|
|
78
|
-
|
|
79
|
-
def get_app_name(pkg: str) -> str:
|
|
80
|
-
"""通过 aapt 提取 APK 的 application label"""
|
|
81
|
-
if not AAPT_PATH:
|
|
82
|
-
return pkg
|
|
83
|
-
apk_path = get_apk_path(pkg)
|
|
84
|
-
if not apk_path:
|
|
85
|
-
return pkg
|
|
86
|
-
tmp = os.path.join(tempfile.gettempdir(), f"apkpull_{os.getpid()}_{pkg.replace('.', '_')}.apk")
|
|
87
|
-
try:
|
|
88
|
-
# pull APK to temp file
|
|
89
|
-
pull_ok = subprocess.run(
|
|
90
|
-
["adb", "pull", apk_path, tmp],
|
|
91
|
-
capture_output=True, encoding="utf-8", errors="replace"
|
|
92
|
-
).returncode == 0
|
|
93
|
-
if not pull_ok or not os.path.exists(tmp):
|
|
94
|
-
return pkg
|
|
95
|
-
# extract label via aapt
|
|
96
|
-
result = subprocess.run(
|
|
97
|
-
[AAPT_PATH, "dump", "badging", tmp],
|
|
98
|
-
capture_output=True, encoding="utf-8", errors="replace"
|
|
99
|
-
)
|
|
100
|
-
for line in result.stdout.split("\n"):
|
|
101
|
-
m = re.search(r"application-label:'(.+?)'", line)
|
|
102
|
-
if m:
|
|
103
|
-
return m.group(1)
|
|
104
|
-
return pkg
|
|
105
|
-
except Exception:
|
|
106
|
-
return pkg
|
|
107
|
-
finally:
|
|
108
|
-
if os.path.exists(tmp):
|
|
109
|
-
try:
|
|
110
|
-
os.remove(tmp)
|
|
111
|
-
except OSError:
|
|
112
|
-
pass
|
|
113
|
-
|
|
114
|
-
def pull_apk(apk_path: str, dest_dir: str) -> bool:
|
|
115
|
-
"""执行 adb pull,拉取 APK 到目标目录"""
|
|
116
|
-
try:
|
|
117
|
-
subprocess.run(
|
|
118
|
-
["adb", "pull", apk_path, dest_dir],
|
|
119
|
-
capture_output=True, text=True, encoding="utf-8", errors="replace", check=True
|
|
120
|
-
)
|
|
121
|
-
return True
|
|
122
|
-
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
123
|
-
return False
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|