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.
Files changed (26) hide show
  1. {apksearch-1.1.0 → apksearch-1.2.5}/PKG-INFO +28 -5
  2. {apksearch-1.1.0 → apksearch-1.2.5}/README.md +26 -3
  3. apksearch-1.2.5/apksearch/__init__.py +7 -0
  4. {apksearch-1.1.0 → apksearch-1.2.5}/apksearch/cli.py +68 -12
  5. apksearch-1.2.5/apksearch/sites/apkcombo.py +107 -0
  6. apksearch-1.2.5/apksearch/sites/apkfab.py +120 -0
  7. {apksearch-1.1.0 → apksearch-1.2.5}/apksearch/sites/apkpure.py +43 -18
  8. {apksearch-1.1.0 → apksearch-1.2.5}/apksearch.egg-info/PKG-INFO +28 -5
  9. {apksearch-1.1.0 → apksearch-1.2.5}/apksearch.egg-info/SOURCES.txt +4 -0
  10. {apksearch-1.1.0 → apksearch-1.2.5}/pyproject.toml +2 -2
  11. apksearch-1.2.5/tests/test_apkcombo.py +38 -0
  12. apksearch-1.2.5/tests/test_apkfab.py +38 -0
  13. apksearch-1.1.0/apksearch/__init__.py +0 -5
  14. {apksearch-1.1.0 → apksearch-1.2.5}/LICENSE +0 -0
  15. {apksearch-1.1.0 → apksearch-1.2.5}/apksearch/__main__.py +0 -0
  16. {apksearch-1.1.0 → apksearch-1.2.5}/apksearch/sites/__init__.py +0 -0
  17. {apksearch-1.1.0 → apksearch-1.2.5}/apksearch/sites/apkmirror.py +0 -0
  18. {apksearch-1.1.0 → apksearch-1.2.5}/apksearch/sites/appteka.py +0 -0
  19. {apksearch-1.1.0 → apksearch-1.2.5}/apksearch.egg-info/dependency_links.txt +0 -0
  20. {apksearch-1.1.0 → apksearch-1.2.5}/apksearch.egg-info/entry_points.txt +0 -0
  21. {apksearch-1.1.0 → apksearch-1.2.5}/apksearch.egg-info/requires.txt +0 -0
  22. {apksearch-1.1.0 → apksearch-1.2.5}/apksearch.egg-info/top_level.txt +0 -0
  23. {apksearch-1.1.0 → apksearch-1.2.5}/setup.cfg +0 -0
  24. {apksearch-1.1.0 → apksearch-1.2.5}/tests/test_apkmirror.py +0 -0
  25. {apksearch-1.1.0 → apksearch-1.2.5}/tests/test_apkpure.py +0 -0
  26. {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.1.0
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 :: 4 - Beta
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!
@@ -0,0 +1,7 @@
1
+ from .sites.apkpure import APKPure
2
+ from .sites.apkmirror import APKMirror
3
+ from .sites.appteka import AppTeka
4
+ from .sites.apkcombo import APKCombo
5
+ from .sites.apkfab import APKFab
6
+
7
+ __all__ = ["APKPure", "APKMirror", "AppTeka", "APKCombo", "APKFab"]
@@ -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
- versions: list[tuple[str, str]] = apkpure.find_versions(apk_link)
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}APKPure:{NC} Version {RED}{version}{NC} not found!")
86
+ print(f"{BOLD}APKCombo:{NC} Version {RED}{version}{NC} not found!")
35
87
  else:
36
- print(f"{BOLD}APKPure:{NC} No Results!")
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
- result_apkpure: tuple[str, str] | None = apkmirror.search_apk()
94
+ result_apkmirror: tuple[str, str] | None = apkmirror.search_apk()
43
95
  except (ConnectionError, ConnectTimeout):
44
- result_apkpure = None
96
+ result_apkmirror = None
45
97
  print(f"{RED}Failed to resolve 'apkmirror.com'!{NC}")
46
- if result_apkpure:
47
- title, apk_link = result_apkpure
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
- result_apkpure: tuple[str, str] | None = appteka.search_apk(version)
117
+ result_appteka: tuple[str, str] | None = appteka.search_apk(version)
66
118
  except (ConnectionError, ConnectTimeout):
67
- result_apkpure = None
119
+ result_appteka = None
68
120
  print(f"{RED}Failed to resolve 'appteka.store'!{NC}")
69
- if result_apkpure:
70
- title, apk_link = result_apkpure
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
- if versions_list:
95
- versions = versions_list.find_all(
96
- "li", {"class": re.compile("^version dt-version-item.*")}
97
- )
98
- for version in versions:
99
- version_icon = version.find("a", {"class": "dt-version-icon"})
100
- version_info = version.find("div", {"class": "version-info"})
101
- if version_icon and version_info:
102
- version_number = version_info.find(
103
- "span", {"class": "name one-line"}
104
- ).text
105
- download_url = self.base_url + version_icon["href"]
106
- versions_info.append((version_number, download_url))
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.1.0
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 :: 4 - Beta
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.1.0"
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 :: 4 - Beta",
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."
@@ -1,5 +0,0 @@
1
- from .sites.apkpure import APKPure
2
- from .sites.apkmirror import APKMirror
3
- from .sites.appteka import AppTeka
4
-
5
- __all__ = ["APKPure", "APKMirror", "AppTeka"]
File without changes
File without changes