apksearch 1.1.0__tar.gz → 1.2.5__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {apksearch-1.1.0 → apksearch-1.2.5}/PKG-INFO +28 -5
- {apksearch-1.1.0 → apksearch-1.2.5}/README.md +26 -3
- apksearch-1.2.5/apksearch/__init__.py +7 -0
- {apksearch-1.1.0 → apksearch-1.2.5}/apksearch/cli.py +68 -12
- apksearch-1.2.5/apksearch/sites/apkcombo.py +107 -0
- apksearch-1.2.5/apksearch/sites/apkfab.py +120 -0
- {apksearch-1.1.0 → apksearch-1.2.5}/apksearch/sites/apkpure.py +43 -18
- {apksearch-1.1.0 → apksearch-1.2.5}/apksearch.egg-info/PKG-INFO +28 -5
- {apksearch-1.1.0 → apksearch-1.2.5}/apksearch.egg-info/SOURCES.txt +4 -0
- {apksearch-1.1.0 → apksearch-1.2.5}/pyproject.toml +2 -2
- apksearch-1.2.5/tests/test_apkcombo.py +38 -0
- apksearch-1.2.5/tests/test_apkfab.py +38 -0
- apksearch-1.1.0/apksearch/__init__.py +0 -5
- {apksearch-1.1.0 → apksearch-1.2.5}/LICENSE +0 -0
- {apksearch-1.1.0 → apksearch-1.2.5}/apksearch/__main__.py +0 -0
- {apksearch-1.1.0 → apksearch-1.2.5}/apksearch/sites/__init__.py +0 -0
- {apksearch-1.1.0 → apksearch-1.2.5}/apksearch/sites/apkmirror.py +0 -0
- {apksearch-1.1.0 → apksearch-1.2.5}/apksearch/sites/appteka.py +0 -0
- {apksearch-1.1.0 → apksearch-1.2.5}/apksearch.egg-info/dependency_links.txt +0 -0
- {apksearch-1.1.0 → apksearch-1.2.5}/apksearch.egg-info/entry_points.txt +0 -0
- {apksearch-1.1.0 → apksearch-1.2.5}/apksearch.egg-info/requires.txt +0 -0
- {apksearch-1.1.0 → apksearch-1.2.5}/apksearch.egg-info/top_level.txt +0 -0
- {apksearch-1.1.0 → apksearch-1.2.5}/setup.cfg +0 -0
- {apksearch-1.1.0 → apksearch-1.2.5}/tests/test_apkmirror.py +0 -0
- {apksearch-1.1.0 → apksearch-1.2.5}/tests/test_apkpure.py +0 -0
- {apksearch-1.1.0 → apksearch-1.2.5}/tests/test_appteka.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: apksearch
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.2.5
|
4
4
|
Summary: Search for apks on varius websites
|
5
5
|
Author-email: Abhi <allinoneallinone00@gmail.com>
|
6
6
|
License: MIT License
|
@@ -28,7 +28,7 @@ License: MIT License
|
|
28
28
|
Project-URL: homepage, https://github.com/AbhiTheModder/apksearch.git
|
29
29
|
Project-URL: source, https://github.com/AbhiTheModder/apksearch.git
|
30
30
|
Project-URL: issues, https://github.com/AbhiTheModder/apksearch.git/issues
|
31
|
-
Classifier: Development Status ::
|
31
|
+
Classifier: Development Status :: 5 - Production/Stable
|
32
32
|
Classifier: Natural Language :: English
|
33
33
|
Classifier: Intended Audience :: Developers
|
34
34
|
Classifier: License :: OSI Approved :: MIT License
|
@@ -63,18 +63,32 @@ There were countless occasions when I needed a specific APK for a package name,
|
|
63
63
|
- **Retrieve APK Versions and Download Links:** It can fetch available versions and their download links for a given APK from APKPure and APKMirror.
|
64
64
|
- **Command-Line Interface:** A CLI is available for users to search for APKs directly from the command line.
|
65
65
|
|
66
|
+
## Supported Websites
|
67
|
+
|
68
|
+
- [APKPure](https://apkpure.net/)
|
69
|
+
- [APKMirror](https://www.apkmirror.com/)
|
70
|
+
- [APKCombo](https://apkcombo.app/)
|
71
|
+
- [APKFab](https://apkfab.com/)
|
72
|
+
- [Appteka](https://appteka.store/)
|
73
|
+
|
74
|
+
> [!NOTE]
|
75
|
+
> **For site owners:**
|
76
|
+
> If you're the owner of a website that's not listed here and you'd like to add support for it, feel free to open an issue or submit a pull request. I'm open to adding more websites to the library.
|
77
|
+
> I respect the _value of user engagement and the revenue_ it generates for your site. To honor this, I have deliberately avoided including a download feature in the library, ensuring users will still need to visit your website, maintaining its traffic and engagement.
|
78
|
+
> Additionally, I kindly ask that you **refrain from enforcing strict blocking measures**, such as aggressive Cloudflare rules, as the library is designed to work collaboratively rather than disruptively. Thank you!
|
79
|
+
|
66
80
|
## Installation
|
67
81
|
|
68
82
|
To install the `apksearch` library, use the following command:
|
69
83
|
|
70
84
|
```sh
|
71
|
-
pip install git+https://github.com/AbhiTheModder/apksearch.git
|
85
|
+
pip install -U git+https://github.com/AbhiTheModder/apksearch.git
|
72
86
|
```
|
73
87
|
|
74
88
|
OR, through pip:
|
75
89
|
|
76
90
|
```sh
|
77
|
-
pip install apksearch
|
91
|
+
pip install -U apksearch
|
78
92
|
```
|
79
93
|
|
80
94
|
## Usage
|
@@ -117,7 +131,7 @@ if result:
|
|
117
131
|
|
118
132
|
### Classes and Methods
|
119
133
|
|
120
|
-
#### `APKPure`
|
134
|
+
#### `APKPure` | `APKCombo` | `APKFab`
|
121
135
|
|
122
136
|
- **`__init__(self, pkg_name: str)`**: Initializes with the package name.
|
123
137
|
- **`search_apk(self) -> None | tuple[str, str]`**: Searches for the APK on APKPure and returns the title and link if found.
|
@@ -146,6 +160,13 @@ pytest
|
|
146
160
|
|
147
161
|
- [ ] Add more websites to search for APKs.
|
148
162
|
|
163
|
+
## Acknowledgements
|
164
|
+
|
165
|
+
- [APKUpdater](https://github.com/rumboalla/apkupdater) for APKMirror API.
|
166
|
+
- [apkeep](https://github.com/EFForg/apkeep) for APKPure API.
|
167
|
+
|
168
|
+
**Recommendation:** If you're looking for an APK downloader, I highly recommend using [apkeep](https://github.com/EFForg/apkeep).
|
169
|
+
|
149
170
|
## License
|
150
171
|
|
151
172
|
This project is licensed under the MIT License. See the [LICENSE](https://github.com/AbhiTheModder/apksearch/blob/main/LICENSE) file for more details.
|
@@ -153,3 +174,5 @@ This project is licensed under the MIT License. See the [LICENSE](https://github
|
|
153
174
|
## Contributing
|
154
175
|
|
155
176
|
Contributions are welcome! Please open an issue or submit a pull request on [GitHub](https://github.com/AbhiTheModder/apksearch).
|
177
|
+
|
178
|
+
If you find this project helpful, please consider giving it a ⭐. Your support is greatly appreciated!
|
@@ -11,18 +11,32 @@ There were countless occasions when I needed a specific APK for a package name,
|
|
11
11
|
- **Retrieve APK Versions and Download Links:** It can fetch available versions and their download links for a given APK from APKPure and APKMirror.
|
12
12
|
- **Command-Line Interface:** A CLI is available for users to search for APKs directly from the command line.
|
13
13
|
|
14
|
+
## Supported Websites
|
15
|
+
|
16
|
+
- [APKPure](https://apkpure.net/)
|
17
|
+
- [APKMirror](https://www.apkmirror.com/)
|
18
|
+
- [APKCombo](https://apkcombo.app/)
|
19
|
+
- [APKFab](https://apkfab.com/)
|
20
|
+
- [Appteka](https://appteka.store/)
|
21
|
+
|
22
|
+
> [!NOTE]
|
23
|
+
> **For site owners:**
|
24
|
+
> If you're the owner of a website that's not listed here and you'd like to add support for it, feel free to open an issue or submit a pull request. I'm open to adding more websites to the library.
|
25
|
+
> I respect the _value of user engagement and the revenue_ it generates for your site. To honor this, I have deliberately avoided including a download feature in the library, ensuring users will still need to visit your website, maintaining its traffic and engagement.
|
26
|
+
> Additionally, I kindly ask that you **refrain from enforcing strict blocking measures**, such as aggressive Cloudflare rules, as the library is designed to work collaboratively rather than disruptively. Thank you!
|
27
|
+
|
14
28
|
## Installation
|
15
29
|
|
16
30
|
To install the `apksearch` library, use the following command:
|
17
31
|
|
18
32
|
```sh
|
19
|
-
pip install git+https://github.com/AbhiTheModder/apksearch.git
|
33
|
+
pip install -U git+https://github.com/AbhiTheModder/apksearch.git
|
20
34
|
```
|
21
35
|
|
22
36
|
OR, through pip:
|
23
37
|
|
24
38
|
```sh
|
25
|
-
pip install apksearch
|
39
|
+
pip install -U apksearch
|
26
40
|
```
|
27
41
|
|
28
42
|
## Usage
|
@@ -65,7 +79,7 @@ if result:
|
|
65
79
|
|
66
80
|
### Classes and Methods
|
67
81
|
|
68
|
-
#### `APKPure`
|
82
|
+
#### `APKPure` | `APKCombo` | `APKFab`
|
69
83
|
|
70
84
|
- **`__init__(self, pkg_name: str)`**: Initializes with the package name.
|
71
85
|
- **`search_apk(self) -> None | tuple[str, str]`**: Searches for the APK on APKPure and returns the title and link if found.
|
@@ -94,6 +108,13 @@ pytest
|
|
94
108
|
|
95
109
|
- [ ] Add more websites to search for APKs.
|
96
110
|
|
111
|
+
## Acknowledgements
|
112
|
+
|
113
|
+
- [APKUpdater](https://github.com/rumboalla/apkupdater) for APKMirror API.
|
114
|
+
- [apkeep](https://github.com/EFForg/apkeep) for APKPure API.
|
115
|
+
|
116
|
+
**Recommendation:** If you're looking for an APK downloader, I highly recommend using [apkeep](https://github.com/EFForg/apkeep).
|
117
|
+
|
97
118
|
## License
|
98
119
|
|
99
120
|
This project is licensed under the MIT License. See the [LICENSE](https://github.com/AbhiTheModder/apksearch/blob/main/LICENSE) file for more details.
|
@@ -101,3 +122,5 @@ This project is licensed under the MIT License. See the [LICENSE](https://github
|
|
101
122
|
## Contributing
|
102
123
|
|
103
124
|
Contributions are welcome! Please open an issue or submit a pull request on [GitHub](https://github.com/AbhiTheModder/apksearch).
|
125
|
+
|
126
|
+
If you find this project helpful, please consider giving it a ⭐. Your support is greatly appreciated!
|
@@ -1,6 +1,6 @@
|
|
1
1
|
import argparse
|
2
2
|
|
3
|
-
from apksearch import APKPure, APKMirror, AppTeka
|
3
|
+
from apksearch import APKPure, APKMirror, AppTeka, APKCombo, APKFab
|
4
4
|
from requests.exceptions import ConnectionError, ConnectTimeout
|
5
5
|
|
6
6
|
# Color codes
|
@@ -22,7 +22,59 @@ def search_apkpure(pkg_name: str, version: str | None) -> None:
|
|
22
22
|
title, apk_link = result_apkpure
|
23
23
|
print(f"{BOLD}APKPure:{NC} Found {GREEN}{title}{NC}") if title else None
|
24
24
|
print(f" ╰─> {BOLD}Link: {YELLOW}{apk_link}{NC}") if not version else None
|
25
|
-
|
25
|
+
if version:
|
26
|
+
versions: list[tuple[str, str]] = apkpure.find_versions(apk_link)
|
27
|
+
if versions:
|
28
|
+
for version_tuple in versions:
|
29
|
+
if version_tuple[0] == version:
|
30
|
+
print(
|
31
|
+
f" ╰─> {BOLD}Version: {GREEN}{version}{NC} - {YELLOW}{version_tuple[1]}{NC}"
|
32
|
+
)
|
33
|
+
break
|
34
|
+
else:
|
35
|
+
print(f"{BOLD}APKPure:{NC} Version {RED}{version}{NC} not found!")
|
36
|
+
else:
|
37
|
+
print(f"{BOLD}APKPure:{NC} No Results!")
|
38
|
+
|
39
|
+
|
40
|
+
def search_apkfab(pkg_name: str, version: str | None) -> None:
|
41
|
+
apkfab = APKFab(pkg_name)
|
42
|
+
try:
|
43
|
+
result_apkfab: tuple[str, str] | None = apkfab.search_apk()
|
44
|
+
except (ConnectionError, ConnectTimeout):
|
45
|
+
result_apkfab = None
|
46
|
+
print(f"{RED}Failed to resolve 'apkfab.com'!{NC}")
|
47
|
+
if result_apkfab:
|
48
|
+
title, apk_link = result_apkfab
|
49
|
+
print(f"{BOLD}APKFab:{NC} Found {GREEN}{title}{NC}") if title else None
|
50
|
+
print(f" ╰─> {BOLD}Link: {YELLOW}{apk_link}{NC}") if not version else None
|
51
|
+
if version:
|
52
|
+
versions: list[tuple[str, str]] = apkfab.find_versions(apk_link)
|
53
|
+
if versions:
|
54
|
+
for version_tuple in versions:
|
55
|
+
if version_tuple[0] == version:
|
56
|
+
print(
|
57
|
+
f" ╰─> {BOLD}Version: {GREEN}{version}{NC} - {YELLOW}{version_tuple[1]}{NC}"
|
58
|
+
)
|
59
|
+
break
|
60
|
+
else:
|
61
|
+
print(f"{BOLD}APKFab:{NC} Version {RED}{version}{NC} not found!")
|
62
|
+
else:
|
63
|
+
print(f"{BOLD}APKFab:{NC} No Results!")
|
64
|
+
|
65
|
+
|
66
|
+
def search_apkcombo(pkg_name: str, version: str | None) -> None:
|
67
|
+
apkcombo = APKCombo(pkg_name)
|
68
|
+
try:
|
69
|
+
result_apkcombo: tuple[str, str] | None = apkcombo.search_apk()
|
70
|
+
except (ConnectionError, ConnectTimeout):
|
71
|
+
result_apkcombo = None
|
72
|
+
print(f"{RED}Failed to resolve 'apkcombo.app'!{NC}")
|
73
|
+
if result_apkcombo:
|
74
|
+
title, apk_link = result_apkcombo
|
75
|
+
print(f"{BOLD}APKCombo:{NC} Found {GREEN}{title}{NC}") if title else None
|
76
|
+
print(f" ╰─> {BOLD}Link: {YELLOW}{apk_link}{NC}") if not version else None
|
77
|
+
versions: list[tuple[str, str]] = apkcombo.find_versions(apk_link)
|
26
78
|
if version:
|
27
79
|
for version_tuple in versions:
|
28
80
|
if version_tuple[0] == version:
|
@@ -31,20 +83,20 @@ def search_apkpure(pkg_name: str, version: str | None) -> None:
|
|
31
83
|
)
|
32
84
|
break
|
33
85
|
else:
|
34
|
-
print(f"{BOLD}
|
86
|
+
print(f"{BOLD}APKCombo:{NC} Version {RED}{version}{NC} not found!")
|
35
87
|
else:
|
36
|
-
print(f"{BOLD}
|
88
|
+
print(f"{BOLD}APKCombo:{NC} No Results!")
|
37
89
|
|
38
90
|
|
39
91
|
def search_apkmirror(pkg_name: str, version: str | None) -> None:
|
40
92
|
apkmirror = APKMirror(pkg_name)
|
41
93
|
try:
|
42
|
-
|
94
|
+
result_apkmirror: tuple[str, str] | None = apkmirror.search_apk()
|
43
95
|
except (ConnectionError, ConnectTimeout):
|
44
|
-
|
96
|
+
result_apkmirror = None
|
45
97
|
print(f"{RED}Failed to resolve 'apkmirror.com'!{NC}")
|
46
|
-
if
|
47
|
-
title, apk_link =
|
98
|
+
if result_apkmirror:
|
99
|
+
title, apk_link = result_apkmirror
|
48
100
|
print(f"{BOLD}APKMirror:{NC} Found {GREEN}{title}{NC}") if title else None
|
49
101
|
print(f" ╰─> {BOLD}Link: {YELLOW}{apk_link}{NC}") if not version else None
|
50
102
|
if version:
|
@@ -62,12 +114,12 @@ def search_apkmirror(pkg_name: str, version: str | None) -> None:
|
|
62
114
|
def search_appteka(pkg_name: str, version: str | None) -> None:
|
63
115
|
appteka = AppTeka(pkg_name)
|
64
116
|
try:
|
65
|
-
|
117
|
+
result_appteka: tuple[str, str] | None = appteka.search_apk(version)
|
66
118
|
except (ConnectionError, ConnectTimeout):
|
67
|
-
|
119
|
+
result_appteka = None
|
68
120
|
print(f"{RED}Failed to resolve 'appteka.store'!{NC}")
|
69
|
-
if
|
70
|
-
title, apk_link =
|
121
|
+
if result_appteka:
|
122
|
+
title, apk_link = result_appteka
|
71
123
|
print(f"{BOLD}AppTeka:{NC} Found {GREEN}{title}{NC}") if title else None
|
72
124
|
if version:
|
73
125
|
if apk_link:
|
@@ -99,6 +151,10 @@ def main():
|
|
99
151
|
search_apkmirror(pkg_name, version)
|
100
152
|
# Initiate search on appteka
|
101
153
|
search_appteka(pkg_name, version)
|
154
|
+
# Initiate search on apkcombo
|
155
|
+
search_apkcombo(pkg_name, version)
|
156
|
+
# Initiate search on apkfab
|
157
|
+
search_apkfab(pkg_name, version)
|
102
158
|
|
103
159
|
|
104
160
|
if __name__ == "__main__":
|
@@ -0,0 +1,107 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
import requests
|
3
|
+
|
4
|
+
|
5
|
+
class APKCombo:
|
6
|
+
"""
|
7
|
+
This class provides methods to search for an APK on APKCombo based on package name,
|
8
|
+
and to find available versions and their download links for a given APK link.
|
9
|
+
|
10
|
+
Parameters:
|
11
|
+
pkg_name (str): The package name of the APK to search for.
|
12
|
+
|
13
|
+
Attributes:
|
14
|
+
pkg_name (str): The package name of the APK to search for.
|
15
|
+
base_url (str): The base URL of the APKCombo website.
|
16
|
+
search_url (str): The URL used to search for APKs on APKCombo.
|
17
|
+
headers (dict): The headers used for making HTTP requests.
|
18
|
+
session (requests.Session): The session object used for making HTTP requests.
|
19
|
+
|
20
|
+
Methods:
|
21
|
+
search_apk() -> None | tuple[str, str]:
|
22
|
+
Searches for the APK on APKCombo and returns the title and link if found.
|
23
|
+
|
24
|
+
find_versions(apk_link: str) -> list[tuple[str, str]]:
|
25
|
+
Finds and returns a list of versions and their download links for the given APK link.
|
26
|
+
"""
|
27
|
+
|
28
|
+
def __init__(self, pkg_name: str):
|
29
|
+
self.pkg_name = pkg_name
|
30
|
+
self.base_url = "https://apkcombo.app"
|
31
|
+
self.search_url = self.base_url + "/search/"
|
32
|
+
self.headers = {
|
33
|
+
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
34
|
+
"accept-language": "en-US,en;q=0.9,en-IN;q=0.8",
|
35
|
+
"cache-control": "no-cache",
|
36
|
+
"cookie": "__apkcombo_lang=en",
|
37
|
+
"dnt": "1",
|
38
|
+
"pragma": "no-cache",
|
39
|
+
"priority": "u=0, i",
|
40
|
+
"referer": "https://apkcombo.app/",
|
41
|
+
"sec-ch-ua": '"Microsoft Edge";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
|
42
|
+
"sec-ch-ua-mobile": "?0",
|
43
|
+
"sec-ch-ua-platform": '"Windows"',
|
44
|
+
"sec-fetch-dest": "document",
|
45
|
+
"sec-fetch-mode": "navigate",
|
46
|
+
"sec-fetch-site": "same-origin",
|
47
|
+
"sec-fetch-user": "?1",
|
48
|
+
"upgrade-insecure-requests": "1",
|
49
|
+
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
|
50
|
+
}
|
51
|
+
self.session = requests.Session()
|
52
|
+
|
53
|
+
def search_apk(self) -> None | tuple[str, str]:
|
54
|
+
"""
|
55
|
+
Searches for the APK on APKCombo and returns the title and link if found.
|
56
|
+
|
57
|
+
Returns:
|
58
|
+
None: If no matching APK is found.
|
59
|
+
tuple[str, str]: A tuple containing the title and link of the matching APK if found.
|
60
|
+
"""
|
61
|
+
pkg_name = self.pkg_name
|
62
|
+
url = self.search_url + pkg_name
|
63
|
+
response: requests.Response = self.session.get(
|
64
|
+
url, headers=self.headers, allow_redirects=False
|
65
|
+
)
|
66
|
+
# Redirect to the APK page if there's only one result. i.e, apk found.
|
67
|
+
if response.status_code == 302:
|
68
|
+
url = self.search_url + response.headers["Location"]
|
69
|
+
response: requests.Response = self.session.get(url, headers=self.headers)
|
70
|
+
elif response.status_code == 200: # Package name not found or multiple results.
|
71
|
+
return None
|
72
|
+
else:
|
73
|
+
raise Exception(f"Error: {response.status_code}")
|
74
|
+
soup = BeautifulSoup(response.text, "html.parser")
|
75
|
+
try:
|
76
|
+
title = soup.find("div", {"class": "app_name"}).text.strip()
|
77
|
+
except AttributeError:
|
78
|
+
return None
|
79
|
+
apk_link = url
|
80
|
+
return title, apk_link
|
81
|
+
|
82
|
+
def find_versions(self, apk_link: str) -> list[tuple[str, str]]:
|
83
|
+
"""
|
84
|
+
Finds and returns a list of versions and their download links for the given APK link.
|
85
|
+
|
86
|
+
Parameters:
|
87
|
+
apk_link (str): The link to the APK on the APKCombo website.
|
88
|
+
|
89
|
+
Returns:
|
90
|
+
list[tuple[str, str]]: A list of tuples, where each tuple contains the version number
|
91
|
+
and its corresponding download link. If no versions are found, an empty list is returned.
|
92
|
+
"""
|
93
|
+
url = apk_link + "/old-versions"
|
94
|
+
response: requests.Response = self.session.get(url, headers=self.headers)
|
95
|
+
soup = BeautifulSoup(response.text, "html.parser")
|
96
|
+
versions_list = soup.find("ul", {"class": "list-versions"})
|
97
|
+
versions_info = []
|
98
|
+
|
99
|
+
if versions_list:
|
100
|
+
versions = versions_list.find_all("a", {"class": "ver-item"})
|
101
|
+
for version in versions:
|
102
|
+
version_number = version.find("span", {"class": "vername"}).text
|
103
|
+
version_number = version_number.split(" ")[-1]
|
104
|
+
download_url = self.base_url + version["href"]
|
105
|
+
versions_info.append((version_number, download_url))
|
106
|
+
|
107
|
+
return versions_info
|
@@ -0,0 +1,120 @@
|
|
1
|
+
from bs4 import BeautifulSoup
|
2
|
+
import requests
|
3
|
+
|
4
|
+
|
5
|
+
class APKFab:
|
6
|
+
"""
|
7
|
+
This class provides methods to search for an APK on APKFab based on package name,
|
8
|
+
and to find available versions and their download links for a given APK link.
|
9
|
+
|
10
|
+
Parameters:
|
11
|
+
pkg_name (str): The package name of the APK to search for.
|
12
|
+
|
13
|
+
Attributes:
|
14
|
+
pkg_name (str): The package name of the APK to search for.
|
15
|
+
base_url (str): The base URL of the APKFab website.
|
16
|
+
search_url (str): The URL used to search for APKs on APKFab.
|
17
|
+
headers (dict): The headers used for making HTTP requests.
|
18
|
+
session (requests.Session): The session object used for making HTTP requests.
|
19
|
+
|
20
|
+
Methods:
|
21
|
+
search_apk() -> None | tuple[str, str]:
|
22
|
+
Searches for the APK on APKFab and returns the title and link if found.
|
23
|
+
|
24
|
+
find_versions(apk_link: str) -> list[tuple[str, str]]:
|
25
|
+
Finds and returns a list of versions and their download links for the given APK link.
|
26
|
+
"""
|
27
|
+
|
28
|
+
def __init__(self, pkg_name: str):
|
29
|
+
self.pkg_name = pkg_name
|
30
|
+
self.base_url = "https://apkfab.com"
|
31
|
+
self.search_url = self.base_url + "/search?q="
|
32
|
+
self.headers = {
|
33
|
+
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
34
|
+
"accept-language": "en-US,en;q=0.9,en-IN;q=0.8",
|
35
|
+
"cache-control": "no-cache",
|
36
|
+
"dnt": "1",
|
37
|
+
"pragma": "no-cache",
|
38
|
+
"priority": "u=0, i",
|
39
|
+
"referer": "https://apkfab.com/search",
|
40
|
+
"sec-ch-ua": '"Microsoft Edge";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
|
41
|
+
"sec-ch-ua-mobile": "?0",
|
42
|
+
"sec-ch-ua-platform": '"Windows"',
|
43
|
+
"sec-fetch-dest": "document",
|
44
|
+
"sec-fetch-mode": "navigate",
|
45
|
+
"sec-fetch-site": "same-origin",
|
46
|
+
"sec-fetch-user": "?1",
|
47
|
+
"upgrade-insecure-requests": "1",
|
48
|
+
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
|
49
|
+
}
|
50
|
+
self.session = requests.Session()
|
51
|
+
|
52
|
+
def search_apk(self) -> None | tuple[str, str]:
|
53
|
+
"""
|
54
|
+
Searches for the APK on APKFab and returns the title and link if found.
|
55
|
+
|
56
|
+
Returns:
|
57
|
+
None: If no matching APK is found.
|
58
|
+
tuple[str, str]: A tuple containing the title and link of the matching APK if found.
|
59
|
+
"""
|
60
|
+
pkg_name = self.pkg_name
|
61
|
+
url = self.search_url + pkg_name
|
62
|
+
response: requests.Response = self.session.get(url, headers=self.headers)
|
63
|
+
soup = BeautifulSoup(response.text, "html.parser")
|
64
|
+
search_result = soup.find("div", {"class": "search-white"})
|
65
|
+
if search_result:
|
66
|
+
container = search_result.find("div", {"class": "container"})
|
67
|
+
if container:
|
68
|
+
lists = container.find("div", {"class": "list-template lists"})
|
69
|
+
if lists:
|
70
|
+
items = lists.find_all("div", {"class": "list"})
|
71
|
+
if items:
|
72
|
+
item = items[0]
|
73
|
+
apk_title = item.find("div", {"class": "title"}).text
|
74
|
+
apk_link = item.find("a")["href"]
|
75
|
+
package_name = apk_link.split("/")[-1]
|
76
|
+
if package_name == pkg_name:
|
77
|
+
return apk_title, apk_link
|
78
|
+
return None
|
79
|
+
|
80
|
+
def find_versions(self, apk_link: str) -> list[tuple[str, str]]:
|
81
|
+
"""
|
82
|
+
Finds and returns a list of versions and their download links for the given APK link.
|
83
|
+
|
84
|
+
Parameters:
|
85
|
+
apk_link (str): The link to the APK on the APKFab website.
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
list[tuple[str, str]]: A list of tuples, where each tuple contains the version number
|
89
|
+
and its corresponding download link. If no versions are found, an empty list is returned.
|
90
|
+
"""
|
91
|
+
versions_info = []
|
92
|
+
if apk_link.startswith(self.base_url):
|
93
|
+
url = apk_link + "/versions"
|
94
|
+
response: requests.Response = self.session.get(url, headers=self.headers)
|
95
|
+
soup = BeautifulSoup(response.text, "html.parser")
|
96
|
+
versions_list = soup.find("div", {"class": "version_history"})
|
97
|
+
|
98
|
+
if versions_list:
|
99
|
+
versions = versions_list.find_all("div", {"class": "list"})
|
100
|
+
if versions:
|
101
|
+
for version in versions:
|
102
|
+
package_info = version.find(
|
103
|
+
"div", {"class": "package_info open_info"}
|
104
|
+
)
|
105
|
+
if package_info:
|
106
|
+
title = package_info.find("div", {"class": "title"})
|
107
|
+
apk_version = title.find(
|
108
|
+
"span", {"class": "version"}
|
109
|
+
).text.strip()
|
110
|
+
dl = version.find(
|
111
|
+
"div", {"class": "v_h_button button_down"}
|
112
|
+
)
|
113
|
+
if dl:
|
114
|
+
download_link = dl.find("a")["href"]
|
115
|
+
versions_info.append((apk_version, download_link))
|
116
|
+
else:
|
117
|
+
# Some versions have multiple variants
|
118
|
+
versions_info.append((apk_version, url))
|
119
|
+
|
120
|
+
return versions_info
|
@@ -29,6 +29,8 @@ class APKPure:
|
|
29
29
|
def __init__(self, pkg_name: str):
|
30
30
|
self.pkg_name = pkg_name
|
31
31
|
self.base_url = "https://apkpure.net"
|
32
|
+
self.cdn_url = "https://d.cdnpure.com/b/APK/"
|
33
|
+
self.cdn_version = "?version="
|
32
34
|
self.search_url = self.base_url + "/search?q="
|
33
35
|
self.headers = {
|
34
36
|
"accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
|
@@ -72,6 +74,29 @@ class APKPure:
|
|
72
74
|
apk_package_name = apk_item["data-dt-pkg"]
|
73
75
|
if apk_package_name == pkg_name:
|
74
76
|
return apk_title, apk_link
|
77
|
+
# If site search resulted 0 results, try cdn link
|
78
|
+
# https://github.com/AbhiTheModder/apksearch/issues/2
|
79
|
+
url = self.cdn_url + pkg_name + self.cdn_version + "latest"
|
80
|
+
response: requests.Response = self.session.get(
|
81
|
+
url, headers=self.headers, allow_redirects=False
|
82
|
+
)
|
83
|
+
try:
|
84
|
+
location = response.headers.get("Location")
|
85
|
+
except AttributeError:
|
86
|
+
return None
|
87
|
+
if location:
|
88
|
+
if location == "https://apkpure.com":
|
89
|
+
return None
|
90
|
+
response: requests.Response = self.session.head(
|
91
|
+
location, allow_redirects=False
|
92
|
+
)
|
93
|
+
try:
|
94
|
+
content = response.headers.get("Content-Disposition")
|
95
|
+
except AttributeError:
|
96
|
+
return None
|
97
|
+
if content:
|
98
|
+
apk_title = content.split("filename=")[1].strip('"').split("_")[0]
|
99
|
+
return apk_title, location
|
75
100
|
return None
|
76
101
|
|
77
102
|
def find_versions(self, apk_link: str) -> list[tuple[str, str]]:
|
@@ -85,24 +110,24 @@ class APKPure:
|
|
85
110
|
list[tuple[str, str]]: A list of tuples, where each tuple contains the version number
|
86
111
|
and its corresponding download link. If no versions are found, an empty list is returned.
|
87
112
|
"""
|
88
|
-
url = apk_link + "/versions"
|
89
|
-
response: requests.Response = self.session.get(url, headers=self.headers)
|
90
|
-
soup = BeautifulSoup(response.text, "html.parser")
|
91
|
-
versions_list = soup.find("ul", {"class": "version-list"})
|
92
113
|
versions_info = []
|
114
|
+
if apk_link.startswith(self.base_url):
|
115
|
+
url = apk_link + "/versions"
|
116
|
+
response: requests.Response = self.session.get(url, headers=self.headers)
|
117
|
+
soup = BeautifulSoup(response.text, "html.parser")
|
118
|
+
versions_list = soup.find("ul", {"class": "version-list"})
|
93
119
|
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
120
|
+
if versions_list:
|
121
|
+
versions = versions_list.find_all(
|
122
|
+
"li", {"class": re.compile("^version dt-version-item.*")}
|
123
|
+
)
|
124
|
+
for ver in versions:
|
125
|
+
version_icon = ver.find("a", {"class": "dt-version-icon"})
|
126
|
+
version_info = ver.find("div", {"class": "version-info"})
|
127
|
+
if version_icon and version_info:
|
128
|
+
version_number = version_info.find(
|
129
|
+
"span", {"class": "name one-line"}
|
130
|
+
).text
|
131
|
+
download_url = self.base_url + version_icon["href"]
|
132
|
+
versions_info.append((version_number, download_url))
|
108
133
|
return versions_info
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: apksearch
|
3
|
-
Version: 1.
|
3
|
+
Version: 1.2.5
|
4
4
|
Summary: Search for apks on varius websites
|
5
5
|
Author-email: Abhi <allinoneallinone00@gmail.com>
|
6
6
|
License: MIT License
|
@@ -28,7 +28,7 @@ License: MIT License
|
|
28
28
|
Project-URL: homepage, https://github.com/AbhiTheModder/apksearch.git
|
29
29
|
Project-URL: source, https://github.com/AbhiTheModder/apksearch.git
|
30
30
|
Project-URL: issues, https://github.com/AbhiTheModder/apksearch.git/issues
|
31
|
-
Classifier: Development Status ::
|
31
|
+
Classifier: Development Status :: 5 - Production/Stable
|
32
32
|
Classifier: Natural Language :: English
|
33
33
|
Classifier: Intended Audience :: Developers
|
34
34
|
Classifier: License :: OSI Approved :: MIT License
|
@@ -63,18 +63,32 @@ There were countless occasions when I needed a specific APK for a package name,
|
|
63
63
|
- **Retrieve APK Versions and Download Links:** It can fetch available versions and their download links for a given APK from APKPure and APKMirror.
|
64
64
|
- **Command-Line Interface:** A CLI is available for users to search for APKs directly from the command line.
|
65
65
|
|
66
|
+
## Supported Websites
|
67
|
+
|
68
|
+
- [APKPure](https://apkpure.net/)
|
69
|
+
- [APKMirror](https://www.apkmirror.com/)
|
70
|
+
- [APKCombo](https://apkcombo.app/)
|
71
|
+
- [APKFab](https://apkfab.com/)
|
72
|
+
- [Appteka](https://appteka.store/)
|
73
|
+
|
74
|
+
> [!NOTE]
|
75
|
+
> **For site owners:**
|
76
|
+
> If you're the owner of a website that's not listed here and you'd like to add support for it, feel free to open an issue or submit a pull request. I'm open to adding more websites to the library.
|
77
|
+
> I respect the _value of user engagement and the revenue_ it generates for your site. To honor this, I have deliberately avoided including a download feature in the library, ensuring users will still need to visit your website, maintaining its traffic and engagement.
|
78
|
+
> Additionally, I kindly ask that you **refrain from enforcing strict blocking measures**, such as aggressive Cloudflare rules, as the library is designed to work collaboratively rather than disruptively. Thank you!
|
79
|
+
|
66
80
|
## Installation
|
67
81
|
|
68
82
|
To install the `apksearch` library, use the following command:
|
69
83
|
|
70
84
|
```sh
|
71
|
-
pip install git+https://github.com/AbhiTheModder/apksearch.git
|
85
|
+
pip install -U git+https://github.com/AbhiTheModder/apksearch.git
|
72
86
|
```
|
73
87
|
|
74
88
|
OR, through pip:
|
75
89
|
|
76
90
|
```sh
|
77
|
-
pip install apksearch
|
91
|
+
pip install -U apksearch
|
78
92
|
```
|
79
93
|
|
80
94
|
## Usage
|
@@ -117,7 +131,7 @@ if result:
|
|
117
131
|
|
118
132
|
### Classes and Methods
|
119
133
|
|
120
|
-
#### `APKPure`
|
134
|
+
#### `APKPure` | `APKCombo` | `APKFab`
|
121
135
|
|
122
136
|
- **`__init__(self, pkg_name: str)`**: Initializes with the package name.
|
123
137
|
- **`search_apk(self) -> None | tuple[str, str]`**: Searches for the APK on APKPure and returns the title and link if found.
|
@@ -146,6 +160,13 @@ pytest
|
|
146
160
|
|
147
161
|
- [ ] Add more websites to search for APKs.
|
148
162
|
|
163
|
+
## Acknowledgements
|
164
|
+
|
165
|
+
- [APKUpdater](https://github.com/rumboalla/apkupdater) for APKMirror API.
|
166
|
+
- [apkeep](https://github.com/EFForg/apkeep) for APKPure API.
|
167
|
+
|
168
|
+
**Recommendation:** If you're looking for an APK downloader, I highly recommend using [apkeep](https://github.com/EFForg/apkeep).
|
169
|
+
|
149
170
|
## License
|
150
171
|
|
151
172
|
This project is licensed under the MIT License. See the [LICENSE](https://github.com/AbhiTheModder/apksearch/blob/main/LICENSE) file for more details.
|
@@ -153,3 +174,5 @@ This project is licensed under the MIT License. See the [LICENSE](https://github
|
|
153
174
|
## Contributing
|
154
175
|
|
155
176
|
Contributions are welcome! Please open an issue or submit a pull request on [GitHub](https://github.com/AbhiTheModder/apksearch).
|
177
|
+
|
178
|
+
If you find this project helpful, please consider giving it a ⭐. Your support is greatly appreciated!
|
@@ -11,9 +11,13 @@ apksearch.egg-info/entry_points.txt
|
|
11
11
|
apksearch.egg-info/requires.txt
|
12
12
|
apksearch.egg-info/top_level.txt
|
13
13
|
apksearch/sites/__init__.py
|
14
|
+
apksearch/sites/apkcombo.py
|
15
|
+
apksearch/sites/apkfab.py
|
14
16
|
apksearch/sites/apkmirror.py
|
15
17
|
apksearch/sites/apkpure.py
|
16
18
|
apksearch/sites/appteka.py
|
19
|
+
tests/test_apkcombo.py
|
20
|
+
tests/test_apkfab.py
|
17
21
|
tests/test_apkmirror.py
|
18
22
|
tests/test_apkpure.py
|
19
23
|
tests/test_appteka.py
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "apksearch"
|
7
|
-
version = "1.
|
7
|
+
version = "1.2.5"
|
8
8
|
description = "Search for apks on varius websites"
|
9
9
|
authors = [{ name = "Abhi", email = "allinoneallinone00@gmail.com" }]
|
10
10
|
license = { file = "LICENSE" }
|
@@ -12,7 +12,7 @@ readme = "README.md"
|
|
12
12
|
requires-python = ">=3.10"
|
13
13
|
dependencies = ["beautifulsoup4>=4.12.3", "requests>=2.32.3"]
|
14
14
|
classifiers = [
|
15
|
-
"Development Status ::
|
15
|
+
"Development Status :: 5 - Production/Stable",
|
16
16
|
"Natural Language :: English",
|
17
17
|
"Intended Audience :: Developers",
|
18
18
|
"License :: OSI Approved :: MIT License",
|
@@ -0,0 +1,38 @@
|
|
1
|
+
from apksearch.sites.apkcombo import APKCombo
|
2
|
+
|
3
|
+
|
4
|
+
def test_search_apk():
|
5
|
+
query = "com.roblox.client"
|
6
|
+
apkcombo = APKCombo(query)
|
7
|
+
result = apkcombo.search_apk()
|
8
|
+
|
9
|
+
assert result is not None, "No APK found for the query."
|
10
|
+
assert isinstance(result, tuple), "Result should be a tuple."
|
11
|
+
assert len(result) == 2, "Tuple should contain two elements."
|
12
|
+
assert isinstance(result[0], str), "First element of the tuple should be a string."
|
13
|
+
assert isinstance(result[1], str), "Second element of the tuple should be a string."
|
14
|
+
|
15
|
+
|
16
|
+
def test_find_versions():
|
17
|
+
query = "com.roblox.client"
|
18
|
+
apkcombo = APKCombo(query)
|
19
|
+
result = apkcombo.search_apk()
|
20
|
+
|
21
|
+
if result:
|
22
|
+
apk_link = result[1]
|
23
|
+
versions = apkcombo.find_versions(apk_link)
|
24
|
+
|
25
|
+
assert isinstance(versions, list), "Versions should be a list."
|
26
|
+
assert len(versions) > 0, "No versions found."
|
27
|
+
assert all(
|
28
|
+
isinstance(version, tuple) for version in versions
|
29
|
+
), "Each version should be a tuple."
|
30
|
+
assert all(
|
31
|
+
len(version) == 2 for version in versions
|
32
|
+
), "Each version tuple should contain two elements."
|
33
|
+
assert all(
|
34
|
+
isinstance(version[0], str) for version in versions
|
35
|
+
), "First element of each version tuple should be a string."
|
36
|
+
assert all(
|
37
|
+
isinstance(version[1], str) for version in versions
|
38
|
+
), "Second element of each version tuple should be a string."
|
@@ -0,0 +1,38 @@
|
|
1
|
+
from apksearch.sites.apkfab import APKFab
|
2
|
+
|
3
|
+
|
4
|
+
def test_search_apk():
|
5
|
+
query = "com.roblox.client"
|
6
|
+
apkfab = APKFab(query)
|
7
|
+
result = apkfab.search_apk()
|
8
|
+
|
9
|
+
assert result is not None, "No APK found for the query."
|
10
|
+
assert isinstance(result, tuple), "Result should be a tuple."
|
11
|
+
assert len(result) == 2, "Tuple should contain two elements."
|
12
|
+
assert isinstance(result[0], str), "First element of the tuple should be a string."
|
13
|
+
assert isinstance(result[1], str), "Second element of the tuple should be a string."
|
14
|
+
|
15
|
+
|
16
|
+
def test_find_versions():
|
17
|
+
query = "com.roblox.client"
|
18
|
+
apkfab = APKFab(query)
|
19
|
+
result = apkfab.search_apk()
|
20
|
+
|
21
|
+
if result:
|
22
|
+
apk_link = result[1]
|
23
|
+
versions = apkfab.find_versions(apk_link)
|
24
|
+
|
25
|
+
assert isinstance(versions, list), "Versions should be a list."
|
26
|
+
assert len(versions) > 0, "No versions found."
|
27
|
+
assert all(
|
28
|
+
isinstance(version, tuple) for version in versions
|
29
|
+
), "Each version should be a tuple."
|
30
|
+
assert all(
|
31
|
+
len(version) == 2 for version in versions
|
32
|
+
), "Each version tuple should contain two elements."
|
33
|
+
assert all(
|
34
|
+
isinstance(version[0], str) for version in versions
|
35
|
+
), "First element of each version tuple should be a string."
|
36
|
+
assert all(
|
37
|
+
isinstance(version[1], str) for version in versions
|
38
|
+
), "Second element of each version tuple should be a string."
|
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
|