Skip to content

Commit

Permalink
Automatic check key retrieval.
Browse files Browse the repository at this point in the history
  • Loading branch information
prof79 committed Mar 22, 2024
1 parent d44f8c3 commit 859d82d
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 91 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ This is a rewrite/refactoring of [Avnsx](https://github.com/Avnsx)'s original [F

⚠️ Due to a [hashing bug](../../issues/13) duplicate videos might be downloaded if a creator re-posts a lot. Downloaded videos will have to be renamed in a future version when video hashing is perfected.

### v0.9.6 2024-03-22

Yay! I coded automatic retrieval of the check key from Fansly's page. Fallback to user input if it fails.
Should the need arise the patterns for locating the JS file and check key are saved/loaded in the `[Logic]` section in `config.ini`.

### v0.9.5 2024-03-22

Fixed headless/non-interactive input blooper during "check key" validation.
Expand Down
5 changes: 5 additions & 0 deletions ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

## 🗒️ Release Notes

### v0.9.6 2024-03-22

Yay! I coded automatic retrieval of the check key from Fansly's page. Fallback to user input if it fails.
Should the need arise the patterns for locating the JS file and check key are saved/loaded in the `[Logic]` section in `config.ini`.

### v0.9.5 2024-03-22

Fixed headless/non-interactive input blooper during "check key" validation.
Expand Down
17 changes: 17 additions & 0 deletions config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,23 @@ def load_config(config: FanslyConfig) -> None:

#endregion Cache

#region Logic

key_default_pattern = r'''this\.checkKey_\s*=\s*["']([^"']+)["']'''
main_js_default_pattern = r'''\ssrc\s*=\s*"(main\..*?\.js)"'''

logic_section = 'Logic'

if not config._parser.has_section(logic_section):
config._parser.add_section(logic_section)

config.check_key_pattern = \
config._parser.get(logic_section, 'check_key_pattern', fallback=key_default_pattern)
config.main_js_pattern = \
config._parser.get(logic_section, 'main_js_pattern', fallback=main_js_default_pattern)

#endregion Logic

# Safe to save! :-)
save_config_or_raise(config)

Expand Down
32 changes: 20 additions & 12 deletions config/fanslyconfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,15 @@ class FanslyConfig(object):
BATCH_SIZE: int = 150

# Configuration file
config_path: Path | None = None
config_path: Optional[Path] = None

# Misc
token_from_browser_name: str | None = None
token_from_browser_name: Optional[str] = None
debug: bool = False
# If specified on the command-line
post_id: str | None = None
post_id: Optional[str] = None
# Set on start after self-update
updated_to: str | None = None
updated_to: Optional[str] = None

# Objects
_parser = ConfigParser(interpolation=None)
Expand All @@ -48,12 +48,12 @@ class FanslyConfig(object):
#region config.ini Fields

# TargetedCreator > username
user_names: set[str] | None = None
user_names: Optional[set[str]] = None

# MyAccount
token: str | None = None
user_agent: str | None = None
check_key: str | None = None
token: Optional[str] = None
user_agent: Optional[str] = None
check_key: Optional[str] = None
#session_id: str = 'null'

# Options
Expand Down Expand Up @@ -87,6 +87,10 @@ class FanslyConfig(object):
cached_device_id: Optional[str] = None
cached_device_id_timestamp: Optional[int] = None

# Logic
check_key_pattern: Optional[str] = None
main_js_pattern: Optional[str] = None

#endregion config.ini

#endregion Fields
Expand Down Expand Up @@ -120,14 +124,14 @@ def get_api(self) -> FanslyApi:
return self._api


def user_names_str(self) -> str | None:
def user_names_str(self) -> Optional[str]:
"""Returns a nicely formatted and alphabetically sorted list of
creator names - for console or config file output.
:return: A single line of all creator names, alphabetically sorted
and separated by commas eg. "alice, bob, chris, dora".
Returns None if user_names is None.
:rtype: str | None
:rtype: Optional[str]
"""
if self.user_names is None:
return None
Expand Down Expand Up @@ -190,6 +194,10 @@ def _sync_settings(self) -> None:
self.cached_device_id = self._api.device_id
self.cached_device_id_timestamp = self._api.device_id_timestamp

# Logic
self._parser.set('Logic', 'check_key_pattern', str(self.check_key_pattern))
self._parser.set('Logic', 'main_js_pattern', str(self.main_js_pattern))


def _load_raw_config(self) -> list[str]:
if self.config_path is None:
Expand Down Expand Up @@ -235,13 +243,13 @@ def useragent_is_valid(self) -> bool:
)


def get_unscrambled_token(self) -> str | None:
def get_unscrambled_token(self) -> Optional[str]:
"""Gets the unscrambled Fansly authorization token.
Unscrambles the token if necessary.
:return: The unscrambled Fansly authorization token.
:rtype: str | None
:rtype: Optional[str]
"""

if self.token is not None:
Expand Down
25 changes: 23 additions & 2 deletions config/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from pathio.pathio import ask_correct_dir
from textio import print_config, print_error, print_info, print_warning
from utils.common import save_config_or_raise
from utils.web import guess_user_agent, open_get_started_url
from utils.web import guess_check_key, guess_user_agent, open_get_started_url


def validate_creator_names(config: FanslyConfig) -> bool:
Expand Down Expand Up @@ -308,12 +308,33 @@ def validate_adjust_check_key(config: FanslyConfig) -> None:
)
print()

if config.user_agent \
and config.main_js_pattern \
and config.check_key_pattern:

guessed_key = guess_check_key(
config.main_js_pattern,
config.check_key_pattern,
config.user_agent,
)

if guessed_key is not None:
config.check_key = guessed_key
save_config_or_raise(config)

print_config(
f"Check key guessed from Fansly homepage: `{config.check_key}`"
)
print()

return

print_warning(
f"Make sure, checking the main.js sources of the Fansly homepage, "
f"\n{20*' '}that the `this.checkKey_` value is identical to this "
f"\n{20*' '}(text within the single quotes only): `{config.check_key}`"
)

if config.interactive:

key_confirmation = input(
Expand Down
4 changes: 2 additions & 2 deletions fansly_downloader_ng.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

"""Fansly Downloader NG"""

__version__ = '0.9.5'
__date__ = '2024-03-22T19:03:00+01'
__version__ = '0.9.6'
__date__ = '2024-03-22T20:18:00+01'
__maintainer__ = 'prof79'
__copyright__ = f'Copyright (C) 2023-2024 by {__maintainer__}'
__authors__ = [
Expand Down
148 changes: 74 additions & 74 deletions updater/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
import os
import platform
import re
import requests
import subprocess
import sys
# import requests
# import subprocess
# import sys

import errors
# import errors

from pathlib import Path
from pkg_resources._vendor.packaging.version import parse as parse_version
from shutil import unpack_archive
# from shutil import unpack_archive

from config import FanslyConfig
from textio import clear_terminal, print_error, print_info, print_update, print_warning
Expand Down Expand Up @@ -114,85 +114,85 @@ def perform_update(program_version: str, release_info: dict) -> bool:

return False

# If current environment is pure Python prompt user to update Fansly Downloader NG themselves
if not getattr(sys, 'frozen', False):
print_warning(f"To update Fansly Downloader, please download the latest version from the GitHub repository.\n{20*' '}Only executable versions of the downloader receive & apply updates automatically.\n")
# but we don't care if user updates or just wants to see this prompt on every execution further on
return False
# # If current environment is pure Python prompt user to update Fansly Downloader NG themselves
# if not getattr(sys, 'frozen', False):
# print_warning(f"To update Fansly Downloader, please download the latest version from the GitHub repository.\n{20*' '}Only executable versions of the downloader receive & apply updates automatically.\n")
# # but we don't care if user updates or just wants to see this prompt on every execution further on
# return False

# if in executable environment, allow self-update
print_update('Please be patient, automatic update initialized ...')
# # if in executable environment, allow self-update
# print_update('Please be patient, automatic update initialized ...')

# re-name current executable, so that the new version can delete it
binary_name = 'fansly-downloader-ng'
suffix = ''
new_name = 'deprecated_version'
# # re-name current executable, so that the new version can delete it
# binary_name = 'fansly-downloader-ng'
# suffix = ''
# new_name = 'deprecated_version'

if platform.system() == 'Windows':
suffix = '.exe'
binary_name = 'Fansly Downloader NG'

current_binary = Path.cwd() / f'{binary_name}{suffix}'
current_binary.rename(current_binary.parent / f'{new_name}{suffix}')

# Declare new release filepath
new_release_archive = Path.cwd() / release_info['release_name']

CHUNK_SIZE = 1_048_576

# download new release
with requests.get(
release_info['download_url'],
allow_redirects=True,
headers = {
'user-agent': f'Fansly Downloader NG {program_version}',
'accept-language': 'en-US,en;q=0.9'
},
stream=True,
) as release_download:

if release_download.status_code != 200:
print_error(f"Failed downloading latest build. Status code: {release_download.status_code} | Body: \n{release_download.text}")
return False

# write to disk
with open(new_release_archive, 'wb') as f:
for chunk in release_download.iter_content(CHUNK_SIZE):
if chunk:
f.write(chunk)
# if platform.system() == 'Windows':
# suffix = '.exe'
# binary_name = 'Fansly Downloader NG'

# current_binary = Path.cwd() / f'{binary_name}{suffix}'
# current_binary.rename(current_binary.parent / f'{new_name}{suffix}')

# # Declare new release filepath
# new_release_archive = Path.cwd() / release_info['release_name']

# CHUNK_SIZE = 1_048_576

# # download new release
# with requests.get(
# release_info['download_url'],
# allow_redirects=True,
# headers = {
# 'user-agent': f'Fansly Downloader NG {program_version}',
# 'accept-language': 'en-US,en;q=0.9'
# },
# stream=True,
# ) as release_download:

# if release_download.status_code != 200:
# print_error(f"Failed downloading latest build. Status code: {release_download.status_code} | Body: \n{release_download.text}")
# return False

# # write to disk
# with open(new_release_archive, 'wb') as f:
# for chunk in release_download.iter_content(CHUNK_SIZE):
# if chunk:
# f.write(chunk)

# must be a common archive format (.zip, .tar, .tar.gz, .tar.bz2, etc.)
print_update('Unpacking new files ...')
unpack_archive(new_release_archive)

# remove .zip leftovers
new_release_archive.unlink()

# start executable from just downloaded latest platform compatible release, with a start argument
# which instructs it to delete old executable & display release notes for newest version
additional_arguments = ['--updated-to', release_info['release_version']]
# Carry command-line arguments over
arguments = sys.argv[1:] + additional_arguments
# # must be a common archive format (.zip, .tar, .tar.gz, .tar.bz2, etc.)
# print_update('Unpacking new files ...')
# unpack_archive(new_release_archive)

# # remove .zip leftovers
# new_release_archive.unlink()

# # start executable from just downloaded latest platform compatible release, with a start argument
# # which instructs it to delete old executable & display release notes for newest version
# additional_arguments = ['--updated-to', release_info['release_version']]
# # Carry command-line arguments over
# arguments = sys.argv[1:] + additional_arguments

current_platform = platform.system()
# current_platform = platform.system()

if current_platform == 'Windows':
# i'm open for improvement suggestions, which will be insensitive to file paths & succeed passing start arguments to compiled executables
subprocess.run(['powershell', '-Command', f"Start-Process -FilePath '{current_binary}' -ArgumentList {', '.join(arguments)}"], shell=True)
# if current_platform == 'Windows':
# # i'm open for improvement suggestions, which will be insensitive to file paths & succeed passing start arguments to compiled executables
# subprocess.run(['powershell', '-Command', f"Start-Process -FilePath '{current_binary}' -ArgumentList {', '.join(arguments)}"], shell=True)

elif current_platform == 'Linux':
# still sensitive to file paths?
subprocess.run([current_binary, *arguments], shell=True)
# elif current_platform == 'Linux':
# # still sensitive to file paths?
# subprocess.run([current_binary, *arguments], shell=True)

elif current_platform == 'Darwin':
# still sensitive to file paths?
subprocess.run(['open', current_binary, *arguments], shell=False)
# elif current_platform == 'Darwin':
# # still sensitive to file paths?
# subprocess.run(['open', current_binary, *arguments], shell=False)

else:
input(f"Platform {current_platform} not supported for auto-update, please update manually instead.")
os._exit(errors.UPDATE_MANUALLY)
# else:
# input(f"Platform {current_platform} not supported for auto-update, please update manually instead.")
# os._exit(errors.UPDATE_MANUALLY)

os._exit(errors.UPDATE_SUCCESS)
# os._exit(errors.UPDATE_SUCCESS)


def post_update_steps(program_version: str, release_info: dict | None) -> None:
Expand Down
Loading

0 comments on commit 859d82d

Please sign in to comment.