apksearch 1.3.1__tar.gz → 1.3.5__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.
Files changed (29) hide show
  1. {apksearch-1.3.1 → apksearch-1.3.5}/PKG-INFO +3 -1
  2. {apksearch-1.3.1 → apksearch-1.3.5}/apksearch/cli.py +53 -29
  3. apksearch-1.3.5/apksearch/sites/__init__.py +10 -0
  4. apksearch-1.3.5/apksearch/sites/apkad.py +142 -0
  5. {apksearch-1.3.1 → apksearch-1.3.5}/apksearch/sites/apkcombo.py +4 -3
  6. {apksearch-1.3.1 → apksearch-1.3.5}/apksearch/sites/apkfab.py +6 -2
  7. {apksearch-1.3.1 → apksearch-1.3.5}/apksearch/sites/apkmirror.py +6 -3
  8. {apksearch-1.3.1 → apksearch-1.3.5}/apksearch/sites/apkpure.py +4 -2
  9. {apksearch-1.3.1 → apksearch-1.3.5}/apksearch/sites/appteka.py +4 -2
  10. {apksearch-1.3.1 → apksearch-1.3.5}/apksearch/sites/aptoide.py +3 -1
  11. {apksearch-1.3.1 → apksearch-1.3.5}/apksearch.egg-info/PKG-INFO +3 -1
  12. {apksearch-1.3.1 → apksearch-1.3.5}/apksearch.egg-info/requires.txt +3 -0
  13. {apksearch-1.3.1 → apksearch-1.3.5}/pyproject.toml +2 -1
  14. {apksearch-1.3.1 → apksearch-1.3.5}/tests/test_apkfab.py +12 -12
  15. {apksearch-1.3.1 → apksearch-1.3.5}/tests/test_apkmirror.py +7 -5
  16. {apksearch-1.3.1 → apksearch-1.3.5}/tests/test_apkpure.py +12 -12
  17. {apksearch-1.3.1 → apksearch-1.3.5}/tests/test_aptoide.py +3 -3
  18. apksearch-1.3.1/apksearch/sites/__init__.py +0 -0
  19. apksearch-1.3.1/apksearch/sites/apkad.py +0 -109
  20. {apksearch-1.3.1 → apksearch-1.3.5}/LICENSE +0 -0
  21. {apksearch-1.3.1 → apksearch-1.3.5}/README.md +0 -0
  22. {apksearch-1.3.1 → apksearch-1.3.5}/apksearch/__init__.py +4 -4
  23. {apksearch-1.3.1 → apksearch-1.3.5}/apksearch/__main__.py +0 -0
  24. {apksearch-1.3.1 → apksearch-1.3.5}/apksearch.egg-info/SOURCES.txt +0 -0
  25. {apksearch-1.3.1 → apksearch-1.3.5}/apksearch.egg-info/dependency_links.txt +0 -0
  26. {apksearch-1.3.1 → apksearch-1.3.5}/apksearch.egg-info/entry_points.txt +0 -0
  27. {apksearch-1.3.1 → apksearch-1.3.5}/apksearch.egg-info/top_level.txt +0 -0
  28. {apksearch-1.3.1 → apksearch-1.3.5}/setup.cfg +0 -0
  29. {apksearch-1.3.1 → apksearch-1.3.5}/tests/test_appteka.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apksearch
3
- Version: 1.3.1
3
+ Version: 1.3.5
4
4
  Summary: Search for apks on varius websites
5
5
  Author-email: Abhi <allinoneallinone00@gmail.com>
6
6
  License: MIT License
@@ -49,6 +49,8 @@ Provides-Extra: dev
49
49
  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
+ Provides-Extra: curl
53
+ Requires-Dist: curl_cffi>=0.12.1b1; extra == "curl"
52
54
  Dynamic: license-file
53
55
 
54
56
  <h1 align="center">apksearch</h1>
@@ -1,16 +1,17 @@
1
1
  import argparse
2
-
3
2
  from collections.abc import Callable
3
+
4
+ from requests.exceptions import ConnectionError, ConnectTimeout
5
+
4
6
  from apksearch import (
5
- APKPure,
6
- APKMirror,
7
- AppTeka,
7
+ APKad,
8
8
  APKCombo,
9
9
  APKFab,
10
- APKad,
10
+ APKMirror,
11
+ APKPure,
12
+ AppTeka,
11
13
  Aptoide,
12
14
  )
13
- from requests.exceptions import ConnectionError, ConnectTimeout
14
15
 
15
16
  # Color codes
16
17
  BOLD = "\033[1m"
@@ -21,7 +22,7 @@ NC = "\033[0m"
21
22
 
22
23
 
23
24
  def search(
24
- func: Callable[[str, str], object],
25
+ func: Callable[[str, str | None], object],
25
26
  pkg_name: str,
26
27
  version: str | None,
27
28
  log_err: bool = False,
@@ -30,7 +31,8 @@ def search(
30
31
  func(pkg_name, version)
31
32
  except Exception as exc:
32
33
  if log_err:
33
- print(f"Error in {func.__name__}: {exc}")
34
+ func_name = getattr(func, "__name__", func.__class__.__name__)
35
+ print(f"Error in {func_name}: {exc}")
34
36
  else:
35
37
  pass
36
38
 
@@ -90,16 +92,20 @@ def search_apkfab(pkg_name: str, version: str | None) -> None:
90
92
  def search_apkad(pkg_name: str, version: str | None) -> None:
91
93
  apkad = APKad(pkg_name)
92
94
  try:
93
- result_apkad: tuple[str, str] | None = apkad.search_apk()
95
+ result_apkad: tuple[str, list[tuple[str, str]]] | None = apkad.search_apk()
94
96
  except (ConnectionError, ConnectTimeout):
95
97
  result_apkad = None
96
98
  print(f"{RED}Failed to resolve 'api.apk.ad'!{NC}")
97
99
  if result_apkad:
98
- title, apk_link = result_apkad
100
+ title, apk_links = result_apkad
99
101
  print(f"{BOLD}APKAD:{NC} Found {GREEN}{title}{NC}") if title else None
100
- print(
101
- f" ╰─> {BOLD}Link: {YELLOW}{apk_link}{NC}"
102
- ) if not version else print(" ╰─> Doesn't support version search!")
102
+ if not version:
103
+ print(f" ╰─> {BOLD}Available APKs:{NC}")
104
+ for i, (filename, url) in enumerate(apk_links, 1):
105
+ print(f" {i}. {filename}")
106
+ print(f" ╰─> {BOLD}Download:{NC} {YELLOW}{url}{NC}")
107
+ else:
108
+ print(" ╰─> Doesn't support version search!")
103
109
  else:
104
110
  print(f"{BOLD}APKAD:{NC} No Results!")
105
111
 
@@ -141,7 +147,7 @@ def search_apkmirror(pkg_name: str, version: str | None) -> None:
141
147
  print(f"{BOLD}APKMirror:{NC} Found {GREEN}{title}{NC}") if title else None
142
148
  print(f" ╰─> {BOLD}Link: {YELLOW}{apk_link}{NC}") if not version else None
143
149
  if version:
144
- download_link = apkmirror.find_version(apk_link, version)
150
+ download_link = apkmirror.find_version(apk_link, version, title)
145
151
  if download_link:
146
152
  print(
147
153
  f" ╰─> {BOLD}Version: {GREEN}{version}{NC} - {YELLOW}{download_link}{NC}"
@@ -155,7 +161,7 @@ def search_apkmirror(pkg_name: str, version: str | None) -> None:
155
161
  def search_appteka(pkg_name: str, version: str | None) -> None:
156
162
  appteka = AppTeka(pkg_name)
157
163
  try:
158
- result_appteka: tuple[str, str] | None = appteka.search_apk(version)
164
+ result_appteka: tuple[str, str | None] | None = appteka.search_apk(version)
159
165
  except (ConnectionError, ConnectTimeout):
160
166
  result_appteka = None
161
167
  print(f"{RED}Failed to resolve 'appteka.store'!{NC}")
@@ -208,26 +214,44 @@ def main():
208
214
  parser.add_argument(
209
215
  "--log_err", help="Enable error logs", action="store_true", required=False
210
216
  )
217
+ parser.add_argument(
218
+ "--sites",
219
+ nargs="+",
220
+ choices=[
221
+ "apkpure",
222
+ "apkmirror",
223
+ "aptoide",
224
+ "appteka",
225
+ "apkcombo",
226
+ "apkfab",
227
+ "apkad",
228
+ ],
229
+ help="Specify the sites to search on",
230
+ required=False,
231
+ )
211
232
  args = parser.parse_args()
212
233
 
213
234
  pkg_name = args.pkg_name
214
235
  version = args.version
215
236
  log_err = args.log_err
237
+ sites = args.sites
238
+
216
239
  print(f"{BOLD}Searching for {YELLOW}{pkg_name}{NC}...")
217
- # Initiate search on apkpure
218
- search(search_apkpure, pkg_name, version, log_err)
219
- # Initiate search on apkmirror
220
- search(search_apkmirror, pkg_name, version, log_err)
221
- # Initiate search on aptoide
222
- search(search_aptoide, pkg_name, version, log_err)
223
- # Initiate search on appteka
224
- search(search_appteka, pkg_name, version, log_err)
225
- # Initiate search on apkcombo
226
- search(search_apkcombo, pkg_name, version, log_err)
227
- # Initiate search on apkfab
228
- search(search_apkfab, pkg_name, version, log_err)
229
- # Initiate search on apkad
230
- search(search_apkad, pkg_name, version, log_err)
240
+
241
+ if not sites or "apkpure" in sites:
242
+ search(search_apkpure, pkg_name, version, log_err)
243
+ if not sites or "apkmirror" in sites:
244
+ search(search_apkmirror, pkg_name, version, log_err)
245
+ if not sites or "aptoide" in sites:
246
+ search(search_aptoide, pkg_name, version, log_err)
247
+ if not sites or "appteka" in sites:
248
+ search(search_appteka, pkg_name, version, log_err)
249
+ if not sites or "apkcombo" in sites:
250
+ search(search_apkcombo, pkg_name, version, log_err)
251
+ if not sites or "apkfab" in sites:
252
+ search(search_apkfab, pkg_name, version, log_err)
253
+ if not sites or "apkad" in sites:
254
+ search(search_apkad, pkg_name, version, log_err)
231
255
 
232
256
 
233
257
  if __name__ == "__main__":
@@ -0,0 +1,10 @@
1
+ try:
2
+ from curl_cffi import requests
3
+
4
+ curl = True
5
+ except ImportError:
6
+ import requests
7
+
8
+ curl = False
9
+
10
+ __all__ = ["requests", "curl"]
@@ -0,0 +1,142 @@
1
+ import base64
2
+ import json
3
+
4
+ from bs4 import BeautifulSoup
5
+
6
+ from apksearch.sites import requests
7
+
8
+
9
+ class APKad:
10
+ """
11
+ This class provides methods to search for an APK on APKAD based on package name,
12
+ and to find available versions and their download links for a given APK link.
13
+
14
+ Parameters:
15
+ pkg_name (str): The package name of the APK to search for.
16
+
17
+ Attributes:
18
+ pkg_name (str): The package name of the APK to search for.
19
+ base_url (str): The base URL of the APKAD website.
20
+ search_url (str): The URL used to search for APKs on APKAD.
21
+ headers (dict): The headers used for making HTTP requests.
22
+ session (requests.Session): The session object used for making HTTP requests.
23
+
24
+ Methods:
25
+ search_apk() -> None | tuple[str, str]:
26
+ Searches for the APK on APKAD and returns the title and link if found.
27
+ """
28
+
29
+ def __init__(self, pkg_name: str):
30
+ self.pkg_name = pkg_name
31
+ self.base_url = "https://apkdownloader.pages.dev"
32
+ self.api_url = "https://api.mi9.com"
33
+ self.token_url = "https://token.mi9.com/"
34
+ self.search_url = self.api_url + "/get"
35
+ self.headers = {
36
+ "accept": "text/event-stream",
37
+ "accept-language": "en-US,en;q=0.9,en-IN;q=0.8",
38
+ "cache-control": "no-cache",
39
+ "dnt": "1",
40
+ "origin": "https://apkdownloader.pages.dev",
41
+ "pragma": "no-cache",
42
+ "priority": "u=1, i",
43
+ "referer": "https://apkdownloader.pages.dev/",
44
+ "sec-ch-ua": '"Microsoft Edge";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
45
+ "sec-ch-ua-mobile": "?0",
46
+ "sec-ch-ua-platform": '"Windows"',
47
+ "sec-fetch-dest": "empty",
48
+ "sec-fetch-mode": "cors",
49
+ "sec-fetch-site": "same-site",
50
+ "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",
51
+ }
52
+ self.session = requests.Session()
53
+
54
+ def get_token(self) -> tuple[str, int] | None:
55
+ """
56
+ Retrieves a token from the token endpoint.
57
+
58
+ Returns:
59
+ tuple[str, int]: A tuple containing the token and timestamp if successful.
60
+ None: If the token retrieval fails.
61
+ """
62
+ data = {
63
+ "package": self.pkg_name,
64
+ "device": "phone",
65
+ "arch": "arm64-v8a",
66
+ "vc": "",
67
+ "device_id": "",
68
+ "sdk": "default",
69
+ }
70
+ response = self.session.post(self.token_url, headers=self.headers, json=data)
71
+ if response.status_code == 200:
72
+ token_data = response.json()
73
+ if token_data.get("success"):
74
+ return token_data.get("token"), token_data.get("timestamp")
75
+ return None
76
+
77
+ def search_apk(self) -> None | tuple[str, list[tuple[str, str]]]:
78
+ token_tuple = self.get_token()
79
+ if not token_tuple:
80
+ return None
81
+
82
+ token, ts = token_tuple
83
+ data_json = json.dumps(
84
+ {
85
+ "hl": "en",
86
+ "package": self.pkg_name,
87
+ "device": "phone",
88
+ "arch": "arm64-v8a",
89
+ "vc": "",
90
+ "device_id": "",
91
+ "sdk": "default",
92
+ "timestamp": ts,
93
+ },
94
+ separators=(",", ":"),
95
+ )
96
+
97
+ data_b64 = base64.b64encode(data_json.encode("utf-8")).decode("utf-8")
98
+
99
+ params = {"token": token, "data": data_b64}
100
+
101
+ response = self.session.get(
102
+ self.search_url, headers=self.headers, params=params, stream=True
103
+ )
104
+
105
+ stream_response = None
106
+ for line in response.iter_lines():
107
+ if not line:
108
+ continue
109
+ line_response = line.decode("utf-8")
110
+ if line_response.startswith("data: "):
111
+ payload = line_response[6:]
112
+ try:
113
+ j = json.loads(payload)
114
+ if j.get("progress") == 100 and j.get("html"):
115
+ stream_response = j
116
+ break
117
+ except json.JSONDecodeError:
118
+ continue
119
+
120
+ if stream_response:
121
+ html_body = stream_response["html"]
122
+ soup = BeautifulSoup(html_body, "html.parser")
123
+ if not soup:
124
+ return None
125
+
126
+ title = soup.find("li", {"class": "_title"})
127
+ title = title.text.strip() if title else self.pkg_name
128
+
129
+ apk_files_div = soup.find("div", {"id": "apkslist"})
130
+ if not apk_files_div:
131
+ return None
132
+
133
+ apk_links: list[tuple[str, str]] = []
134
+ for a in apk_files_div.find_all("a", href=True):
135
+ link = a["href"].strip()
136
+ filename = a.find("span", {"class": "der_name"})
137
+ filename = filename.text.strip() if filename else link.split("/")[-1]
138
+ apk_links.append((filename, link))
139
+
140
+ return title, apk_links
141
+
142
+ return None
@@ -1,5 +1,6 @@
1
1
  from bs4 import BeautifulSoup
2
- import requests
2
+
3
+ from apksearch.sites import requests
3
4
 
4
5
 
5
6
  class APKCombo:
@@ -27,7 +28,7 @@ class APKCombo:
27
28
 
28
29
  def __init__(self, pkg_name: str):
29
30
  self.pkg_name = pkg_name
30
- self.base_url = "https://apkcombo.app"
31
+ self.base_url = "https://apkcombo.com"
31
32
  self.search_url = self.base_url + "/search"
32
33
  self.headers = {
33
34
  "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",
@@ -37,7 +38,7 @@ class APKCombo:
37
38
  "dnt": "1",
38
39
  "pragma": "no-cache",
39
40
  "priority": "u=0, i",
40
- "referer": "https://apkcombo.app/",
41
+ "referer": "https://apkcombo.com/",
41
42
  "sec-ch-ua": '"Microsoft Edge";v="131", "Chromium";v="131", "Not_A Brand";v="24"',
42
43
  "sec-ch-ua-mobile": "?0",
43
44
  "sec-ch-ua-platform": '"Windows"',
@@ -1,5 +1,6 @@
1
1
  from bs4 import BeautifulSoup
2
- import requests
2
+
3
+ from apksearch.sites import requests, curl
3
4
 
4
5
 
5
6
  class APKFab:
@@ -59,7 +60,10 @@ class APKFab:
59
60
  """
60
61
  pkg_name = self.pkg_name
61
62
  url = self.search_url + pkg_name
62
- response: requests.Response = self.session.get(url, headers=self.headers)
63
+ params = {"headers": self.headers}
64
+ if curl:
65
+ params["impersonate"] = "chrome"
66
+ response: requests.Response = self.session.get(url, **params)
63
67
  soup = BeautifulSoup(response.text, "html.parser")
64
68
  search_result = soup.find("div", {"class": "search-white"})
65
69
  if search_result:
@@ -1,4 +1,6 @@
1
- import requests
1
+ import re
2
+
3
+ from apksearch.sites import requests
2
4
 
3
5
 
4
6
  class APKMirror:
@@ -59,7 +61,7 @@ class APKMirror:
59
61
  return title, apk_link
60
62
  return None
61
63
 
62
- def find_version(self, apk_link: str, version: str) -> str:
64
+ def find_version(self, apk_link: str, version: str, title: str) -> str | None:
63
65
  """
64
66
  Finds and returns the download link for the given APK link and version.
65
67
 
@@ -70,7 +72,8 @@ class APKMirror:
70
72
  Returns:
71
73
  str: The download link for the specified version of the APK.
72
74
  """
73
- name = apk_link.split("/")[-2]
75
+ name = re.sub(r"[(),]", "", title).lower().replace(" ", "-")
76
+ name = re.sub(r"-+", "-", name)
74
77
  version = version.replace(".", "-")
75
78
  url = apk_link + name + "-" + version + "-release"
76
79
  response = self.session.get(url, headers=self.headers)
@@ -1,6 +1,8 @@
1
1
  import re
2
+
2
3
  from bs4 import BeautifulSoup
3
- import requests
4
+
5
+ from apksearch.sites import requests
4
6
 
5
7
 
6
8
  class APKPure:
@@ -150,7 +152,7 @@ class APKPure:
150
152
  title, versions = api_result
151
153
  if versions:
152
154
  return title, versions[0][1]
153
-
155
+
154
156
  return None
155
157
 
156
158
  def find_versions(self, apk_link: str) -> list[tuple[str, str]]:
@@ -1,6 +1,8 @@
1
1
  import re
2
+
2
3
  from bs4 import BeautifulSoup
3
- import requests
4
+
5
+ from apksearch.sites import requests
4
6
 
5
7
 
6
8
  class AppTeka:
@@ -48,7 +50,7 @@ class AppTeka:
48
50
  }
49
51
  self.session = requests.Session()
50
52
 
51
- def search_apk(self, version: str = None) -> None | tuple[str, str]:
53
+ def search_apk(self, version: str | None = None) -> None | tuple[str, str | None]:
52
54
  """
53
55
  Searches for the APK on AppTeka and returns the title and link if found.
54
56
 
@@ -1,6 +1,8 @@
1
1
  import re
2
+
2
3
  from bs4 import BeautifulSoup
3
- import requests
4
+
5
+ from apksearch.sites import requests
4
6
 
5
7
 
6
8
  class Aptoide:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: apksearch
3
- Version: 1.3.1
3
+ Version: 1.3.5
4
4
  Summary: Search for apks on varius websites
5
5
  Author-email: Abhi <allinoneallinone00@gmail.com>
6
6
  License: MIT License
@@ -49,6 +49,8 @@ Provides-Extra: dev
49
49
  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
+ Provides-Extra: curl
53
+ Requires-Dist: curl_cffi>=0.12.1b1; extra == "curl"
52
54
  Dynamic: license-file
53
55
 
54
56
  <h1 align="center">apksearch</h1>
@@ -1,6 +1,9 @@
1
1
  beautifulsoup4>=4.12.3
2
2
  requests>=2.32.3
3
3
 
4
+ [curl]
5
+ curl_cffi>=0.12.1b1
6
+
4
7
  [dev]
5
8
  pytest>=7.4.3
6
9
  black>=23.12.1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "apksearch"
7
- version = "1.3.1"
7
+ version = "1.3.5"
8
8
  description = "Search for apks on varius websites"
9
9
  authors = [{ name = "Abhi", email = "allinoneallinone00@gmail.com" }]
10
10
  license = { file = "LICENSE" }
@@ -34,6 +34,7 @@ issues = "https://github.com/AbhiTheModder/apksearch.git/issues"
34
34
 
35
35
  [project.optional-dependencies]
36
36
  dev = ["pytest>=7.4.3", "black>=23.12.1", "flake8>=6.1.0"]
37
+ curl = ["curl_cffi>=0.12.1b1"]
37
38
 
38
39
  [tool.pytest.ini_options]
39
40
  minversion = "7.4.3"
@@ -24,15 +24,15 @@ def test_find_versions():
24
24
 
25
25
  assert isinstance(versions, list), "Versions should be a list."
26
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."
27
+ assert all(isinstance(version, tuple) for version in versions), (
28
+ "Each version should be a tuple."
29
+ )
30
+ assert all(len(version) == 2 for version in versions), (
31
+ "Each version tuple should contain two elements."
32
+ )
33
+ assert all(isinstance(version[0], str) for version in versions), (
34
+ "First element of each version tuple should be a string."
35
+ )
36
+ assert all(isinstance(version[1], str) for version in versions), (
37
+ "Second element of each version tuple should be a string."
38
+ )
@@ -20,11 +20,12 @@ def test_find_versions_nfound():
20
20
  result = apkmirror.search_apk()
21
21
 
22
22
  if result:
23
+ title = result[0]
23
24
  apk_link = result[1]
24
25
 
25
26
  assert apk_link.startswith("https://"), "APK link should be a valid URL."
26
27
 
27
- download_link = apkmirror.find_version(apk_link, version)
28
+ download_link = apkmirror.find_version(apk_link, version, title)
28
29
 
29
30
  assert download_link is None, "Version not found."
30
31
 
@@ -36,10 +37,11 @@ def test_find_versions_found():
36
37
  result = apkmirror.search_apk()
37
38
 
38
39
  if result:
40
+ title = result[0]
39
41
  apk_link = result[1]
40
- download_link = apkmirror.find_version(apk_link, version)
42
+ download_link = apkmirror.find_version(apk_link, version, title)
41
43
 
42
44
  assert isinstance(download_link, str), "Download link should be a string."
43
- assert download_link.startswith(
44
- "https://"
45
- ), "Download link should be a valid URL."
45
+ assert download_link.startswith("https://"), (
46
+ "Download link should be a valid URL."
47
+ )
@@ -24,15 +24,15 @@ def test_find_versions():
24
24
 
25
25
  assert isinstance(versions, list), "Versions should be a list."
26
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."
27
+ assert all(isinstance(version, tuple) for version in versions), (
28
+ "Each version should be a tuple."
29
+ )
30
+ assert all(len(version) == 2 for version in versions), (
31
+ "Each version tuple should contain two elements."
32
+ )
33
+ assert all(isinstance(version[0], str) for version in versions), (
34
+ "First element of each version tuple should be a string."
35
+ )
36
+ assert all(isinstance(version[1], str) for version in versions), (
37
+ "Second element of each version tuple should be a string."
38
+ )
@@ -24,6 +24,6 @@ def test_find_versions():
24
24
 
25
25
  assert isinstance(versions, list), "Versions should be a list."
26
26
  assert len(versions) > 0, "No versions found."
27
- assert all(
28
- isinstance(version, str) for version in versions
29
- ), "Each version should be a string."
27
+ assert all(isinstance(version, str) for version in versions), (
28
+ "Each version should be a string."
29
+ )
File without changes
@@ -1,109 +0,0 @@
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
File without changes
File without changes
@@ -1,9 +1,9 @@
1
- from .sites.apkpure import APKPure
2
- from .sites.apkmirror import APKMirror
3
- from .sites.appteka import AppTeka
1
+ from .sites.apkad import APKad
4
2
  from .sites.apkcombo import APKCombo
5
3
  from .sites.apkfab import APKFab
6
- from .sites.apkad import APKad
4
+ from .sites.apkmirror import APKMirror
5
+ from .sites.apkpure import APKPure
6
+ from .sites.appteka import AppTeka
7
7
  from .sites.aptoide import Aptoide
8
8
 
9
9
  __all__ = [
File without changes