├── Assets ├── download_icon.png ├── github_logo.png ├── info_icon.png ├── logo.ico ├── logo.png ├── qs_logo.png ├── stop_icon.png └── telegram_logo.png ├── FapelloDownloader.py ├── LICENSE ├── LICENSE.txt ├── README.md └── requirements.txt /Assets/download_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Djdefrag/Fapello.Downloader/655a9c18628b29027076049da751b49a35fbc9b2/Assets/download_icon.png -------------------------------------------------------------------------------- /Assets/github_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Djdefrag/Fapello.Downloader/655a9c18628b29027076049da751b49a35fbc9b2/Assets/github_logo.png -------------------------------------------------------------------------------- /Assets/info_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Djdefrag/Fapello.Downloader/655a9c18628b29027076049da751b49a35fbc9b2/Assets/info_icon.png -------------------------------------------------------------------------------- /Assets/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Djdefrag/Fapello.Downloader/655a9c18628b29027076049da751b49a35fbc9b2/Assets/logo.ico -------------------------------------------------------------------------------- /Assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Djdefrag/Fapello.Downloader/655a9c18628b29027076049da751b49a35fbc9b2/Assets/logo.png -------------------------------------------------------------------------------- /Assets/qs_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Djdefrag/Fapello.Downloader/655a9c18628b29027076049da751b49a35fbc9b2/Assets/qs_logo.png -------------------------------------------------------------------------------- /Assets/stop_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Djdefrag/Fapello.Downloader/655a9c18628b29027076049da751b49a35fbc9b2/Assets/stop_icon.png -------------------------------------------------------------------------------- /Assets/telegram_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Djdefrag/Fapello.Downloader/655a9c18628b29027076049da751b49a35fbc9b2/Assets/telegram_logo.png -------------------------------------------------------------------------------- /FapelloDownloader.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | # Standard library imports 4 | import sys 5 | from time import sleep 6 | from webbrowser import open as open_browser 7 | from warnings import filterwarnings 8 | 9 | from multiprocessing import ( 10 | Process, 11 | Queue as multiprocessing_Queue, 12 | freeze_support as multiprocessing_freeze_support 13 | ) 14 | from typing import Callable 15 | from shutil import rmtree as remove_directory 16 | from itertools import repeat as itertools_repeat 17 | from threading import Thread 18 | from multiprocessing.pool import ThreadPool 19 | 20 | from os import ( 21 | sep as os_separator, 22 | makedirs as os_makedirs, 23 | listdir as os_listdir 24 | ) 25 | 26 | from os.path import ( 27 | dirname as os_path_dirname, 28 | abspath as os_path_abspath, 29 | join as os_path_join, 30 | exists as os_path_exists, 31 | ) 32 | 33 | 34 | # Third-party library imports 35 | from re import compile as re_compile 36 | from requests import get as requests_get 37 | from fnmatch import filter as fnmatch_filter 38 | from PIL.Image import open as pillow_image_open 39 | from bs4 import BeautifulSoup 40 | from urllib.request import Request, urlopen 41 | 42 | 43 | 44 | # GUI imports 45 | from tkinter import StringVar, CENTER 46 | from customtkinter import ( 47 | CTk, 48 | CTkButton, 49 | CTkEntry, 50 | CTkFont, 51 | CTkImage, 52 | CTkLabel, 53 | CTkToplevel, 54 | set_appearance_mode, 55 | set_default_color_theme, 56 | ) 57 | 58 | filterwarnings("ignore") 59 | 60 | app_name = "Fapello.Downloader" 61 | version = "3.6" 62 | 63 | text_color = "#F0F0F0" 64 | app_name_color = "#ffbf00" 65 | 66 | githubme = "https://github.com/Djdefrag/Fapello.Downloader" 67 | telegramme = "https://linktr.ee/j3ngystudio" 68 | qs_link = "https://github.com/Djdefrag/QualityScaler" 69 | 70 | COMPLETED_STATUS = "Completed" 71 | DOWNLOADING_STATUS = "Downloading" 72 | ERROR_STATUS = "Error" 73 | STOP_STATUS = "Stop" 74 | 75 | HEADERS_FOR_REQUESTS = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36" } 76 | 77 | # Utils 78 | 79 | def opengithub() -> None: open_browser(githubme, new=1) 80 | 81 | def opentelegram() -> None: open_browser(telegramme, new=1) 82 | 83 | def openqualityscaler() -> None: open_browser(qs_link, new=1) 84 | 85 | def find_by_relative_path(relative_path: str) -> str: 86 | base_path = getattr(sys, '_MEIPASS', os_path_dirname(os_path_abspath(__file__))) 87 | return os_path_join(base_path, relative_path) 88 | 89 | def create_temp_dir(name_dir: str) -> None: 90 | if os_path_exists(name_dir): remove_directory(name_dir) 91 | if not os_path_exists(name_dir): os_makedirs(name_dir, mode=0o777) 92 | 93 | def stop_thread() -> None: 94 | stop = 1 + "x" 95 | 96 | def prepare_filename(file_url, index, file_type) -> str: 97 | first_part_filename = str(file_url).split("/")[-3] 98 | 99 | if file_type == "image": extension = ".jpg" 100 | elif file_type == "video": extension = ".mp4" 101 | 102 | filename = first_part_filename + "_" + str(index) + extension 103 | 104 | return filename 105 | 106 | def show_error_message(exception: str) -> None: 107 | 108 | messageBox_title = "Download error" 109 | messageBox_subtitle = "Please report the error on Github or Telegram" 110 | messageBox_text = f" {str(exception)} " 111 | 112 | CTkMessageBox( 113 | messageType = "error", 114 | title = messageBox_title, 115 | subtitle = messageBox_subtitle, 116 | default_value = None, 117 | option_list = [messageBox_text] 118 | ) 119 | 120 | def read_process_status() -> None: 121 | actual_step = processing_queue.get() 122 | 123 | if actual_step == DOWNLOADING_STATUS: 124 | write_process_status(processing_queue, DOWNLOADING_STATUS) 125 | 126 | return actual_step 127 | 128 | def write_process_status( 129 | processing_queue: multiprocessing_Queue, 130 | step: str 131 | ) -> None: 132 | 133 | while not processing_queue.empty(): processing_queue.get() 134 | processing_queue.put(f"{step}") 135 | 136 | def count_files_in_directory(target_dir: str) -> int: 137 | return len(fnmatch_filter(os_listdir(target_dir), '*.*')) 138 | 139 | def thread_check_steps_download( 140 | link: str, 141 | how_many_files: int 142 | ) -> None: 143 | 144 | sleep(1) 145 | target_dir = link.split("/")[3] 146 | 147 | try: 148 | while True: 149 | actual_step = read_process_status() 150 | 151 | if actual_step == COMPLETED_STATUS: 152 | info_message.set(f"Download completed! :)") 153 | stop_thread() 154 | 155 | elif actual_step == DOWNLOADING_STATUS: 156 | file_count = count_files_in_directory(target_dir) 157 | info_message.set(f"Downloading {str(file_count)} / {str(how_many_files)}") 158 | 159 | elif actual_step == STOP_STATUS: 160 | info_message.set(f"Download stopped") 161 | stop_thread() 162 | 163 | elif ERROR_STATUS in actual_step: 164 | error_message = f"Error while downloading :(" 165 | info_message.set(error_message) 166 | 167 | error = actual_step.replace(ERROR_STATUS, "") 168 | show_error_message(error) 169 | stop_thread() 170 | 171 | else: 172 | info_message.set(actual_step) 173 | 174 | sleep(1) 175 | except: 176 | place_download_button() 177 | 178 | 179 | 180 | # Core 181 | 182 | def check_button_command() -> None: 183 | 184 | selected_link = str(selected_url.get()).strip() 185 | 186 | if selected_link == "Paste link here https://fapello.com/emily-rat---/": info_message.set("Insert a valid Fapello.com link") 187 | 188 | elif selected_link == "": info_message.set("Insert a valid Fapello.com link") 189 | 190 | elif "https://fapello.com" in selected_link: 191 | 192 | if not selected_link.endswith("/"): selected_link = selected_link + '/' 193 | 194 | how_many_files = get_Fapello_files_number(selected_link) 195 | 196 | if how_many_files == 0: 197 | info_message.set("No files found for this link") 198 | else: 199 | info_message.set(f"Found {how_many_files} files for this link") 200 | 201 | else: info_message.set("Insert a valid Fapello.com link") 202 | 203 | def download_button_command() -> None: 204 | global process_download 205 | global cpu_number 206 | global processing_queue 207 | 208 | info_message.set("Starting download") 209 | write_process_status(processing_queue, "Starting download") 210 | 211 | try: cpu_number = int(float(str(selected_cpu_number.get()))) 212 | except: 213 | info_message.set("Cpu number must be a numeric value") 214 | return 215 | 216 | selected_link = str(selected_url.get()).strip() 217 | 218 | if selected_link == "Paste link here https://fapello.com/emily-rat---/": 219 | info_message.set("Insert a valid Fapello.com link") 220 | 221 | elif selected_link == "": 222 | info_message.set("Insert a valid Fapello.com link") 223 | 224 | elif "https://fapello.com" in selected_link: 225 | 226 | download_type = 'fapello.com' 227 | 228 | if not selected_link.endswith("/"): selected_link = selected_link + '/' 229 | 230 | how_many_images = get_Fapello_files_number(selected_link) 231 | 232 | if how_many_images == 0: 233 | info_message.set("No files found for this link") 234 | else: 235 | process_download = Process( 236 | target = download_orchestrator, 237 | args = ( 238 | processing_queue, 239 | selected_link, 240 | cpu_number 241 | ) 242 | ) 243 | process_download.start() 244 | 245 | thread_wait = Thread( 246 | target = thread_check_steps_download, 247 | args = ( 248 | selected_link, 249 | how_many_images 250 | ) 251 | ) 252 | thread_wait.start() 253 | 254 | place_stop_button() 255 | 256 | else: 257 | info_message.set("Insert a valid Fapello.com link") 258 | 259 | def stop_download_process() -> None: 260 | global process_download 261 | try: 262 | process_download 263 | except: 264 | pass 265 | else: 266 | process_download.kill() 267 | 268 | def stop_button_command() -> None: 269 | stop_download_process() 270 | write_process_status(processing_queue, f"{STOP_STATUS}") 271 | 272 | def get_Fapello_file_url(link: str) -> tuple: 273 | 274 | page = requests_get(link, headers = HEADERS_FOR_REQUESTS) 275 | soup = BeautifulSoup(page.content, "html.parser") 276 | file_element = soup.find("div", class_="flex justify-between items-center") 277 | try: 278 | if 'type="video/mp4' in str(file_element): 279 | file_url = file_element.find("source").get("src") 280 | file_type = "video" 281 | print(f" > Video: {file_url}") 282 | else: 283 | file_url = file_element.find("img").get("src") 284 | file_type = "image" 285 | print(f" > Image: {file_url}") 286 | 287 | return file_url, file_type 288 | except: 289 | return None, None 290 | 291 | def get_Fapello_files_number(url: str) -> int: 292 | 293 | page = requests_get(url, headers = HEADERS_FOR_REQUESTS) 294 | soup = BeautifulSoup(page.content, "html.parser") 295 | 296 | all_href_links = soup.find_all('a', href = re_compile(url)) 297 | 298 | for link in all_href_links: 299 | link_href = link.get('href') 300 | link_href_stripped = link_href.rstrip('/') 301 | link_href_numeric = link_href_stripped.split('/')[-1] 302 | if link_href_numeric.isnumeric(): 303 | print(f"> Found {link_href_numeric} files") 304 | return int(link_href_numeric) + 1 305 | 306 | return 0 307 | 308 | def thread_download_file( 309 | link: str, 310 | target_dir: str, 311 | index: int 312 | ) -> None: 313 | 314 | link = link + str(index) 315 | model_name = link.split('/')[3] 316 | 317 | file_url, file_type = get_Fapello_file_url(link) 318 | 319 | if file_url != None and model_name in file_url: 320 | try: 321 | file_name = prepare_filename(file_url, index, file_type) 322 | file_path = os_path_join(target_dir, file_name) 323 | 324 | request = Request(file_url, headers = HEADERS_FOR_REQUESTS) 325 | response = urlopen(request) 326 | 327 | with open(file_path, 'wb') as output_file: output_file.write(response.read()) 328 | 329 | except: 330 | pass 331 | 332 | def download_orchestrator( 333 | processing_queue: multiprocessing_Queue, 334 | selected_link: str, 335 | cpu_number: int 336 | ): 337 | 338 | target_dir = selected_link.split("/")[3] 339 | list_of_index = [] 340 | 341 | write_process_status(processing_queue, DOWNLOADING_STATUS) 342 | 343 | try: 344 | create_temp_dir(target_dir) 345 | how_many_files = get_Fapello_files_number(selected_link) 346 | list_of_index = [index for index in range(how_many_files)] 347 | 348 | with ThreadPool(cpu_number) as pool: 349 | pool.starmap( 350 | thread_download_file, 351 | zip( 352 | itertools_repeat(selected_link), 353 | itertools_repeat(target_dir), 354 | list_of_index 355 | ) 356 | ) 357 | 358 | write_process_status(processing_queue, COMPLETED_STATUS) 359 | 360 | except Exception as error: 361 | print(error) 362 | pass 363 | 364 | 365 | 366 | # UI function 367 | 368 | def place_github_button(): 369 | git_button = CTkButton(master = window, 370 | command = opengithub, 371 | image = logo_git, 372 | width = 30, 373 | height = 30, 374 | border_width = 1, 375 | fg_color = "transparent", 376 | text_color = "#C0C0C0", 377 | border_color = "#404040", 378 | anchor = "center", 379 | text = "", 380 | font = bold11) 381 | 382 | git_button.place(relx = 0.055, rely = 0.875, anchor = CENTER) 383 | 384 | def place_telegram_button(): 385 | telegram_button = CTkButton(master = window, 386 | image = logo_telegram, 387 | command = opentelegram, 388 | width = 30, 389 | height = 30, 390 | border_width = 1, 391 | fg_color = "transparent", 392 | text_color = "#C0C0C0", 393 | border_color = "#404040", 394 | anchor = "center", 395 | text = "", 396 | font = bold11) 397 | telegram_button.place(relx = 0.055, rely = 0.95, anchor = CENTER) 398 | 399 | def place_qualityscaler_button(): 400 | qualityscaler_button = CTkButton( 401 | master = window, 402 | image = logo_qs, 403 | command = openqualityscaler, 404 | width = 30, 405 | height = 30, 406 | border_width = 1, 407 | fg_color = "transparent", 408 | text_color = "#C0C0C0", 409 | border_color = "#404040", 410 | anchor = "center", 411 | text = "", 412 | font = bold11) 413 | qualityscaler_button.place(relx = 0.055, rely = 0.8, anchor = CENTER) 414 | 415 | def open_info_simultaneous_downloads(): 416 | 417 | CTkMessageBox( 418 | messageType = 'info', 419 | title = "Simultaneous downloads", 420 | subtitle = "This widget allows to choose how many files are downloaded simultaneously", 421 | default_value = "6", 422 | option_list = [] 423 | ) 424 | 425 | def open_info_tips(): 426 | CTkMessageBox( 427 | messageType = 'info', 428 | title = "Connection tips", 429 | subtitle = "In case of problems with reaching the website, follow these tips", 430 | default_value = None, 431 | option_list = [ 432 | " Many internet providers block access to websites such as fapello.com", 433 | " In this case you can use custom DNS or use a VPN", 434 | 435 | "\n To facilitate there is a free program called DNSJumper\n" + 436 | " • it can find the best custom DNS for your internet line and set them directly\n" + 437 | " • it can quickly revert to the default DNS in case of problems \n" + 438 | " • has also a useful function called DNS Flush that solves problems connecting to the Fapello.com \n", 439 | 440 | " On some occasions, the download may freeze, just stop and restart the download" 441 | ] 442 | ) 443 | 444 | def place_app_name(): 445 | app_name_label = CTkLabel(master = window, 446 | text = app_name + " " + version, 447 | text_color = app_name_color, 448 | font = bold20, 449 | anchor = "w") 450 | 451 | app_name_label.place(relx = 0.5, 452 | rely = 0.1, 453 | anchor = CENTER) 454 | 455 | def place_link_textbox(): 456 | link_textbox = create_text_box(selected_url, 150, 32) 457 | link_textbox.place(relx = 0.435, rely = 0.3, relwidth = 0.7, anchor = CENTER) 458 | 459 | def place_check_button(): 460 | check_button = CTkButton( 461 | master = window, 462 | command = check_button_command, 463 | text = "CHECK", 464 | width = 60, 465 | height = 30, 466 | font = bold11, 467 | border_width = 1, 468 | fg_color = "#282828", 469 | text_color = "#E0E0E0", 470 | border_color = "#0096FF" 471 | ) 472 | check_button.place(relx = 0.865, rely = 0.3, anchor = CENTER) 473 | 474 | def place_simultaneous_downloads_textbox(): 475 | cpu_button = create_info_button(open_info_simultaneous_downloads, "Simultaneous downloads") 476 | cpu_textbox = create_text_box(selected_cpu_number, 110, 32) 477 | 478 | cpu_button.place(relx = 0.42, rely = 0.42, anchor = CENTER) 479 | cpu_textbox.place(relx = 0.75, rely = 0.42, anchor = CENTER) 480 | 481 | def place_tips(): 482 | tips_button = create_info_button(open_info_tips, "Connection tips", width = 110) 483 | tips_button.place(relx = 0.8, rely = 0.9, anchor = CENTER) 484 | 485 | def place_message_label(): 486 | message_label = CTkLabel( 487 | master = window, 488 | textvariable = info_message, 489 | height = 25, 490 | font = bold11, 491 | fg_color = "#ffbf00", 492 | text_color = "#000000", 493 | anchor = "center", 494 | corner_radius = 25 495 | ) 496 | message_label.place(relx = 0.5, rely = 0.78, anchor = CENTER) 497 | 498 | def place_download_button(): 499 | download_button = CTkButton( 500 | master = window, 501 | command = download_button_command, 502 | text = "DOWNLOAD", 503 | image = download_icon, 504 | width = 140, 505 | height = 30, 506 | font = bold11, 507 | border_width = 1, 508 | fg_color = "#282828", 509 | text_color = "#E0E0E0", 510 | border_color = "#0096FF" 511 | ) 512 | download_button.place(relx = 0.5, rely = 0.9, anchor = CENTER) 513 | 514 | def place_stop_button(): 515 | stop_button = CTkButton( 516 | master = window, 517 | command = stop_button_command, 518 | text = "STOP", 519 | image = stop_icon, 520 | width = 140, 521 | height = 30, 522 | font = bold11, 523 | border_width = 1, 524 | fg_color = "#282828", 525 | text_color = "#E0E0E0", 526 | border_color = "#0096FF" 527 | ) 528 | stop_button.place(relx = 0.5, rely = 0.9, anchor = CENTER) 529 | 530 | 531 | 532 | # Main/GUI functions --------------------------- 533 | 534 | def on_app_close() -> None: 535 | window.grab_release() 536 | window.destroy() 537 | stop_download_process() 538 | 539 | class CTkMessageBox(CTkToplevel): 540 | 541 | def __init__( 542 | self, 543 | messageType: str, 544 | title: str, 545 | subtitle: str, 546 | default_value: str, 547 | option_list: list, 548 | ) -> None: 549 | 550 | super().__init__() 551 | 552 | self._running: bool = False 553 | 554 | self._messageType = messageType 555 | self._title = title 556 | self._subtitle = subtitle 557 | self._default_value = default_value 558 | self._option_list = option_list 559 | self._ctkwidgets_index = 0 560 | 561 | self.title('') 562 | self.lift() # lift window on top 563 | self.attributes("-topmost", True) # stay on top 564 | self.protocol("WM_DELETE_WINDOW", self._on_closing) 565 | self.after(10, self._create_widgets) # create widgets with slight delay, to avoid white flickering of background 566 | self.resizable(False, False) 567 | self.grab_set() # make other windows not clickable 568 | 569 | def _ok_event( 570 | self, 571 | event = None 572 | ) -> None: 573 | self.grab_release() 574 | self.destroy() 575 | 576 | def _on_closing( 577 | self 578 | ) -> None: 579 | self.grab_release() 580 | self.destroy() 581 | 582 | def createEmptyLabel( 583 | self 584 | ) -> CTkLabel: 585 | 586 | return CTkLabel(master = self, 587 | fg_color = "transparent", 588 | width = 500, 589 | height = 17, 590 | text = '') 591 | 592 | def placeInfoMessageTitleSubtitle( 593 | self, 594 | ) -> None: 595 | 596 | spacingLabel1 = self.createEmptyLabel() 597 | spacingLabel2 = self.createEmptyLabel() 598 | 599 | if self._messageType == "info": 600 | title_subtitle_text_color = "#3399FF" 601 | elif self._messageType == "error": 602 | title_subtitle_text_color = "#FF3131" 603 | 604 | titleLabel = CTkLabel( 605 | master = self, 606 | width = 500, 607 | anchor = 'w', 608 | justify = "left", 609 | fg_color = "transparent", 610 | text_color = title_subtitle_text_color, 611 | font = bold22, 612 | text = self._title 613 | ) 614 | 615 | if self._default_value != None: 616 | defaultLabel = CTkLabel( 617 | master = self, 618 | width = 500, 619 | anchor = 'w', 620 | justify = "left", 621 | fg_color = "transparent", 622 | text_color = "#3399FF", 623 | font = bold17, 624 | text = f"Default: {self._default_value}" 625 | ) 626 | 627 | subtitleLabel = CTkLabel( 628 | master = self, 629 | width = 500, 630 | anchor = 'w', 631 | justify = "left", 632 | fg_color = "transparent", 633 | text_color = title_subtitle_text_color, 634 | font = bold14, 635 | text = self._subtitle 636 | ) 637 | 638 | spacingLabel1.grid(row = self._ctkwidgets_index, column = 0, columnspan = 2, padx = 0, pady = 0, sticky = "ew") 639 | 640 | self._ctkwidgets_index += 1 641 | titleLabel.grid(row = self._ctkwidgets_index, column = 0, columnspan = 2, padx = 25, pady = 0, sticky = "ew") 642 | 643 | if self._default_value != None: 644 | self._ctkwidgets_index += 1 645 | defaultLabel.grid(row = self._ctkwidgets_index, column = 0, columnspan = 2, padx = 25, pady = 0, sticky = "ew") 646 | 647 | self._ctkwidgets_index += 1 648 | subtitleLabel.grid(row = self._ctkwidgets_index, column = 0, columnspan = 2, padx = 25, pady = 0, sticky = "ew") 649 | 650 | self._ctkwidgets_index += 1 651 | spacingLabel2.grid(row = self._ctkwidgets_index, column = 0, columnspan = 2, padx = 0, pady = 0, sticky = "ew") 652 | 653 | def placeInfoMessageOptionsText( 654 | self, 655 | ) -> None: 656 | 657 | for option_text in self._option_list: 658 | optionLabel = CTkLabel(master = self, 659 | width = 600, 660 | height = 45, 661 | corner_radius = 6, 662 | anchor = 'w', 663 | justify = "left", 664 | text_color = "#C0C0C0", 665 | fg_color = "#282828", 666 | bg_color = "transparent", 667 | font = bold12, 668 | text = option_text) 669 | 670 | self._ctkwidgets_index += 1 671 | optionLabel.grid(row = self._ctkwidgets_index, column = 0, columnspan = 2, padx = 25, pady = 4, sticky = "ew") 672 | 673 | spacingLabel3 = self.createEmptyLabel() 674 | 675 | self._ctkwidgets_index += 1 676 | spacingLabel3.grid(row = self._ctkwidgets_index, column = 0, columnspan = 2, padx = 0, pady = 0, sticky = "ew") 677 | 678 | def placeInfoMessageOkButton( 679 | self 680 | ) -> None: 681 | 682 | ok_button = CTkButton( 683 | master = self, 684 | command = self._ok_event, 685 | text = 'OK', 686 | width = 125, 687 | font = bold11, 688 | border_width = 1, 689 | fg_color = "#282828", 690 | text_color = "#E0E0E0", 691 | border_color = "#0096FF" 692 | ) 693 | 694 | self._ctkwidgets_index += 1 695 | ok_button.grid(row = self._ctkwidgets_index, column = 1, columnspan = 1, padx = (10, 20), pady = (10, 20), sticky = "e") 696 | 697 | def _create_widgets( 698 | self 699 | ) -> None: 700 | 701 | self.grid_columnconfigure((0, 1), weight=1) 702 | self.rowconfigure(0, weight=1) 703 | 704 | self.placeInfoMessageTitleSubtitle() 705 | self.placeInfoMessageOptionsText() 706 | self.placeInfoMessageOkButton() 707 | 708 | def create_info_button( 709 | command: Callable, 710 | text: str, 711 | width: int = 150 712 | ) -> CTkButton: 713 | 714 | return CTkButton( 715 | master = window, 716 | command = command, 717 | text = text, 718 | fg_color = "transparent", 719 | hover_color = "#181818", 720 | text_color = "#C0C0C0", 721 | anchor = "w", 722 | height = 22, 723 | width = width, 724 | corner_radius = 10, 725 | font = bold12, 726 | image = info_icon 727 | ) 728 | 729 | def create_text_box(textvariable, width, heigth): 730 | return CTkEntry( 731 | master = window, 732 | textvariable = textvariable, 733 | border_width = 1, 734 | width = width, 735 | height = heigth, 736 | font = bold11, 737 | justify = "center", 738 | fg_color = "#000000", 739 | border_color = "#404040" 740 | ) 741 | 742 | 743 | 744 | class App: 745 | def __init__(self, window): 746 | window.title('') 747 | width = 500 748 | height = 500 749 | window.geometry("500x500") 750 | window.minsize(width, height) 751 | window.resizable(False, False) 752 | window.iconbitmap(find_by_relative_path("Assets" + os_separator + "logo.ico")) 753 | 754 | window.protocol("WM_DELETE_WINDOW", on_app_close) 755 | 756 | place_app_name() 757 | place_qualityscaler_button() 758 | place_github_button() 759 | place_telegram_button() 760 | place_link_textbox() 761 | place_check_button() 762 | place_simultaneous_downloads_textbox() 763 | place_tips() 764 | place_message_label() 765 | place_download_button() 766 | 767 | if __name__ == "__main__": 768 | multiprocessing_freeze_support() 769 | 770 | set_appearance_mode("Dark") 771 | set_default_color_theme("dark-blue") 772 | 773 | processing_queue = multiprocessing_Queue(maxsize=1) 774 | 775 | window = CTk() 776 | 777 | selected_url = StringVar() 778 | info_message = StringVar() 779 | selected_cpu_number = StringVar() 780 | 781 | selected_url.set("Paste link here https://fapello.com/emily-rat---/") 782 | selected_cpu_number.set("6") 783 | info_message.set("Hi :)") 784 | 785 | font = "Segoe UI" 786 | bold8 = CTkFont(family = font, size = 8, weight = "bold") 787 | bold9 = CTkFont(family = font, size = 9, weight = "bold") 788 | bold10 = CTkFont(family = font, size = 10, weight = "bold") 789 | bold11 = CTkFont(family = font, size = 11, weight = "bold") 790 | bold12 = CTkFont(family = font, size = 12, weight = "bold") 791 | bold13 = CTkFont(family = font, size = 13, weight = "bold") 792 | bold14 = CTkFont(family = font, size = 14, weight = "bold") 793 | bold16 = CTkFont(family = font, size = 16, weight = "bold") 794 | bold17 = CTkFont(family = font, size = 17, weight = "bold") 795 | bold18 = CTkFont(family = font, size = 18, weight = "bold") 796 | bold19 = CTkFont(family = font, size = 19, weight = "bold") 797 | bold20 = CTkFont(family = font, size = 20, weight = "bold") 798 | bold21 = CTkFont(family = font, size = 21, weight = "bold") 799 | bold22 = CTkFont(family = font, size = 22, weight = "bold") 800 | bold23 = CTkFont(family = font, size = 23, weight = "bold") 801 | bold24 = CTkFont(family = font, size = 24, weight = "bold") 802 | 803 | # Images 804 | logo_git = CTkImage(pillow_image_open(find_by_relative_path(f"Assets{os_separator}github_logo.png")), size=(15, 15)) 805 | logo_telegram = CTkImage(pillow_image_open(find_by_relative_path(f"Assets{os_separator}telegram_logo.png")), size=(15, 15)) 806 | stop_icon = CTkImage(pillow_image_open(find_by_relative_path(f"Assets{os_separator}stop_icon.png")), size=(15, 15)) 807 | info_icon = CTkImage(pillow_image_open(find_by_relative_path(f"Assets{os_separator}info_icon.png")), size=(14, 14)) 808 | download_icon = CTkImage(pillow_image_open(find_by_relative_path(f"Assets{os_separator}download_icon.png")), size=(15, 15)) 809 | logo_qs = CTkImage(pillow_image_open(find_by_relative_path(f"Assets{os_separator}qs_logo.png")), size=(15, 15)) 810 | 811 | app = App(window) 812 | window.update() 813 | window.mainloop() 814 | 815 | 816 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Annunziata Gianluca 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Fapello.Downloader LICENSE 2 | 3 | 13/09/2024 4 | 5 | Disclaimer of Liability 6 | 7 | Please read this disclaimer carefully before using our app. By using the app, you agree to be bound by the terms and conditions stated below. If you do not agree to these terms, do not use the application. 8 | 9 | Usage Responsibility: 10 | The app allows users to download and access various types of content. The user assumes full responsibility for the use of the app and any content downloaded through it. The app developer disclaims any liability for how users choose to use the app and the consequences of such use. 11 | 12 | Content Ownership: 13 | The app developer does not claim ownership of the content downloaded through the app. Users are solely responsible for ensuring that the downloaded content complies with copyright laws, licenses, and terms of use of the respective content owners. 14 | 15 | No Legal Advice: 16 | The app developer is not a legal expert and does not provide legal advice. The app's information and functionality are provided for general informational purposes only and should not be considered legal advice. Users should consult appropriate legal professionals for legal matters. 17 | 18 | Indemnification: 19 | To the extent permitted by law, users agree to indemnify and hold harmless the app developer, its affiliates, employees, and partners from any claims, losses, liabilities, damages, expenses, or demands arising from their use of the app, including any violation of applicable laws or rights of third parties. 20 | 21 | Limitation of Liability: 22 | In no event shall the app developer be liable for any direct, indirect, incidental, special, consequential, or punitive damages arising out of or in connection with the use or inability to use the app, even if advised of the possibility of such damages. 23 | 24 | Changes to the Disclaimer: 25 | The app developer reserves the right to modify or update this disclaimer at any time without prior notice. It is the user's responsibility to review this disclaimer periodically for changes. 26 | 27 | By using this app, you acknowledge that you have read and understood this disclaimer and agree to its terms. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

Fapello.Downloader - NSFW images/videos downloader app

5 | 6 | 9 | 10 |
11 |
12 |
13 | 14 |
15 | 16 | ## Other projects.🤓 17 | 18 | - https://github.com/Djdefrag/QualityScaler / QualityScaler - image/video AI upscaler 19 | - https://github.com/Djdefrag/RealScaler / RealScaler - image/video AI upscaler app (Real-ESRGAN) 20 | - https://github.com/Djdefrag/FluidFrames.RIFE / FluidFrames.RIFE - video AI frame generation 21 | 22 | 23 | ## How is made. 🛠 24 | 25 | Fapello.Downloader is completely written in Python, from backend to frontend. External packages are: 26 | - [ ] Core -> beautifulsoup / requests 27 | - [ ] GUI -> customtkinter 28 | - [ ] Packaging -> pyinstaller 29 | 30 | ## How to use. 👨‍💻 31 | * Copy the Fapello link of interest (for example: https://fapello.com/mia-kha***/) 32 | * Paste the copied link in FapelloDownloader textbox 33 | * Press Download button 34 | * Wait for the download to complete 35 | * A folder will be created with all images/videos 36 | 37 | ## Next steps. 🤫 38 | - [ ] Update libraries 39 | - [x] Python 3.10 (expecting ~10% more performance) 40 | - [x] Python 3.11 (expecting ~30% more performance) 41 | 42 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # CORE 2 | beautifulsoup4 3 | requests 4 | Pillow 5 | 6 | # GUI 7 | customtkinter 8 | 9 | # UTIL 10 | pyinstaller 11 | 12 | 13 | 14 | --------------------------------------------------------------------------------