apksearch 1.2.0__py3-none-any.whl → 1.2.6__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- apksearch/__init__.py +3 -1
- apksearch/cli.py +82 -14
- apksearch/sites/apkad.py +109 -0
- apksearch/sites/apkcombo.py +6 -3
- apksearch/sites/apkfab.py +120 -0
- apksearch/sites/apkpure.py +43 -18
- {apksearch-1.2.0.dist-info → apksearch-1.2.6.dist-info}/METADATA +40 -15
- apksearch-1.2.6.dist-info/RECORD +16 -0
- {apksearch-1.2.0.dist-info → apksearch-1.2.6.dist-info}/WHEEL +1 -1
- apksearch-1.2.0.dist-info/RECORD +0 -14
- {apksearch-1.2.0.dist-info → apksearch-1.2.6.dist-info}/LICENSE +0 -0
- {apksearch-1.2.0.dist-info → apksearch-1.2.6.dist-info}/entry_points.txt +0 -0
- {apksearch-1.2.0.dist-info → apksearch-1.2.6.dist-info}/top_level.txt +0 -0
apksearch/__init__.py
CHANGED
@@ -2,5 +2,7 @@ from .sites.apkpure import APKPure
|
|
2
2
|
from .sites.apkmirror import APKMirror
|
3
3
|
from .sites.appteka import AppTeka
|
4
4
|
from .sites.apkcombo import APKCombo
|
5
|
+
from .sites.apkfab import APKFab
|
6
|
+
from .sites.apkad import APKAD
|
5
7
|
|
6
|
-
__all__ = ["APKPure", "APKMirror", "AppTeka", "APKCombo"]
|
8
|
+
__all__ = ["APKPure", "APKMirror", "AppTeka", "APKCombo", "APKFab", "APKAD"]
|
apksearch/cli.py
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
import argparse
|
2
2
|
|
3
|
-
from
|
3
|
+
from collections.abc import Callable
|
4
|
+
from apksearch import APKPure, APKMirror, AppTeka, APKCombo, APKFab, APKAD
|
4
5
|
from requests.exceptions import ConnectionError, ConnectTimeout
|
5
6
|
|
6
7
|
# Color codes
|
@@ -11,6 +12,21 @@ YELLOW = "\033[93m"
|
|
11
12
|
NC = "\033[0m"
|
12
13
|
|
13
14
|
|
15
|
+
def search(
|
16
|
+
func: Callable[[str, str], object],
|
17
|
+
pkg_name: str,
|
18
|
+
version: str | None,
|
19
|
+
log_err: bool = False,
|
20
|
+
) -> None:
|
21
|
+
try:
|
22
|
+
func(pkg_name, version)
|
23
|
+
except Exception as exc:
|
24
|
+
if log_err:
|
25
|
+
print(f"Error in {func.__name__}: {exc}")
|
26
|
+
else:
|
27
|
+
pass
|
28
|
+
|
29
|
+
|
14
30
|
def search_apkpure(pkg_name: str, version: str | None) -> None:
|
15
31
|
apkpure = APKPure(pkg_name)
|
16
32
|
try:
|
@@ -22,20 +38,64 @@ def search_apkpure(pkg_name: str, version: str | None) -> None:
|
|
22
38
|
title, apk_link = result_apkpure
|
23
39
|
print(f"{BOLD}APKPure:{NC} Found {GREEN}{title}{NC}") if title else None
|
24
40
|
print(f" ╰─> {BOLD}Link: {YELLOW}{apk_link}{NC}") if not version else None
|
25
|
-
versions: list[tuple[str, str]] = apkpure.find_versions(apk_link)
|
26
41
|
if version:
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
42
|
+
versions: list[tuple[str, str]] = apkpure.find_versions(apk_link)
|
43
|
+
if versions:
|
44
|
+
for version_tuple in versions:
|
45
|
+
if version_tuple[0] == version:
|
46
|
+
print(
|
47
|
+
f" ╰─> {BOLD}Version: {GREEN}{version}{NC} - {YELLOW}{version_tuple[1]}{NC}"
|
48
|
+
)
|
49
|
+
break
|
50
|
+
else:
|
51
|
+
print(f"{BOLD}APKPure:{NC} Version {RED}{version}{NC} not found!")
|
35
52
|
else:
|
36
53
|
print(f"{BOLD}APKPure:{NC} No Results!")
|
37
54
|
|
38
55
|
|
56
|
+
def search_apkfab(pkg_name: str, version: str | None) -> None:
|
57
|
+
apkfab = APKFab(pkg_name)
|
58
|
+
try:
|
59
|
+
result_apkfab: tuple[str, str] | None = apkfab.search_apk()
|
60
|
+
except (ConnectionError, ConnectTimeout):
|
61
|
+
result_apkfab = None
|
62
|
+
print(f"{RED}Failed to resolve 'apkfab.com'!{NC}")
|
63
|
+
if result_apkfab:
|
64
|
+
title, apk_link = result_apkfab
|
65
|
+
print(f"{BOLD}APKFab:{NC} Found {GREEN}{title}{NC}") if title else None
|
66
|
+
print(f" ╰─> {BOLD}Link: {YELLOW}{apk_link}{NC}") if not version else None
|
67
|
+
if version:
|
68
|
+
versions: list[tuple[str, str]] = apkfab.find_versions(apk_link)
|
69
|
+
if versions:
|
70
|
+
for version_tuple in versions:
|
71
|
+
if version_tuple[0] == version:
|
72
|
+
print(
|
73
|
+
f" ╰─> {BOLD}Version: {GREEN}{version}{NC} - {YELLOW}{version_tuple[1]}{NC}"
|
74
|
+
)
|
75
|
+
break
|
76
|
+
else:
|
77
|
+
print(f"{BOLD}APKFab:{NC} Version {RED}{version}{NC} not found!")
|
78
|
+
else:
|
79
|
+
print(f"{BOLD}APKFab:{NC} No Results!")
|
80
|
+
|
81
|
+
|
82
|
+
def search_apkad(pkg_name: str, version: str | None) -> None:
|
83
|
+
apkad = APKAD(pkg_name)
|
84
|
+
try:
|
85
|
+
result_apkad: tuple[str, str] | None = apkad.search_apk()
|
86
|
+
except (ConnectionError, ConnectTimeout):
|
87
|
+
result_apkad = None
|
88
|
+
print(f"{RED}Failed to resolve 'api.apk.ad'!{NC}")
|
89
|
+
if result_apkad:
|
90
|
+
title, apk_link = result_apkad
|
91
|
+
print(f"{BOLD}APKAD:{NC} Found {GREEN}{title}{NC}") if title else None
|
92
|
+
print(
|
93
|
+
f" ╰─> {BOLD}Link: {YELLOW}{apk_link}{NC}"
|
94
|
+
) if not version else print(" ╰─> Doesn't support version search!")
|
95
|
+
else:
|
96
|
+
print(f"{BOLD}APKAD:{NC} No Results!")
|
97
|
+
|
98
|
+
|
39
99
|
def search_apkcombo(pkg_name: str, version: str | None) -> None:
|
40
100
|
apkcombo = APKCombo(pkg_name)
|
41
101
|
try:
|
@@ -113,19 +173,27 @@ def main():
|
|
113
173
|
)
|
114
174
|
parser.add_argument("pkg_name", help="The package name of the APK")
|
115
175
|
parser.add_argument("--version", help="The version of the APK", required=False)
|
176
|
+
parser.add_argument(
|
177
|
+
"--log_err", help="Enable error logs", action="store_true", required=False
|
178
|
+
)
|
116
179
|
args = parser.parse_args()
|
117
180
|
|
118
181
|
pkg_name = args.pkg_name
|
119
182
|
version = args.version
|
183
|
+
log_err = args.log_err
|
120
184
|
print(f"{BOLD}Searching for {YELLOW}{pkg_name}{NC}...")
|
121
185
|
# Initiate search on apkpure
|
122
|
-
search_apkpure
|
186
|
+
search(search_apkpure, pkg_name, version, log_err)
|
123
187
|
# Initiate search on apkmirror
|
124
|
-
search_apkmirror
|
188
|
+
search(search_apkmirror, pkg_name, version, log_err)
|
125
189
|
# Initiate search on appteka
|
126
|
-
search_appteka
|
190
|
+
search(search_appteka, pkg_name, version, log_err)
|
127
191
|
# Initiate search on apkcombo
|
128
|
-
search_apkcombo
|
192
|
+
search(search_apkcombo, pkg_name, version, log_err)
|
193
|
+
# Initiate search on apkfab
|
194
|
+
search(search_apkfab, pkg_name, version, log_err)
|
195
|
+
# Initiate search on apkad
|
196
|
+
search(search_apkad, pkg_name, version, log_err)
|
129
197
|
|
130
198
|
|
131
199
|
if __name__ == "__main__":
|
apksearch/sites/apkad.py
ADDED
@@ -0,0 +1,109 @@
|
|
1
|
+
import json
|
2
|
+
from bs4 import BeautifulSoup
|
3
|
+
import requests
|
4
|
+
|
5
|
+
|
6
|
+
class APKAD:
|
7
|
+
"""
|
8
|
+
This class provides methods to search for an APK on APKAD based on package name,
|
9
|
+
and to find available versions and their download links for a given APK link.
|
10
|
+
|
11
|
+
Parameters:
|
12
|
+
pkg_name (str): The package name of the APK to search for.
|
13
|
+
|
14
|
+
Attributes:
|
15
|
+
pkg_name (str): The package name of the APK to search for.
|
16
|
+
base_url (str): The base URL of the APKAD website.
|
17
|
+
search_url (str): The URL used to search for APKs on APKAD.
|
18
|
+
headers (dict): The headers used for making HTTP requests.
|
19
|
+
session (requests.Session): The session object used for making HTTP requests.
|
20
|
+
|
21
|
+
Methods:
|
22
|
+
search_apk() -> None | tuple[str, str]:
|
23
|
+
Searches for the APK on APKAD and returns the title and link if found.
|
24
|
+
"""
|
25
|
+
|
26
|
+
def __init__(self, pkg_name: str):
|
27
|
+
self.pkg_name = pkg_name
|
28
|
+
self.base_url = "https://downloader.apk.ad"
|
29
|
+
self.api_url = "https://api.apk.ad"
|
30
|
+
self.search_url = (
|
31
|
+
self.api_url
|
32
|
+
+ f"/get?hl=en&package={self.pkg_name}&device=phone&arch=arm64-v8a&vc=&device_id="
|
33
|
+
)
|
34
|
+
self.headers = {
|
35
|
+
"accept": "text/event-stream",
|
36
|
+
"accept-language": "en-US,en;q=0.9,en-IN;q=0.8",
|
37
|
+
"cache-control": "no-cache",
|
38
|
+
"dnt": "1",
|
39
|
+
"origin": "https://downloader.apk.ad",
|
40
|
+
"pragma": "no-cache",
|
41
|
+
"priority": "u=1, i",
|
42
|
+
"referer": "https://downloader.apk.ad/",
|
43
|
+
"sec-ch-ua": '"Microsoft Edge";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
|
44
|
+
"sec-ch-ua-mobile": "?0",
|
45
|
+
"sec-ch-ua-platform": '"Windows"',
|
46
|
+
"sec-fetch-dest": "empty",
|
47
|
+
"sec-fetch-mode": "cors",
|
48
|
+
"sec-fetch-site": "same-site",
|
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 APKAD 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
|
+
url = self.search_url
|
62
|
+
response = self.session.get(url, headers=self.headers, stream=True)
|
63
|
+
stream_response = ""
|
64
|
+
for line in response.iter_lines():
|
65
|
+
if line:
|
66
|
+
line_response = line.decode("utf-8")
|
67
|
+
if '"progress":100' in line_response:
|
68
|
+
line_response = line_response[6:]
|
69
|
+
stream_response += line_response
|
70
|
+
break
|
71
|
+
if stream_response:
|
72
|
+
data = json.loads(stream_response)
|
73
|
+
html_body = data["html"]
|
74
|
+
soup = BeautifulSoup(html_body, "html.parser")
|
75
|
+
if not soup:
|
76
|
+
return None
|
77
|
+
title = soup.find("li", {"class": "_title"})
|
78
|
+
if title:
|
79
|
+
title = title.text.strip()
|
80
|
+
button = soup.find(
|
81
|
+
"button", {"onclick": True, "id": "downloadButtonapk"}
|
82
|
+
)["onclick"]
|
83
|
+
if button:
|
84
|
+
zip_args = [
|
85
|
+
arg.strip("'")
|
86
|
+
for arg in button.split("zip(")[1].split(")")[0].split(",")
|
87
|
+
]
|
88
|
+
h = zip_args[0] # hash
|
89
|
+
p = zip_args[-1] # type
|
90
|
+
token = zip_args[1] # token
|
91
|
+
ip = zip_args[2] # ip
|
92
|
+
google_id = zip_args[3] # package_name
|
93
|
+
t = zip_args[4] # time
|
94
|
+
apk_url = f"https://zip.apk.ad/compress?h={h}&p={p}&token={token}&ip={ip}&google_id={google_id}&t={t}"
|
95
|
+
apk_url_response = self.session.get(
|
96
|
+
url=apk_url, headers=self.headers, stream=True
|
97
|
+
)
|
98
|
+
for line in apk_url_response.iter_lines():
|
99
|
+
if line:
|
100
|
+
line_response = line.decode("utf-8")
|
101
|
+
if "File is ready for download." in line_response:
|
102
|
+
line_response = json.loads(line_response)
|
103
|
+
line_html = line_response["html"]
|
104
|
+
line_soup = BeautifulSoup(line_html, "html.parser")
|
105
|
+
download_link = line_soup.find("a")["href"]
|
106
|
+
if download_link.endswith("\n"):
|
107
|
+
download_link = download_link[:-1]
|
108
|
+
return title, download_link
|
109
|
+
return None
|
apksearch/sites/apkcombo.py
CHANGED
@@ -28,7 +28,7 @@ class APKCombo:
|
|
28
28
|
def __init__(self, pkg_name: str):
|
29
29
|
self.pkg_name = pkg_name
|
30
30
|
self.base_url = "https://apkcombo.app"
|
31
|
-
self.search_url = self.base_url + "/search
|
31
|
+
self.search_url = self.base_url + "/search"
|
32
32
|
self.headers = {
|
33
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
34
|
"accept-language": "en-US,en;q=0.9,en-IN;q=0.8",
|
@@ -59,7 +59,7 @@ class APKCombo:
|
|
59
59
|
tuple[str, str]: A tuple containing the title and link of the matching APK if found.
|
60
60
|
"""
|
61
61
|
pkg_name = self.pkg_name
|
62
|
-
url = self.search_url + pkg_name
|
62
|
+
url = self.search_url + "/" + pkg_name
|
63
63
|
response: requests.Response = self.session.get(
|
64
64
|
url, headers=self.headers, allow_redirects=False
|
65
65
|
)
|
@@ -72,7 +72,10 @@ class APKCombo:
|
|
72
72
|
else:
|
73
73
|
raise Exception(f"Error: {response.status_code}")
|
74
74
|
soup = BeautifulSoup(response.text, "html.parser")
|
75
|
-
|
75
|
+
try:
|
76
|
+
title = soup.find("div", {"class": "app_name"}).text.strip()
|
77
|
+
except AttributeError:
|
78
|
+
return None
|
76
79
|
apk_link = url
|
77
80
|
return title, apk_link
|
78
81
|
|
@@ -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
|
apksearch/sites/apkpure.py
CHANGED
@@ -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
|
-
Metadata-Version: 2.
|
1
|
+
Metadata-Version: 2.2
|
2
2
|
Name: apksearch
|
3
|
-
Version: 1.2.
|
3
|
+
Version: 1.2.6
|
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
|
@@ -50,31 +50,55 @@ Requires-Dist: pytest>=7.4.3; extra == "dev"
|
|
50
50
|
Requires-Dist: black>=23.12.1; extra == "dev"
|
51
51
|
Requires-Dist: flake8>=6.1.0; extra == "dev"
|
52
52
|
|
53
|
-
|
53
|
+
<h1 align="center">apksearch</h1>
|
54
54
|
|
55
55
|
`apksearch` is a Python library designed to search for APK files on different APK websites, such as APKPure and APKMirror. It allows users to find APKs, check for available versions, and retrieve download links.
|
56
56
|
|
57
57
|
**The Inspiration:**
|
58
58
|
There were countless occasions when I needed a specific APK for a package name, only to find it unavailable on popular platforms. This led to the tedious task of manually visiting multiple websites and searching one by one.
|
59
|
+
<details>
|
60
|
+
<summary>screenshot</summary>
|
61
|
+
<p align="center">
|
62
|
+
<img width="500" src="https://github.com/user-attachments/assets/cd54eaeb-a56b-40b3-835f-b48b1e7772f3"></img><br>
|
63
|
+
As you can see, Roblox version <code>2.647.716</code> is not available on APKPure and APKCombo, this helped me avoid going through these sites.
|
64
|
+
</p>
|
65
|
+
</details>
|
66
|
+
|
67
|
+
**P.S:** If you're looking for an APK downloader, I highly recommend using [apkeep](https://github.com/EFForg/apkeep).
|
59
68
|
|
60
69
|
# Features
|
61
70
|
|
62
71
|
- **Search APKs:** The library provides methods to search for APKs using package names.
|
63
|
-
- **Retrieve APK Versions and Download Links:** It can fetch available versions and their download links for a given APK from
|
72
|
+
- **Retrieve APK Versions and Download Links:** It can fetch available versions and their download links for a given APK from various websites.
|
64
73
|
- **Command-Line Interface:** A CLI is available for users to search for APKs directly from the command line.
|
65
74
|
|
75
|
+
## Supported Websites
|
76
|
+
|
77
|
+
- [APKPure](https://apkpure.net/)
|
78
|
+
- [APKMirror](https://www.apkmirror.com/)
|
79
|
+
- [APKCombo](https://apkcombo.app/)
|
80
|
+
- [APKFab](https://apkfab.com/)
|
81
|
+
- [Appteka](https://appteka.store/)
|
82
|
+
- [APKAD](https://apk.ad/)
|
83
|
+
|
84
|
+
> [!NOTE]
|
85
|
+
> **For site owners:**
|
86
|
+
> 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.
|
87
|
+
> 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.
|
88
|
+
> 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!
|
89
|
+
|
66
90
|
## Installation
|
67
91
|
|
68
92
|
To install the `apksearch` library, use the following command:
|
69
93
|
|
70
94
|
```sh
|
71
|
-
pip install git+https://github.com/AbhiTheModder/apksearch.git
|
95
|
+
pip install -U git+https://github.com/AbhiTheModder/apksearch.git
|
72
96
|
```
|
73
97
|
|
74
98
|
OR, through pip:
|
75
99
|
|
76
100
|
```sh
|
77
|
-
pip install apksearch
|
101
|
+
pip install -U apksearch
|
78
102
|
```
|
79
103
|
|
80
104
|
## Usage
|
@@ -117,7 +141,7 @@ if result:
|
|
117
141
|
|
118
142
|
### Classes and Methods
|
119
143
|
|
120
|
-
#### `APKPure`
|
144
|
+
#### `APKPure` | `APKCombo` | `APKFab`
|
121
145
|
|
122
146
|
- **`__init__(self, pkg_name: str)`**: Initializes with the package name.
|
123
147
|
- **`search_apk(self) -> None | tuple[str, str]`**: Searches for the APK on APKPure and returns the title and link if found.
|
@@ -129,17 +153,11 @@ if result:
|
|
129
153
|
- **`search_apk(self) -> None | tuple[str, str]`**: Searches for the APK on APKMirror and returns the title and link if found.
|
130
154
|
- **`find_version(self, apk_link: str, version: str) -> str`**: Finds and returns the download link for the given APK link and version.
|
131
155
|
|
132
|
-
#### `AppTeka`
|
156
|
+
#### `AppTeka` | `APKAD`
|
133
157
|
|
134
158
|
- **`__init__(self, pkg_name: str)`**: Initializes with the package name.
|
135
159
|
- **`search_apk(self, version: str = None) -> None | tuple[str, str]`**: Searches for the APK on AppTeka and returns the title and link if found. If a version is provided, it checks if that version is available and returns the corresponding download link, None otherwise. If no version is provided, it returns the link for the latest version available.
|
136
160
|
|
137
|
-
#### `APKCombo`
|
138
|
-
|
139
|
-
- **`__init__(self, pkg_name: str)`**: Initializes with the package name.
|
140
|
-
- **`search_apk(self) -> None | tuple[str, str]`**: Searches for the APK on APKCombo and returns the title and link if found.
|
141
|
-
- **`find_versions(self, apk_link: str) -> list[tuple[str, str]]`**: Finds and returns a list of versions and their download links for the given APK link.
|
142
|
-
|
143
161
|
### Testing
|
144
162
|
|
145
163
|
The project includes tests for the `sites` classes. To run the tests, use the following command:
|
@@ -152,6 +170,11 @@ pytest
|
|
152
170
|
|
153
171
|
- [ ] Add more websites to search for APKs.
|
154
172
|
|
173
|
+
## Acknowledgements
|
174
|
+
|
175
|
+
- [APKUpdater](https://github.com/rumboalla/apkupdater) for APKMirror API.
|
176
|
+
- [apkeep](https://github.com/EFForg/apkeep) for APKPure API.
|
177
|
+
|
155
178
|
## License
|
156
179
|
|
157
180
|
This project is licensed under the MIT License. See the [LICENSE](https://github.com/AbhiTheModder/apksearch/blob/main/LICENSE) file for more details.
|
@@ -159,3 +182,5 @@ This project is licensed under the MIT License. See the [LICENSE](https://github
|
|
159
182
|
## Contributing
|
160
183
|
|
161
184
|
Contributions are welcome! Please open an issue or submit a pull request on [GitHub](https://github.com/AbhiTheModder/apksearch).
|
185
|
+
|
186
|
+
If you find this project helpful, please consider giving it a ⭐. Your support is greatly appreciated!
|
@@ -0,0 +1,16 @@
|
|
1
|
+
apksearch/__init__.py,sha256=ktoWOmWE68fx36Wz8DZWu6E85FXTVjBEuJtvqFxqsI8,288
|
2
|
+
apksearch/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
|
3
|
+
apksearch/cli.py,sha256=KjN9UWX_Qt5au05wDJfzUJqmOLnL8-kSI_FzRubrCUk,7670
|
4
|
+
apksearch/sites/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
+
apksearch/sites/apkad.py,sha256=ny7LxUU7vS4o4DT4-uT5SZDgoUd5DnZdMqgXcFYO_ME,4811
|
6
|
+
apksearch/sites/apkcombo.py,sha256=AiJhSb0subQLb51dr8MQaxgeEZ77wI99InfigYfrcl4,4718
|
7
|
+
apksearch/sites/apkfab.py,sha256=5f0fOBmQk0E30NcVE2cF80soJqI5ziOukU6_xMPtgFQ,5502
|
8
|
+
apksearch/sites/apkmirror.py,sha256=IpS6r7ovM3g2dZrkR_a9zQpHy-jqrU2nJ3ykVkWxers,3127
|
9
|
+
apksearch/sites/apkpure.py,sha256=7IUY4u6q6ga_8CFQ1DCaeU_WS2xE70T_7PUU0o20NCk,5941
|
10
|
+
apksearch/sites/appteka.py,sha256=in04JYF043n0fUvBW1QQeXnNbYekGUIh4qeame951nA,6372
|
11
|
+
apksearch-1.2.6.dist-info/LICENSE,sha256=Icu9iAY9cAaraq-HaAk8aWpXS1nE-3U_wfaOhN-HQUw,1061
|
12
|
+
apksearch-1.2.6.dist-info/METADATA,sha256=pK9nEbUbjpcsfvf6WaTvrU_K1btWT29P6QfVFjbdau8,7699
|
13
|
+
apksearch-1.2.6.dist-info/WHEEL,sha256=In9FTNxeP60KnTkGw7wk6mJPYd_dQSjEZmXdBdMCI-8,91
|
14
|
+
apksearch-1.2.6.dist-info/entry_points.txt,sha256=FeAPgNPSU1tCwQNaKAmk6eQYbMHtsltcgeWSGUTxu0k,49
|
15
|
+
apksearch-1.2.6.dist-info/top_level.txt,sha256=VguZMODhlXWwDyJJNJms5syJ4EHmWSQOS45J9I5Cv5o,10
|
16
|
+
apksearch-1.2.6.dist-info/RECORD,,
|
apksearch-1.2.0.dist-info/RECORD
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
apksearch/__init__.py,sha256=7gSvDwU4DieNIkOHP3m8rnpFt_54XitPiWBzuZ-kkf4,205
|
2
|
-
apksearch/__main__.py,sha256=MSmt_5Xg84uHqzTN38JwgseJK8rsJn_11A8WD99VtEo,61
|
3
|
-
apksearch/cli.py,sha256=iQe1riJhBUjByyb1WBgbiSxN_WHvOLATwUZkxU4X0Gs,5079
|
4
|
-
apksearch/sites/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
5
|
-
apksearch/sites/apkcombo.py,sha256=zhlbsD6I97QTWFRTR1aOzM7gGW9_--qwJ1kUHSp8_Ik,4641
|
6
|
-
apksearch/sites/apkmirror.py,sha256=IpS6r7ovM3g2dZrkR_a9zQpHy-jqrU2nJ3ykVkWxers,3127
|
7
|
-
apksearch/sites/apkpure.py,sha256=oUH-D1Xh04Bdb9eWQlX74B8OR3wm9lOEWqvvCC0-dUo,4785
|
8
|
-
apksearch/sites/appteka.py,sha256=in04JYF043n0fUvBW1QQeXnNbYekGUIh4qeame951nA,6372
|
9
|
-
apksearch-1.2.0.dist-info/LICENSE,sha256=Icu9iAY9cAaraq-HaAk8aWpXS1nE-3U_wfaOhN-HQUw,1061
|
10
|
-
apksearch-1.2.0.dist-info/METADATA,sha256=99vlfqVCjL8L2jB23uaaKCrMG7lhE8bgSyi64N9ynJs,6309
|
11
|
-
apksearch-1.2.0.dist-info/WHEEL,sha256=PZUExdf71Ui_so67QXpySuHtCi3-J3wvF4ORK6k_S8U,91
|
12
|
-
apksearch-1.2.0.dist-info/entry_points.txt,sha256=FeAPgNPSU1tCwQNaKAmk6eQYbMHtsltcgeWSGUTxu0k,49
|
13
|
-
apksearch-1.2.0.dist-info/top_level.txt,sha256=VguZMODhlXWwDyJJNJms5syJ4EHmWSQOS45J9I5Cv5o,10
|
14
|
-
apksearch-1.2.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|