apksearch 1.2.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 (25) hide show
  1. {apksearch-1.2.0 → apksearch-1.2.5}/PKG-INFO +28 -11
  2. {apksearch-1.2.0 → apksearch-1.2.5}/README.md +26 -9
  3. {apksearch-1.2.0 → apksearch-1.2.5}/apksearch/__init__.py +2 -1
  4. {apksearch-1.2.0 → apksearch-1.2.5}/apksearch/cli.py +39 -10
  5. {apksearch-1.2.0 → apksearch-1.2.5}/apksearch/sites/apkcombo.py +4 -1
  6. apksearch-1.2.5/apksearch/sites/apkfab.py +120 -0
  7. {apksearch-1.2.0 → apksearch-1.2.5}/apksearch/sites/apkpure.py +43 -18
  8. {apksearch-1.2.0 → apksearch-1.2.5}/apksearch.egg-info/PKG-INFO +28 -11
  9. {apksearch-1.2.0 → apksearch-1.2.5}/apksearch.egg-info/SOURCES.txt +2 -0
  10. {apksearch-1.2.0 → apksearch-1.2.5}/pyproject.toml +2 -2
  11. apksearch-1.2.5/tests/test_apkfab.py +38 -0
  12. {apksearch-1.2.0 → apksearch-1.2.5}/LICENSE +0 -0
  13. {apksearch-1.2.0 → apksearch-1.2.5}/apksearch/__main__.py +0 -0
  14. {apksearch-1.2.0 → apksearch-1.2.5}/apksearch/sites/__init__.py +0 -0
  15. {apksearch-1.2.0 → apksearch-1.2.5}/apksearch/sites/apkmirror.py +0 -0
  16. {apksearch-1.2.0 → apksearch-1.2.5}/apksearch/sites/appteka.py +0 -0
  17. {apksearch-1.2.0 → apksearch-1.2.5}/apksearch.egg-info/dependency_links.txt +0 -0
  18. {apksearch-1.2.0 → apksearch-1.2.5}/apksearch.egg-info/entry_points.txt +0 -0
  19. {apksearch-1.2.0 → apksearch-1.2.5}/apksearch.egg-info/requires.txt +0 -0
  20. {apksearch-1.2.0 → apksearch-1.2.5}/apksearch.egg-info/top_level.txt +0 -0
  21. {apksearch-1.2.0 → apksearch-1.2.5}/setup.cfg +0 -0
  22. {apksearch-1.2.0 → apksearch-1.2.5}/tests/test_apkcombo.py +0 -0
  23. {apksearch-1.2.0 → apksearch-1.2.5}/tests/test_apkmirror.py +0 -0
  24. {apksearch-1.2.0 → apksearch-1.2.5}/tests/test_apkpure.py +0 -0
  25. {apksearch-1.2.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.2.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.
@@ -134,12 +148,6 @@ if result:
134
148
  - **`__init__(self, pkg_name: str)`**: Initializes with the package name.
135
149
  - **`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
150
 
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
151
  ### Testing
144
152
 
145
153
  The project includes tests for the `sites` classes. To run the tests, use the following command:
@@ -152,6 +160,13 @@ pytest
152
160
 
153
161
  - [ ] Add more websites to search for APKs.
154
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
+
155
170
  ## License
156
171
 
157
172
  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 +174,5 @@ This project is licensed under the MIT License. See the [LICENSE](https://github
159
174
  ## Contributing
160
175
 
161
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.
@@ -82,12 +96,6 @@ if result:
82
96
  - **`__init__(self, pkg_name: str)`**: Initializes with the package name.
83
97
  - **`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.
84
98
 
85
- #### `APKCombo`
86
-
87
- - **`__init__(self, pkg_name: str)`**: Initializes with the package name.
88
- - **`search_apk(self) -> None | tuple[str, str]`**: Searches for the APK on APKCombo and returns the title and link if found.
89
- - **`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.
90
-
91
99
  ### Testing
92
100
 
93
101
  The project includes tests for the `sites` classes. To run the tests, use the following command:
@@ -100,6 +108,13 @@ pytest
100
108
 
101
109
  - [ ] Add more websites to search for APKs.
102
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
+
103
118
  ## License
104
119
 
105
120
  This project is licensed under the MIT License. See the [LICENSE](https://github.com/AbhiTheModder/apksearch/blob/main/LICENSE) file for more details.
@@ -107,3 +122,5 @@ This project is licensed under the MIT License. See the [LICENSE](https://github
107
122
  ## Contributing
108
123
 
109
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!
@@ -2,5 +2,6 @@ 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
5
6
 
6
- __all__ = ["APKPure", "APKMirror", "AppTeka", "APKCombo"]
7
+ __all__ = ["APKPure", "APKMirror", "AppTeka", "APKCombo", "APKFab"]
@@ -1,6 +1,6 @@
1
1
  import argparse
2
2
 
3
- from apksearch import APKPure, APKMirror, AppTeka, APKCombo
3
+ from apksearch import APKPure, APKMirror, AppTeka, APKCombo, APKFab
4
4
  from requests.exceptions import ConnectionError, ConnectTimeout
5
5
 
6
6
  # Color codes
@@ -22,20 +22,47 @@ 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)
26
25
  if version:
27
- for version_tuple in versions:
28
- if version_tuple[0] == version:
29
- print(
30
- f" ╰─> {BOLD}Version: {GREEN}{version}{NC} - {YELLOW}{version_tuple[1]}{NC}"
31
- )
32
- break
33
- else:
34
- print(f"{BOLD}APKPure:{NC} Version {RED}{version}{NC} not found!")
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!")
35
36
  else:
36
37
  print(f"{BOLD}APKPure:{NC} No Results!")
37
38
 
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
+
39
66
  def search_apkcombo(pkg_name: str, version: str | None) -> None:
40
67
  apkcombo = APKCombo(pkg_name)
41
68
  try:
@@ -126,6 +153,8 @@ def main():
126
153
  search_appteka(pkg_name, version)
127
154
  # Initiate search on apkcombo
128
155
  search_apkcombo(pkg_name, version)
156
+ # Initiate search on apkfab
157
+ search_apkfab(pkg_name, version)
129
158
 
130
159
 
131
160
  if __name__ == "__main__":
@@ -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
- title = soup.find("div", {"class": "app_name"}).text.strip()
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
@@ -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.2.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.
@@ -134,12 +148,6 @@ if result:
134
148
  - **`__init__(self, pkg_name: str)`**: Initializes with the package name.
135
149
  - **`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
150
 
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
151
  ### Testing
144
152
 
145
153
  The project includes tests for the `sites` classes. To run the tests, use the following command:
@@ -152,6 +160,13 @@ pytest
152
160
 
153
161
  - [ ] Add more websites to search for APKs.
154
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
+
155
170
  ## License
156
171
 
157
172
  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 +174,5 @@ This project is licensed under the MIT License. See the [LICENSE](https://github
159
174
  ## Contributing
160
175
 
161
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!
@@ -12,10 +12,12 @@ apksearch.egg-info/requires.txt
12
12
  apksearch.egg-info/top_level.txt
13
13
  apksearch/sites/__init__.py
14
14
  apksearch/sites/apkcombo.py
15
+ apksearch/sites/apkfab.py
15
16
  apksearch/sites/apkmirror.py
16
17
  apksearch/sites/apkpure.py
17
18
  apksearch/sites/appteka.py
18
19
  tests/test_apkcombo.py
20
+ tests/test_apkfab.py
19
21
  tests/test_apkmirror.py
20
22
  tests/test_apkpure.py
21
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.2.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.apkfab import APKFab
2
+
3
+
4
+ def test_search_apk():
5
+ query = "com.roblox.client"
6
+ apkfab = APKFab(query)
7
+ result = apkfab.search_apk()
8
+
9
+ assert result is not None, "No APK found for the query."
10
+ assert isinstance(result, tuple), "Result should be a tuple."
11
+ assert len(result) == 2, "Tuple should contain two elements."
12
+ assert isinstance(result[0], str), "First element of the tuple should be a string."
13
+ assert isinstance(result[1], str), "Second element of the tuple should be a string."
14
+
15
+
16
+ def test_find_versions():
17
+ query = "com.roblox.client"
18
+ apkfab = APKFab(query)
19
+ result = apkfab.search_apk()
20
+
21
+ if result:
22
+ apk_link = result[1]
23
+ versions = apkfab.find_versions(apk_link)
24
+
25
+ assert isinstance(versions, list), "Versions should be a list."
26
+ assert len(versions) > 0, "No versions found."
27
+ assert all(
28
+ isinstance(version, tuple) for version in versions
29
+ ), "Each version should be a tuple."
30
+ assert all(
31
+ len(version) == 2 for version in versions
32
+ ), "Each version tuple should contain two elements."
33
+ assert all(
34
+ isinstance(version[0], str) for version in versions
35
+ ), "First element of each version tuple should be a string."
36
+ assert all(
37
+ isinstance(version[1], str) for version in versions
38
+ ), "Second element of each version tuple should be a string."
File without changes
File without changes