Compare commits

...

318 Commits

Author SHA1 Message Date
Roy 215ed34388 Start Fresh will now attempt to remove Steam Shortcuts
Added functions to clean environment variables and run the NSLGameScanner script. Updated deletion logic for various directories and files.
2026-06-12 14:41:18 -07:00
Roy e5bfb5f501 Add fcntl import to NSLGameScanner.py 2026-06-11 18:56:02 -07:00
Roy de1ae75c16 moved lock 2026-06-11 18:52:32 -07:00
Roy b6aaff2959 Implement single instance check with lock file
Add single_instance function to prevent multiple instances.
2026-06-11 18:46:02 -07:00
Roy 07c1dc88e6 Merge pull request #932 from superness/main
fix: retry WebSocket connection on first run instead of silent exit (#895)
2026-06-10 13:10:53 -07:00
superness bb73939339 fix: retry WebSocket connection on first run instead of silent exit
fetch_targets() called sys.exit(0) on ConnectionRefusedError. Steam's CEF
debugger on port 8080 is not always ready on first run, so the scanner exited
silently and added no shortcuts (issue #895, first-run half).

Replace with retry + exponential backoff (15 attempts, 2s base, 10s cap),
print progress so the retry is visible, and raise the last error instead of
exiting silently.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-10 13:22:08 -04:00
Roy 34e0bf1f87 Refactor .desktop file deletion logic 2026-06-10 03:47:48 -07:00
Roy 5504eda0f6 Enhance Battle.net config parsing and game handling
Refactor Battle.net game parsing logic and improve key handling.
2026-06-10 02:54:36 -07:00
Roy 445cfafb32 Remove scan state reset logic from NSLGameScanner
Removed code for resetting scan state from the gate file.
2026-06-10 01:26:22 -07:00
Roy 3fd414caec Add files via upload 2026-06-09 23:58:20 -07:00
Roy c0f0fcc68a Update README.md 2026-06-09 14:05:51 -07:00
Roy 067b9f861f Add Boosteroid URL processing to scanner 2026-06-09 13:58:53 -07:00
Roy a558488b0d Add pkill for wineserver in NonSteamLaunchers.sh
Terminate wineserver process after waiting.
2026-06-09 03:02:55 -07:00
Roy 6399dff8cc Update installation completion messages in NonSteamLaunchers.sh
Removed old progress echo statements and added new ones for installation completion.
2026-06-09 02:38:42 -07:00
Roy 79a7d2a44e Refactor Rockstar Games Launcher installation script 2026-06-09 02:14:21 -07:00
Roy edfcb32c14 Enable pipefail option for error handling 2026-06-09 01:41:54 -07:00
Roy 6e0c22411b Comment out set -o pipefail in NonSteamLaunchers.sh
Comment out pipefail option for debugging purposes
2026-06-09 01:20:15 -07:00
Roy 4cf91b1320 Refactor post-install command execution
Removed sleep and pkill commands from post-install process.
2026-06-09 01:12:32 -07:00
Roy 809bd92694 Adjust post-install timing with sleep command
Replaced wait command with sleep for better timing control.
2026-06-09 01:00:36 -07:00
Roy 09ce5ce133 Refactor zenity progress command in NonSteamLaunchers.sh
Updated zenity progress command formatting for clarity.
2026-06-09 00:41:56 -07:00
Roy 08c46611d7 Refactor NSLGameScanner.py by removing commented code
Removed unused Python version variable and related code.
2026-06-08 14:38:51 -07:00
Roy 73511e820c Clean up unused vdf module setup code
Removed commented-out code for downloading and setting up the vdf module, including directory creation and environment variable setup.
2026-06-08 14:37:28 -07:00
Roy f62ad4a8cf Update version number to v4.2.91 2026-06-08 03:11:19 -07:00
Roy 4f552c5b3c Refactor injection logic with tryInject function 2026-06-08 03:07:27 -07:00
Roy 579cdfdef0 Refactor game mode detection and injection logic 2026-06-08 02:35:13 -07:00
Roy d9afa910c0 Update NonSteamLaunchers.sh 2026-06-07 22:40:26 -07:00
Roy 426fb47c24 Refine NSL Game Scanner completion messages
Updated NSL Game Scanner status messages based on scan state.
2026-06-07 21:55:58 -07:00
Roy 6497af39aa Fix EA App installation command syntax 2026-06-07 15:48:16 -07:00
Roy c1731b8765 Add GOG Galaxy to Ubisoft Connect launcher check 2026-06-07 06:10:16 -07:00
Roy 03a5065f63 Comment out manifest file creation for Steam app
Commented out the call to create_steam_store_app_manifest_file function.
2026-06-07 06:00:00 -07:00
Roy cafbc388dc Merge pull request #927 from moraroy/dependabot/pip/idna-3.18
Bump idna from 3.17 to 3.18
2026-06-07 05:56:12 -07:00
Roy 5043e420b6 Comment out process termination for EADesktop.exe
Comment out the termination of EADesktop.exe process before installing EA App.
2026-06-07 05:25:23 -07:00
Roy a6708f3b61 Update EA Desktop launcher paths
Updated EA Desktop launcher paths to version 13.720.0.6233.
2026-06-07 04:06:42 -07:00
Roy 4c6338ff63 Update itch.io paths to new version
Updated itch.io executable paths to version 26.13.0.
2026-06-07 03:46:30 -07:00
Roy bdf4f57b13 Add NSL_SCAN_STATE to env_file if not present
Ensure NSL_SCAN_STATE is added to env_file if missing.
2026-06-07 03:34:14 -07:00
Roy 1345e75e35 Refactor NSL_SCAN_STATE check in NonSteamLaunchers.sh
Refactor NSL_SCAN_STATE check to use a variable for file path and add confirmation message.
2026-06-07 03:25:45 -07:00
Roy 3b9228c7e2 Modify NSL_SCAN_STATE export logic
Check if NSL_SCAN_STATE is already set before adding it to env_vars.
2026-06-07 02:55:27 -07:00
Roy 7d5aad2b32 Added new feature...NSL game scanner integration. 2026-06-07 02:13:32 -07:00
Roy e9c657edf1 Fix formatting and improve NSLGameScanner restart logic 2026-06-07 02:06:27 -07:00
dependabot[bot] ff32ed3ef9 Bump idna from 3.17 to 3.18
Bumps [idna](https://github.com/kjd/idna) from 3.17 to 3.18.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.md)
- [Commits](https://github.com/kjd/idna/compare/v3.17...v3.18)

---
updated-dependencies:
- dependency-name: idna
  dependency-version: '3.18'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-06-06 15:02:28 +00:00
Roy cd124132cc Enhance fetch_targets with error handling and timeout
Added error handling and timeout to fetch_targets function.
2026-06-06 07:08:45 -07:00
Roy be8bd97c55 Add NSL_SCAN_STATE environment variable export 2026-06-06 06:10:04 -07:00
Roy e9d635cf07 Update launcher detection for NonSteamLaunchers 2026-06-05 03:07:21 -07:00
Roy c8a11ebd84 Refactor UMU shortcut modification logic
Added a normalization function to standardize codename comparisons and updated the shortcut modification logic to handle UMU entries more robustly.
2026-06-05 00:57:03 -07:00
Roy 4fede75a6e Uncomment code to add new entry to shortcuts
fixed duplication
2026-06-04 23:49:11 -07:00
Roy 65fb8debb8 Refactor game metadata to allow null values
Updated game metadata handling to allow null values for developer and publisher fields instead of defaulting to 'Unknown'. Adjusted platform handling to return null instead of 'Unknown' when no data is available.
2026-06-02 03:40:00 -07:00
Roy ee46328a73 Enhance README with additional features and details
Updated README to include player count information and clarify .desktop file creation.
2026-06-01 05:20:23 -07:00
Roy 800b6dbd04 Remove console logs for game details
Removed debug console logs for game information.
2026-06-01 05:18:21 -07:00
Roy e64c6abdc5 Added Player Count to Metadata 2026-06-01 05:11:54 -07:00
Roy d2b4778919 Merge pull request #923 from moraroy/dependabot/pip/idna-3.17
Bump idna from 3.16 to 3.17
2026-05-30 14:02:29 -07:00
dependabot[bot] 66f0a99630 Bump idna from 3.16 to 3.17
Bumps [idna](https://github.com/kjd/idna) from 3.16 to 3.17.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.md)
- [Commits](https://github.com/kjd/idna/compare/v3.16...v3.17)

---
updated-dependencies:
- dependency-name: idna
  dependency-version: '3.17'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-30 15:02:31 +00:00
Roy 983ac18514 Remove unnecessary systemctl commands
Removed systemctl commands for daemon reload and restart.
2026-05-29 00:49:13 -07:00
Roy dff6045347 Reload and restart plugin_loader after installation
Added systemctl commands to reload and restart plugin_loader after installation.
2026-05-28 22:58:26 -07:00
Roy 3bab8fc87a Revise README instructions for Decky Loader setup
Updated instructions for launching Decky Loader and added information on startup.
2026-05-28 04:00:01 -07:00
Roy 0b0ff25a6b Bump version from v4.2.89 to v4.2.90 2026-05-28 02:41:19 -07:00
Roy bb993af3c2 Refactor launcher icons to include dynamic URLs
Metadata: now you can click launcher icon to go to game page websites, Added gamingonsteam, speedrun, Steamcharts, SteamInputDB to metadata links
2026-05-28 02:33:21 -07:00
Roy d6340ec9bc Refactor launcher icon handling in NSLGameScanner 2026-05-24 00:27:12 -07:00
Roy 9f9e3bfc98 Merge pull request #921 from moraroy/dependabot/pip/idna-3.16
Bump idna from 3.15 to 3.16
2026-05-23 20:24:32 -07:00
dependabot[bot] 034def7b27 Bump idna from 3.15 to 3.16
Bumps [idna](https://github.com/kjd/idna) from 3.15 to 3.16.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.md)
- [Commits](https://github.com/kjd/idna/compare/v3.15...v3.16)

---
updated-dependencies:
- dependency-name: idna
  dependency-version: '3.16'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-24 03:22:54 +00:00
Roy e873366177 Merge pull request #920 from moraroy/dependabot/pip/certifi-2026.5.20
Bump certifi from 2026.4.22 to 2026.5.20
2026-05-23 20:20:44 -07:00
dependabot[bot] 6601ab0b7f Bump certifi from 2026.4.22 to 2026.5.20
Bumps [certifi](https://github.com/certifi/python-certifi) from 2026.4.22 to 2026.5.20.
- [Commits](https://github.com/certifi/python-certifi/compare/2026.04.22...2026.05.20)

---
updated-dependencies:
- dependency-name: certifi
  dependency-version: 2026.5.20
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-23 15:02:33 +00:00
Roy 11a6bdab61 Merge pull request #918 from moraroy/dependabot/pip/idna-3.15
Bump idna from 3.13 to 3.15
2026-05-17 13:38:05 -07:00
Roy 9ee61c9013 Merge pull request #917 from moraroy/dependabot/pip/requests-2.34.2
Bump requests from 2.33.1 to 2.34.2
2026-05-17 13:33:35 -07:00
dependabot[bot] 880b1e92eb Bump idna from 3.13 to 3.15
Bumps [idna](https://github.com/kjd/idna) from 3.13 to 3.15.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.md)
- [Commits](https://github.com/kjd/idna/compare/v3.13...v3.15)

---
updated-dependencies:
- dependency-name: idna
  dependency-version: '3.15'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-16 15:02:55 +00:00
dependabot[bot] 524100bc02 Bump requests from 2.33.1 to 2.34.2
Bumps [requests](https://github.com/psf/requests) from 2.33.1 to 2.34.2.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.33.1...v2.34.2)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.34.2
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-16 15:02:52 +00:00
Roy 5cf6e879a0 Update EA Desktop launcher paths and cleanup
Updated EA Desktop paths and removed temporary EA fix code.
2026-05-14 02:31:10 -07:00
Roy 9fdc622f55 Merge pull request #915 from moraroy/dependabot/pip/urllib3-2.7.0
Bump urllib3 from 2.6.3 to 2.7.0
2026-05-11 19:19:18 -07:00
dependabot[bot] 60613b3e92 Bump urllib3 from 2.6.3 to 2.7.0
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.3 to 2.7.0.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.6.3...2.7.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.7.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-09 15:02:40 +00:00
Roy 56b91d49e6 Update GitHub stats images in README
Release Please / release-please (push) Has been cancelled
2026-05-09 03:07:35 -07:00
Roy 2efe4f3843 Refactor bottom links layout and add scrolling arrows
Release Please / release-please (push) Has been cancelled
Adjusted styles for bottom links and added scroll arrows.
2026-05-06 01:18:21 -07:00
Roy 007c54446f Revise YouTube and article sections in README
Updated YouTube and article links with new formatting.
2026-05-06 01:15:16 -07:00
Roy 8cd30e72f3 Merge pull request #908 from moraroy/dependabot/pip/certifi-2026.4.22
Release Please / release-please (push) Has been cancelled
Bump certifi from 2026.2.25 to 2026.4.22
2026-05-04 00:02:28 -07:00
dependabot[bot] c6866c4a94 Bump certifi from 2026.2.25 to 2026.4.22
Bumps [certifi](https://github.com/certifi/python-certifi) from 2026.2.25 to 2026.4.22.
- [Commits](https://github.com/certifi/python-certifi/compare/2026.02.25...2026.04.22)

---
updated-dependencies:
- dependency-name: certifi
  dependency-version: 2026.4.22
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-04 06:58:30 +00:00
Roy 1570e1ae5d Merge pull request #909 from moraroy/dependabot/pip/idna-3.13
Bump idna from 3.11 to 3.13
2026-05-03 23:57:24 -07:00
dependabot[bot] 14ceba5d45 Bump idna from 3.11 to 3.13
Bumps [idna](https://github.com/kjd/idna) from 3.11 to 3.13.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst)
- [Commits](https://github.com/kjd/idna/compare/v3.11...v3.13)

---
updated-dependencies:
- dependency-name: idna
  dependency-version: '3.13'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-25 15:02:38 +00:00
Roy 2249350107 Merge pull request #905 from moraroy/dependabot/pip/rich-gte-14.0.0-and-lt-16
Release Please / release-please (push) Has been cancelled
Update rich requirement from <15,>=14.0.0 to >=14.0.0,<16
2026-04-21 04:14:55 -07:00
dependabot[bot] 34378716a9 Update rich requirement from <15,>=14.0.0 to >=14.0.0,<16
Updates the requirements on [rich](https://github.com/Textualize/rich) to permit the latest version.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v14.0.0...v15.0.0)

---
updated-dependencies:
- dependency-name: rich
  dependency-version: 15.0.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-18 15:02:33 +00:00
Roy e7a918d62d Add files via upload
Release Please / release-please (push) Has been cancelled
2026-04-12 05:33:01 -07:00
Roy dcfa6cef44 Merge pull request #899 from moraroy/dependabot/pip/charset-normalizer-3.4.7
Release Please / release-please (push) Has been cancelled
Bump charset-normalizer from 3.4.6 to 3.4.7
2026-04-07 03:57:14 -07:00
dependabot[bot] d1a371494a Bump charset-normalizer from 3.4.6 to 3.4.7
Bumps [charset-normalizer](https://github.com/jawah/charset_normalizer) from 3.4.6 to 3.4.7.
- [Release notes](https://github.com/jawah/charset_normalizer/releases)
- [Changelog](https://github.com/jawah/charset_normalizer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jawah/charset_normalizer/compare/3.4.6...3.4.7)

---
updated-dependencies:
- dependency-name: charset-normalizer
  dependency-version: 3.4.7
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-07 10:55:48 +00:00
Roy dce8cb8b2b Merge pull request #900 from moraroy/dependabot/pip/requests-2.33.1
Bump requests from 2.33.0 to 2.33.1
2026-04-07 03:54:32 -07:00
dependabot[bot] c71d8f718c Bump requests from 2.33.0 to 2.33.1
Bumps [requests](https://github.com/psf/requests) from 2.33.0 to 2.33.1.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.33.0...v2.33.1)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.33.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-04-04 15:02:45 +00:00
Roy 3a0f4c9c50 Merge pull request #894 from moraroy/dependabot/pip/requests-2.33.0
Release Please / release-please (push) Has been cancelled
Bump requests from 2.32.5 to 2.33.0
2026-03-31 02:59:05 -07:00
dependabot[bot] 28916c9d68 Bump requests from 2.32.5 to 2.33.0
Bumps [requests](https://github.com/psf/requests) from 2.32.5 to 2.33.0.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.5...v2.33.0)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.33.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-28 15:02:37 +00:00
Roy adee0aa156 Merge pull request #891 from moraroy/dependabot/pip/charset-normalizer-3.4.6
Release Please / release-please (push) Has been cancelled
Bump charset-normalizer from 3.4.5 to 3.4.6
2026-03-26 04:08:26 -07:00
dependabot[bot] 80415a9009 Bump charset-normalizer from 3.4.5 to 3.4.6
Bumps [charset-normalizer](https://github.com/jawah/charset_normalizer) from 3.4.5 to 3.4.6.
- [Release notes](https://github.com/jawah/charset_normalizer/releases)
- [Changelog](https://github.com/jawah/charset_normalizer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jawah/charset_normalizer/compare/3.4.5...3.4.6)

---
updated-dependencies:
- dependency-name: charset-normalizer
  dependency-version: 3.4.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-21 15:02:31 +00:00
Roy a876023ca9 Fix install command for itch.io launcher
Release Please / release-please (push) Has been cancelled
2026-03-17 06:49:08 -07:00
Roy bdc94863cc Fix formatting for itch.io installation command 2026-03-17 06:41:49 -07:00
Roy 2b760d765b Set specific paths for itch.io launcher
Updated itch.io paths to specific version 26.9.0.
2026-03-17 05:54:07 -07:00
Roy 55a4875450 Update version number to v4.2.89
Release Please / release-please (push) Has been cancelled
2026-03-12 01:56:12 -07:00
Roy f113b0039c Fixed icon error 2026-03-12 01:26:04 -07:00
Roy d7cc6cee26 Mark Gryphlink and Super Monkey Ball Online as ✔️ 2026-03-12 00:53:33 -07:00
Roy 460002a926 Added Super Monkey Ball Online to streaming sites 2026-03-12 00:49:28 -07:00
Roy 4bdbf14adf Add 'Super Monkey Ball Online' to game entries 2026-03-12 00:43:23 -07:00
Roy d3d4c763dc Update version number to v4.2.88
Release Please / release-please (push) Has been cancelled
2026-03-10 03:00:53 -07:00
Roy ba8c868a90 Enhance game data with discount information and launcher handling
Added discounted price and discount percent fields to game data. Improved launcher name resolution and icon retrieval logic.
2026-03-10 02:57:05 -07:00
Roy 56c47fd9a9 Merge pull request #886 from moraroy/dependabot/pip/charset-normalizer-3.4.5
Release Please / release-please (push) Has been cancelled
Bump charset-normalizer from 3.4.4 to 3.4.5
2026-03-07 21:36:43 -08:00
dependabot[bot] de234c0a26 Bump charset-normalizer from 3.4.4 to 3.4.5
Bumps [charset-normalizer](https://github.com/jawah/charset_normalizer) from 3.4.4 to 3.4.5.
- [Release notes](https://github.com/jawah/charset_normalizer/releases)
- [Changelog](https://github.com/jawah/charset_normalizer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jawah/charset_normalizer/compare/3.4.4...3.4.5)

---
updated-dependencies:
- dependency-name: charset-normalizer
  dependency-version: 3.4.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-03-08 05:35:29 +00:00
Roy ee7898cbd3 Merge pull request #883 from moraroy/dependabot/pip/certifi-2026.2.25
Bump certifi from 2026.1.4 to 2026.2.25
2026-03-07 21:34:20 -08:00
dependabot[bot] 1636eceb49 Bump certifi from 2026.1.4 to 2026.2.25
Bumps [certifi](https://github.com/certifi/python-certifi) from 2026.1.4 to 2026.2.25.
- [Commits](https://github.com/certifi/python-certifi/compare/2026.01.04...2026.02.25)

---
updated-dependencies:
- dependency-name: certifi
  dependency-version: 2026.2.25
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-28 16:02:41 +00:00
Roy 8a54ca33e3 Merge pull request #880 from svenliebig/patch-1
Release Please / release-please (push) Has been cancelled
docs: small correction in readme
2026-02-23 13:57:26 -08:00
Sven Liebig cec4e691bb docs: small correction in readme
added two missing single semicolons to curl instructions.
2026-02-23 10:33:12 +01:00
Roy fff5f08473 Update Gryphlink installer URL and version
Release Please / release-please (push) Has been cancelled
2026-02-10 00:52:15 -08:00
Roy 398614fe00 Merge pull request #875 from moraroy/dependabot/pip/ruff-gte-0.12.1-and-lt-0.16
Release Please / release-please (push) Has been cancelled
Update ruff requirement from <0.15,>=0.12.1 to >=0.12.1,<0.16
2026-02-08 03:00:03 -08:00
dependabot[bot] ed4a00e6c4 Update ruff requirement from <0.15,>=0.12.1 to >=0.12.1,<0.16
Updates the requirements on [ruff](https://github.com/astral-sh/ruff) to permit the latest version.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.12.1...0.15.0)

---
updated-dependencies:
- dependency-name: ruff
  dependency-version: 0.15.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-02-07 16:02:43 +00:00
Roy b93068acd4 Refactor itch.io paths to use dynamic versioning
Release Please / release-please (push) Has been cancelled
Updated itch.io paths to dynamically find the latest version.
2026-02-07 04:39:52 -08:00
Roy 3c2c7ce602 Refactor fadeOutAndStop and improve audio handling
Release Please / release-please (push) Has been cancelled
Refactor fadeOutAndStop function to accept parameters and improve volume fading logic. Remove unnecessary fadeInterval variable and enhance error handling during YouTube audio playback.
2026-02-03 03:38:29 -08:00
Roy 3a6450a9a3 Refactor theme music handling and improve caching
Release Please / release-please (push) Has been cancelled
2026-02-02 04:16:40 -08:00
Roy 784eac1d2a Refactor playtime data handling in NSLGameScanner 2026-02-02 03:41:21 -08:00
Roy 74ec8c9224 Refactor theme music handling in NSLGameScanner
Release Please / release-please (push) Has been cancelled
2026-02-02 02:39:25 -08:00
Roy 33c06c6947 Add files via upload
Release Please / release-please (push) Has been cancelled
2026-02-01 07:43:10 -08:00
Roy 7a5fb3a386 Update version number to v4.2.87
Release Please / release-please (push) Has been cancelled
2026-01-31 08:11:00 -08:00
Roy 74b9305f91 Update README.md 2026-01-31 05:29:14 -08:00
Roy 7c436eeb1d Added Gryphlink Launcher 2026-01-31 03:19:29 -08:00
Roy ee8b8abec9 Update display name for Endfield to Arknights: Endfield 2026-01-31 02:11:52 -08:00
Roy 1f5fc50062 Refactor game details fetching with caching 2026-01-31 02:05:08 -08:00
Roy fadab483d3 Add files via upload 2026-01-31 02:01:10 -08:00
Roy b12c04c94a Enhance game details with Metacritic information
Release Please / release-please (push) Has been cancelled
Added Metacritic score and URL to game data display.
2026-01-29 04:27:00 -08:00
Roy 0393f36329 Set default theme music to off
Release Please / release-please (push) Has been cancelled
Change default behavior of theme music to off if parsing fails.
2026-01-28 01:54:49 -08:00
Roy 04c11ecc9e Update README.md
Release Please / release-please (push) Has been cancelled
2026-01-27 08:03:23 -08:00
Roy ec7325cb9f Add files via upload 2026-01-27 06:05:55 -08:00
Roy dde79a4e4a Added new feature Metadata for all nonsteam shortcuts 2026-01-27 06:02:40 -08:00
Roy 2bd695a5ed Merge pull request #864 from kronarq/fix-ge-proton-path-issue
Release Please / release-please (push) Has been cancelled
Fix for ProtonUp-Qt installs of GE-Proton
2026-01-24 04:47:55 -08:00
Kronarq 1e18ae1647 Make - between proton & major version optional because ProtonUp-Qt installs to GE-ProtonXX-YY 2026-01-24 05:30:58 -07:00
Roy 5e9d1ccda2 Merge pull request #863 from kronarq/requests-to-urllib-fixes
Fix urllib/requests mismatch in fetch_and_parse_csv fallback
2026-01-24 02:15:24 -08:00
Kronarq 8f8e9c3194 Fix urllib/requests mismatch in fetch_and_parse_csv fallback
The fallback code was incorrectly mixing urllib.request with requests
library methods (.raise_for_status(), .text) and exception handling.
Fixed to properly use urllib patterns.
2026-01-24 02:31:11 -07:00
Roy 472b64c0cd Added Big Fish Games Manager and fixed all SD card code to work again
Release Please / release-please (push) Has been cancelled
2026-01-23 03:09:59 -08:00
Roy 88bc974128 Implement uninstall warning for selected items
Release Please / release-please (push) Has been cancelled
Add warning dialog for uninstalling with selections.
2026-01-22 16:22:59 -08:00
Roy 04bea8d4bd Add Big Fish Games Manager to the list of launchers
Release Please / release-please (push) Has been cancelled
2026-01-22 06:41:57 -08:00
Roy 6f70afe3f7 Update release-please action and add checkout step 2026-01-22 06:35:59 -08:00
Roy 324b39e874 Add files via upload 2026-01-22 06:14:26 -08:00
Roy d29ef42985 Add files via upload
Release Please / release-please (push) Has been cancelled
2026-01-22 05:21:13 -08:00
Roy 746dffbf65 Remove duplicate flatpak override command
Release Please / release-please (push) Has been cancelled
2026-01-13 21:35:04 -08:00
Roy c5279623f0 Added LibreWolf & Vivaldi 2026-01-13 21:19:03 -08:00
Roy 1454fddef3 Added LibreWolf & Vivaldi 2026-01-13 21:17:32 -08:00
Roy 849380e3d4 Update README.md
Release Please / release-please (push) Has been cancelled
2026-01-13 13:43:28 -08:00
Roy a443427e6b Update version to v4.2.84 and add Hytale support 2026-01-13 13:10:09 -08:00
Roy bde517ca40 Add custom launch options for Hytale Launcher 2026-01-13 13:02:32 -08:00
Roy b077d8f9c8 Fix formatting in README.md
Release Please / release-please (push) Has been cancelled
2026-01-12 17:13:04 -08:00
Roy 0767edd423 Fix punctuation and spacing in README description
Corrected punctuation and spacing in the README.
2026-01-12 17:00:12 -08:00
Roy 57f92375fa Use environment variables for chrome directory options
Release Please / release-please (push) Has been cancelled
2026-01-12 02:46:50 -08:00
Roy 310c750071 Update launcher_name to use custom launch options 2026-01-12 02:39:27 -08:00
Roy 920793c5b4 Fix string formatting in detect_browser_name function 2026-01-12 02:28:06 -08:00
Roy 617476347a Refactor create_new_entry calls to use browser_for_env 2026-01-12 02:19:10 -08:00
Roy a245b0305d Add browser_for_env function to retrieve browser options
Added a function to get browser options from environment variables.
2026-01-12 02:07:21 -08:00
Roy 723e12cf2c Fix case sensitivity in browser detection 2026-01-12 01:49:30 -08:00
Roy dc8c2cb71c Add browser detection and update entry creation
Added a function to detect the browser name based on launch options and updated create_new_entry calls to include the detected browser name.
2026-01-12 01:39:42 -08:00
Roy bfe0217e1d Add urllib imports for handling URLs 2026-01-12 01:12:41 -08:00
Roy 0ea9002c70 Refactor imports and update URL handling logic 2026-01-12 01:07:41 -08:00
Roy bce80a0739 Merge pull request #857 from moraroy/dependabot/pip/certifi-2026.1.4
Release Please / release-please (push) Has been cancelled
Bump certifi from 2025.11.12 to 2026.1.4
2026-01-10 17:07:27 -08:00
Roy 32de4174bf Merge pull request #856 from moraroy/dependabot/pip/urllib3-2.6.3
Bump urllib3 from 2.6.2 to 2.6.3
2026-01-10 17:00:50 -08:00
dependabot[bot] 321d030dee Bump certifi from 2025.11.12 to 2026.1.4
Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.11.12 to 2026.1.4.
- [Commits](https://github.com/certifi/python-certifi/compare/2025.11.12...2026.01.04)

---
updated-dependencies:
- dependency-name: certifi
  dependency-version: 2026.1.4
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-10 16:01:06 +00:00
dependabot[bot] c58077380b Bump urllib3 from 2.6.2 to 2.6.3
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.2 to 2.6.3.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.6.2...2.6.3)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.6.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-10 16:00:57 +00:00
Roy f938ca1f99 Refactor create_steam_store_app_manifest_file
Release Please / release-please (push) Has been cancelled
Refactor app manifest creation to use VDF format and improve error handling.
2026-01-08 21:28:30 -08:00
Roy b975f5e6a7 Implement uninstallation for multiple browsers
Added options to uninstall Google Chrome, Mozilla Firefox, Brave, and Microsoft Edge using Flatpak.
2026-01-08 21:18:25 -08:00
Roy 2ae2fdb5ef Delete Modules/steamgrid directory
Release Please / release-please (push) Has been cancelled
2026-01-08 02:27:51 -08:00
Roy 0f3d2bb259 Delete Modules/requests directory 2026-01-08 02:26:54 -08:00
Roy 513a5ef0c5 Update folders to clone in game scanner function
Removed 'requests' and 'steamgrid' from folders to clone in the update_nsl_game_scanner function.
2026-01-08 02:26:06 -08:00
Roy 0f6f8e9996 Refactor desktop shortcut creation logic
Removed redundant desktop shortcut creation for NonSteamLaunchers.
2026-01-08 02:17:03 -08:00
Roy a9f987ba69 Refactor env_vars cleanup logic
Release Please / release-please (push) Has been cancelled
Refactor env_vars file handling to remove unwanted lines using a dedicated function.
2026-01-07 04:21:16 -08:00
Roy 20c0b0c484 Enhance exclusion checks for launch options
Release Please / release-please (push) Has been cancelled
2026-01-04 00:41:17 -08:00
Roy cf09aa6a1f Added multi-browser support and the ability to name your websites 2026-01-04 00:08:57 -08:00
Roy 594c7437bd Enhance custom website handling and launch options 2026-01-03 23:13:08 -08:00
Roy 041c6ea467 Update FUNDING.yml 2026-01-03 00:27:23 -08:00
Roy dadceeb94e Add ChoiTech link to README
Release Please / release-please (push) Has been cancelled
2025-12-31 22:05:44 -08:00
Roy 4c6e14eca1 Update README.md
Release Please / release-please (push) Has been cancelled
2025-12-30 20:56:50 -08:00
Roy 813c89b983 Update README.md 2025-12-30 20:55:24 -08:00
Roy 09f56e83a0 Update GameJolt Launcher download URL
Release Please / release-please (push) Has been cancelled
2025-12-24 11:21:02 -08:00
Roy 526d4e534c Add files via upload 2025-12-24 08:37:54 -08:00
Roy 2da400dea4 Add ssl import to NSLGameScanner.py 2025-12-24 05:05:15 -08:00
Roy f18c3114a4 Update version number to v4.2.82
Release Please / release-please (push) Has been cancelled
2025-12-22 11:38:15 -08:00
Roy a9bdce3979 Inject watcher code for game launch detection 2025-12-22 11:02:49 -08:00
Roy 5dd1d89e9d Refactor boot video download logic
Release Please / release-please (push) Has been cancelled
Refactor boot video fetching logic to use urllib instead of requests. Update download URL construction and improve error handling.
2025-12-17 11:02:48 -08:00
Roy 904cca8d53 Update README.md
Release Please / release-please (push) Has been cancelled
2025-12-17 08:36:55 -08:00
Roy d9a18a7509 Update README.md 2025-12-17 08:33:55 -08:00
Roy 22e823bba2 Update version number to v4.2.81 2025-12-17 06:12:41 -08:00
Roy 5cde12fa02 Enhance GOG game launch with extra arguments support
Release Please / release-please (push) Has been cancelled
Added support for extracting extra GOG arguments from launch options and updated runner command accordingly.
2025-12-16 18:00:01 -08:00
Roy 83b474a73d Remove unused variable 'logged_in_home'
Release Please / release-please (push) Has been cancelled
2025-12-16 04:56:04 -08:00
Roy 733fa0c9c9 Refactor NonSteamLaunchers.sh for clarity and efficiency
Refactor variable assignments and improve readability.
2025-12-16 04:55:34 -08:00
Roy b929fb6f69 Fix sys.path insertion to avoid duplicates
Correct the insertion of sys.path to prevent duplicates.
2025-12-16 04:44:29 -08:00
Roy 142ad46dff Enhance EA App scanner and shortcut creation logic
Added logic to handle desktop shortcut creation for non-Steam launchers and improved registry fallback handling for EA App games.
2025-12-16 03:27:01 -08:00
Roy 70024df474 Improve error handling in GE-Proton download process
Refactor download and checksum download logic for clarity and error handling.
2025-12-16 02:25:44 -08:00
Roy f3310223d0 Refactor NSL Game Scanner update process
Release Please / release-please (push) Has been cancelled
Updated the NSL Game Scanner function to streamline the update process, including downloading and setting up the latest Python script and managing service states.
2025-12-14 13:41:35 -08:00
Roy 71cc38ba14 Replace file copy with symlink creation for applications 2025-12-14 08:54:01 -08:00
Roy edfe68ce7e Update README.md
Release Please / release-please (push) Has been cancelled
2025-12-14 07:18:16 -08:00
Roy 7b09d8ca3b Update version number to v4.2.8 2025-12-14 05:07:58 -08:00
Roy 6b9dd71206 Improve logging for .desktop file deletion 2025-12-14 03:11:07 -08:00
Roy db9060c5a5 Update runner_cmd assignment in NSLGameScanner
Removed error message for invalid GOG game path and set runner_cmd.
2025-12-14 02:42:53 -08:00
Roy 091cb02109 Refactor gameId error handling in NSLGameScanner
Release Please / release-please (push) Has been cancelled
Updated error handling for missing gameId in launch options.
2025-12-13 13:00:53 -08:00
Roy 9826855e76 Fix kdialog prompt message formatting 2025-12-13 11:31:39 -08:00
Roy 3a5fdc6461 Implement .desktop file creation for Steam Machine users
Added logic to create and update .desktop files for games, including extracting game IDs and handling launch options.
2025-12-13 11:24:16 -08:00
Roy 68d2b9592c Merge pull request #840 from moraroy/dependabot/pip/urllib3-2.6.2
Bump urllib3 from 2.6.0 to 2.6.2
2025-12-13 10:46:33 -08:00
dependabot[bot] 618eb573a8 Bump urllib3 from 2.6.0 to 2.6.2
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.0 to 2.6.2.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.6.0...2.6.2)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.6.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-13 16:02:26 +00:00
Roy be6d19a1b3 Refactor Steam AppList lookup and caching logic
Release Please / release-please (push) Has been cancelled
2025-12-09 04:01:00 -08:00
Roy eac7cd4bd3 Merge pull request #837 from tlaufkoetter/patch-1
Release Please / release-please (push) Has been cancelled
Fix itch.io launcher name
2025-12-08 18:25:37 -08:00
tlaufkoetter a7faeda64f Fix itch.io launcher name
Was listed as "Itch.io" in the supported launchers section. It will only be installed with the name "itch.io" (case sensitive). I only tried it with "Itch.io" and only found out through browsing the source code that it's supposed to be "itch.io".
2025-12-08 21:01:43 +01:00
Roy 6edc8d9cf0 Refactor Steam App ID retrieval with caching
Release Please / release-please (push) Has been cancelled
2025-12-08 09:46:38 -08:00
Roy e402a60d8b Sanitize note content and format output
Sanitize content by removing existing [p] tags and replace newlines with Steam-friendly line breaks.
2025-12-08 09:11:37 -08:00
Roy 727a58fa50 Update README.md
Release Please / release-please (push) Has been cancelled
2025-12-07 09:04:59 -08:00
Roy 31af2fc92e Update version number to v4.2.76 2025-12-07 08:47:20 -08:00
Roy baae26fc6e Add urllib import to NSLGameScanner.py 2025-12-07 04:26:59 -08:00
Roy 93c18eff2d Improve request handling and fallback mechanism
Release Please / release-please (push) Has been cancelled
Added timeout parameter to requests for improved reliability and replaced the cached Steam AppList fallback with a direct search fallback using the Steam store.
2025-12-07 04:20:29 -08:00
Roy 0dce30534c Remove commented-out notification code
Removed commented-out code for notification tones and Steam client notifications to clean up the code.
2025-12-06 23:13:42 -08:00
Roy 318ef5a4f3 Merge pull request #832 from moraroy/dependabot/pip/urllib3-2.6.0
Release Please / release-please (push) Has been cancelled
Bump urllib3 from 2.5.0 to 2.6.0
2025-12-06 12:18:44 -08:00
Roy 497a3960e7 Refactor notification and shortcut creation functions
Release Please / release-please (push) Has been cancelled
Refactor notification handling and shortcut creation logic.
2025-12-06 10:44:25 -08:00
dependabot[bot] 59d1267758 Bump urllib3 from 2.5.0 to 2.6.0
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.5.0 to 2.6.0.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.5.0...2.6.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-06 16:02:27 +00:00
Roy c313f63ecf Add files via upload 2025-12-06 04:54:07 -08:00
Roy 5592775e19 Refactor send_steam_notification for app removals
Updated the send_steam_notification function to handle removed applications and added logic to clean up empty collections. Adjusted WebSocket message handling and improved error handling for desktop file deletions.
2025-12-06 04:47:30 -08:00
Roy df68044842 Refactor launcher path determination for Waydroid
Release Please / release-please (push) Has been cancelled
2025-12-03 07:55:20 -08:00
Roy 3590ae73b9 Fixed notification logic 2025-12-03 07:41:26 -08:00
Roy 3caf4370b7 Fix argument formatting in launch command
Release Please / release-please (push) Has been cancelled
2025-12-02 05:35:15 -08:00
Roy 015f3947c1 Refactor GOG Galaxy game launch options handling 2025-12-02 05:29:07 -08:00
Roy 7243d1c45f Refactor GOG Galaxy game info retrieval 2025-12-02 04:47:01 -08:00
Roy ead6d04dc7 Update version number to v4.2.75
Release Please / release-please (push) Has been cancelled
2025-12-01 05:25:55 -08:00
Roy ec8f17770a Update NSLGameScanner.py
Release Please / release-please (push) Has been cancelled
2025-11-25 17:10:38 -08:00
Roy 5a4df4b833 Update NonSteamLaunchers.sh 2025-11-24 05:14:19 -08:00
Roy 5f1e4674d3 Update NonSteamLaunchers.sh 2025-11-24 05:06:54 -08:00
Roy 1879a642c1 Update NonSteamLaunchers.sh
Release Please / release-please (push) Has been cancelled
2025-11-23 09:34:36 -08:00
Roy 1f6e10d4a4 Update NSLGameScanner.py 2025-11-23 09:33:17 -08:00
Roy dd17e70668 Update README.md 2025-11-21 08:22:47 -08:00
Roy 072fbf46f5 Update README.md 2025-11-21 08:19:54 -08:00
Roy 8b14452f02 Merge pull request #822 from krakerz/patch-1
Enhance README with Waydroid Apps support details
2025-11-21 08:06:35 -08:00
Alvi A a178fec3d2 Enhance README with Waydroid Apps support details
Added support information for Waydroid Apps and detection instructions.
2025-11-21 23:03:31 +07:00
Roy b0e6dcf36c Update NSLGameScanner.py 2025-11-21 07:50:08 -08:00
Roy e06ff6048c Refactor executable path handling for GOG games 2025-11-20 06:07:57 -08:00
Roy 7ad0b0efb6 Update executable path handling in NSLGameScanner 2025-11-20 05:54:43 -08:00
Roy 04254d5917 Merge pull request #821 from agvantibo-again/agvantibo-again-patch-1
Fix minor grammar issue in README.md
2025-11-20 03:49:47 -08:00
Savchenko Dmitriy 0ce4c44063 Fix minor grammar issue in README.md 2025-11-20 14:33:18 +03:00
Roy 26e6920d17 Update shortcut sort title with app name 2025-11-18 02:04:11 -08:00
Roy 0bdeb11e2d Fix NSLGameSaves path and update GeForce NOW description
Corrected the path for NSLGameSaves and updated the description for NVIDIA GeForce NOW.
2025-11-17 23:44:13 -08:00
Roy 0fd2efdd34 Fix GITHUB_URL in NSLPluginInstaller.sh
Release Please / release-please (push) Has been cancelled
2025-11-16 06:16:39 -08:00
Roy 932bfd7b08 Merge pull request #817 from moraroy/dependabot/pip/pytest-gte-8.0.2-and-lt-10
Release Please / release-please (push) Has been cancelled
Update pytest requirement from <9,>=8.0.2 to >=8.0.2,<10
2025-11-16 02:22:03 -08:00
Roy e0a7affd35 Merge pull request #816 from moraroy/dependabot/pip/certifi-2025.11.12
Bump certifi from 2025.10.5 to 2025.11.12
2025-11-15 20:40:09 -08:00
dependabot[bot] 42cd21014b Update pytest requirement from <9,>=8.0.2 to >=8.0.2,<10
Updates the requirements on [pytest](https://github.com/pytest-dev/pytest) to permit the latest version.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.0.2...9.0.1)

---
updated-dependencies:
- dependency-name: pytest
  dependency-version: 9.0.1
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-15 16:01:52 +00:00
dependabot[bot] f4bd2509e2 Bump certifi from 2025.10.5 to 2025.11.12
Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.10.5 to 2025.11.12.
- [Commits](https://github.com/certifi/python-certifi/compare/2025.10.05...2025.11.12)

---
updated-dependencies:
- dependency-name: certifi
  dependency-version: 2025.11.12
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-11-15 16:01:46 +00:00
Roy 87c437aeed Track game launch in NSLGameScanner 2025-11-15 03:49:43 -08:00
Roy 16d31018e1 Fix typo in README.md regarding SteamOS 2025-11-15 03:24:23 -08:00
Roy 3e98bdbc04 Update executable paths for Epic Games Launcher
Release Please / release-please (push) Has been cancelled
2025-11-13 21:07:01 -08:00
Roy 6c604a564a Fix Epic Games path in uninstall_launcher call 2025-11-13 20:59:09 -08:00
Roy b20fa8244b Merge pull request #813 from Flare576/patch-1
fix(epic): update install path
2025-11-13 20:57:09 -08:00
Roy a4143d140a Update 'Start Fresh' description in README
Clarified the 'Start Fresh' option description in README.md.
2025-11-13 20:36:39 -08:00
Jeremy Scherer 6090a8e97e fix(epic): update install path
Tested locally - application is detected correctly and added to library after change
2025-11-13 19:34:32 -06:00
Roy 643ca62f78 Add files via upload
Release Please / release-please (push) Has been cancelled
2025-11-13 03:04:50 -08:00
Roy cb766e43ca Add files via upload 2025-11-13 03:03:20 -08:00
Roy 399b9e93c5 Update README.md 2025-11-13 02:39:00 -08:00
Roy 830c42e359 Enhance README with clearer installation instructions
Updated README to clarify launcher installation details and features.
2025-11-13 02:33:59 -08:00
Roy 6f295a4b3a Added new collection category for all launchers, NonSteamLaunchers
Release Please / release-please (push) Has been cancelled
2025-11-12 05:21:00 -08:00
Roy 482982a51b Reduce RestartSec from 30 to 20 seconds
Release Please / release-please (push) Has been cancelled
2025-11-10 18:06:17 -08:00
Roy a61f71b675 Refactor NSL Game Scanner script for manual execution
Release Please / release-please (push) Has been cancelled
Updated NSL Game Scanner script to remove service file handling and added initial manual run.
2025-11-10 12:51:50 -08:00
Roy c2c056cb89 Implement systemd service for NSL Game Scanner
Added systemd service setup for NSL Game Scanner.
2025-11-10 12:26:33 -08:00
Roy bc0695bef5 Refactor NSL Game Scanner script for improvements
Refactor NSL Game Scanner script to improve functionality and structure. Added checks for existing modules, updated service management, and enhanced user feedback messages.
2025-11-10 11:44:47 -08:00
Roy 1ffa3e6ccd Remove service setup from NSLGameScanner.py
Removed service setup code for nslgamescanner.
2025-11-10 11:29:01 -08:00
Roy 25ef61fb6a Remove service stop and cleanup commands 2025-11-10 07:48:16 -08:00
Roy 3513ae5c60 Comment out service start command
Comment out the command to start the service immediately.
2025-11-10 07:34:28 -08:00
Roy 70ca10ae44 Refactor JS injection and shortcut creation logic
Refactor shortcut injection process and improve comments.
2025-11-10 06:44:01 -08:00
Roy f28a1f19f2 Refactor JS injection and shortcut creation logic 2025-11-10 05:53:14 -08:00
Roy 6e9e634c19 Stop service and clean up NSLGameScanner files
Release Please / release-please (push) Has been cancelled
Stop the NSLGameScanner service and remove related files.
2025-11-10 04:56:49 -08:00
Roy 2e6fbe73f7 Remove Epic Games installation function
Removed the temporary fix for Epic Games installation from the script.
2025-11-10 04:37:18 -08:00
Roy bfeac8e152 Update injection message in NSLGameScanner.py 2025-11-10 03:43:55 -08:00
Roy 15f834627a Update executable paths to Win64 for Epic Games Launcher 2025-11-10 03:25:12 -08:00
Roy 6d4c078c63 changed epic from win32 to win64 2025-11-10 03:22:47 -08:00
Roy 36f84dfb81 Added a feature request template for users 2025-11-10 02:59:27 -08:00
Roy 101fb40aba Added custom bug report for users 2025-11-10 02:50:09 -08:00
Roy cfdf0c1990 Update version number to v4.2.73
Release Please / release-please (push) Has been cancelled
2025-11-07 06:14:42 -08:00
Roy 745b015cac Refactor bubble hover and paste button logic 2025-11-07 06:02:59 -08:00
Roy 435fdfc77f Enhance bubble interactivity and visibility
Release Please / release-please (push) Has been cancelled
Refactor bubble behavior and improve UI interactions.
2025-11-07 05:26:14 -08:00
Roy b35e0ef25a Refactor video caching logic in NSLGameScanner
Release Please / release-please (push) Has been cancelled
2025-11-06 04:15:41 -08:00
Roy 3ed3bf726f Enhance README with additional script features
Release Please / release-please (push) Has been cancelled
Expanded the description of the NonSteamLaunchers script to include details about playtime tracking, boot videos, and theme music.
2025-11-05 04:53:55 -08:00
Roy 3ae952d59a Update README with Music Button feature details
Clarified the functionality of the Music Button feature and its interaction with game themes.
2025-11-05 04:51:36 -08:00
Roy 68a4c2112e Delete SDH-GameThemeMusicPatcher.sh
Release Please / release-please (push) Has been cancelled
2025-11-05 04:26:58 -08:00
Roy e34ff1331b Update version number to v4.2.72 2025-11-05 03:37:44 -08:00
Roy 476ee78711 Refactor theme music event handling and caching
Refactor theme music handling and caching logic for better clarity and efficiency.
2025-11-05 03:30:37 -08:00
Roy ed57754ec5 Refactor playtime and thememusic injection code
Release Please / release-please (push) Has been cancelled
2025-11-03 23:33:35 -08:00
Roy 9d1b119542 fixed button to not offset to steam corner 2025-11-03 21:04:46 -08:00
Roy f3b100679d Refactor theme music toggle button implementation
Release Please / release-please (push) Has been cancelled
2025-11-03 14:37:55 -08:00
Roy 305e2ffae2 Update version number to v4.2.71
Release Please / release-please (push) Has been cancelled
2025-11-02 21:10:48 -08:00
Roy dd1b970cad added on and off button for music desktop users 2025-11-02 21:00:55 -08:00
Roy 688ac8e4d6 Update version number to v4.2.7
Release Please / release-please (push) Has been cancelled
2025-11-02 10:01:31 -08:00
Roy 2e2d4eb25e Added Game Theme Music for desktop 2025-11-02 10:00:54 -08:00
Roy e1eeae1b2e
Release Please / release-please (push) Has been cancelled
2025-10-31 02:11:35 -07:00
Roy 9d8075027a Update NonSteamLaunchers.sh
Release Please / release-please (push) Has been cancelled
2025-10-27 04:48:39 -07:00
Roy ea7c7f8f55 Playtime for non steam games 2025-10-27 03:53:14 -07:00
Roy 47fa3f1216 Update SDH-GameThemeMusicPatcher.sh
Release Please / release-please (push) Has been cancelled
2025-10-26 05:27:11 -07:00
Roy d9e0f60517 Merge pull request #810 from moraroy/dependabot/pip/idna-3.11
Release Please / release-please (push) Has been cancelled
Bump idna from 3.10 to 3.11
2025-10-19 04:04:19 -07:00
dependabot[bot] bd7da61b51 Bump idna from 3.10 to 3.11
Bumps [idna](https://github.com/kjd/idna) from 3.10 to 3.11.
- [Release notes](https://github.com/kjd/idna/releases)
- [Changelog](https://github.com/kjd/idna/blob/master/HISTORY.rst)
- [Commits](https://github.com/kjd/idna/compare/v3.10...v3.11)

---
updated-dependencies:
- dependency-name: idna
  dependency-version: '3.11'
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-19 11:03:39 +00:00
Roy 53dde899f8 Merge pull request #809 from moraroy/dependabot/pip/charset-normalizer-3.4.4
Bump charset-normalizer from 3.4.3 to 3.4.4
2025-10-19 04:02:35 -07:00
dependabot[bot] 638e9316a8 Bump charset-normalizer from 3.4.3 to 3.4.4
Bumps [charset-normalizer](https://github.com/jawah/charset_normalizer) from 3.4.3 to 3.4.4.
- [Release notes](https://github.com/jawah/charset_normalizer/releases)
- [Changelog](https://github.com/jawah/charset_normalizer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jawah/charset_normalizer/compare/3.4.3...3.4.4)

---
updated-dependencies:
- dependency-name: charset-normalizer
  dependency-version: 3.4.4
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-18 15:02:14 +00:00
Roy a6b4144e9c Update README.md
Release Please / release-please (push) Has been cancelled
2025-10-17 01:45:19 -07:00
Roy 64b78a471a refactor logic
Release Please / release-please (push) Has been cancelled
2025-10-16 04:49:14 -07:00
Roy e62f1fdbb9 Bump version from 4.2.4 to 4.2.5
Release Please / release-please (push) Has been cancelled
2025-10-16 01:49:31 -07:00
Roy 64fcafdcbc Add patch for disabling Gamescope WSI in Proton
Patch Proton Python script to disable Gamescope WSI if not already patched.
2025-10-16 01:33:55 -07:00
Roy b5027d6490 Update Xbox Cloud Gaming URL condition
Release Please / release-please (push) Has been cancelled
2025-10-14 05:41:16 -07:00
Roy bc7b8f01a4 Refactor URL checks for Xbox and Amazon Luna 2025-10-14 04:00:48 -07:00
Roy a038483e58 Update GameJolt Launcher download URL
Release Please / release-please (push) Has been cancelled
2025-10-12 22:11:57 -07:00
Roy 2b367835b3 Refactor Chrome bookmark scanner for clarity 2025-10-12 21:37:34 -07:00
Roy 01b2e5b47d Enhance Chrome bookmark scanner for multiple platforms 2025-10-12 21:30:59 -07:00
Roy 0dc79e2a61 Merge pull request #808 from moraroy/dependabot/pip/ruff-gte-0.12.1-and-lt-0.15
Release Please / release-please (push) Has been cancelled
Update ruff requirement from <0.14,>=0.12.1 to >=0.12.1,<0.15
2025-10-11 17:16:26 -07:00
Roy e04590d936 Merge pull request #807 from moraroy/dependabot/pip/certifi-2025.10.5
Bump certifi from 2025.8.3 to 2025.10.5
2025-10-11 17:12:34 -07:00
dependabot[bot] 79d295b59b Update ruff requirement from <0.14,>=0.12.1 to >=0.12.1,<0.15
Updates the requirements on [ruff](https://github.com/astral-sh/ruff) to permit the latest version.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.12.1...0.14.0)

---
updated-dependencies:
- dependency-name: ruff
  dependency-version: 0.14.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-11 15:02:32 +00:00
dependabot[bot] 4cbfac4ed8 Bump certifi from 2025.8.3 to 2025.10.5
Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.8.3 to 2025.10.5.
- [Commits](https://github.com/certifi/python-certifi/compare/2025.08.03...2025.10.05)

---
updated-dependencies:
- dependency-name: certifi
  dependency-version: 2025.10.5
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-10-11 15:02:19 +00:00
Roy b8394b7158 Add handling for 'wow_classic_era' game key
Release Please / release-please (push) Has been cancelled
2025-10-04 01:08:16 -07:00
Roy 7e54381769 Add files via upload 2025-10-03 22:29:39 -07:00
Roy e40beda9b6 Implement audio notifications with playTone function
Release Please / release-please (push) Has been cancelled
Added functionality to play custom notification sounds using the Web Audio API.
2025-09-29 23:48:15 -07:00
Roy 0f1f380e3f Implement sub-launch game mapping for Ubisoft Connect
Release Please / release-please (push) Has been cancelled
Added support for bundled sub-launch games in Ubisoft Connect.
2025-09-29 04:29:40 -07:00
Roy 2f76a8bde1 Enhance EA App game info extraction and encoding
Added encoding fix and improved EA App game info extraction.
2025-09-29 04:19:08 -07:00
Roy bf2aade65f Update GameJolt Launcher download URL
Release Please / release-please (push) Has been cancelled
2025-09-27 17:30:13 -07:00
Roy 2b1587cdf7 Enhance EA Games path detection logic
Release Please / release-please (push) Has been cancelled
Updated EA Games path detection to include legacy paths and improved error handling for missing OfferID.
2025-09-27 03:34:36 -07:00
Roy 415af41d9f Update yt-dlp download URL to latest version
Release Please / release-please (push) Has been cancelled
2025-09-26 20:25:35 -07:00
Roy 95651b33a8 Implement local UMU database loading with fallback
Release Please / release-please (push) Has been cancelled
Added local UMU database fallback mechanism and improved error handling.
2025-09-25 23:23:56 -07:00
Roy a2ea90fe12 Refactor shortcut handling and artwork assignment
Release Please / release-please (push) Has been cancelled
2025-09-22 01:21:30 -07:00
Roy bd82999815 Refactor shortcut handling and logging 2025-09-22 01:11:07 -07:00
Roy 8ea4657bcf Enhance comments and variable initialization in script
Release Please / release-please (push) Has been cancelled
Refactor comments for clarity and improve variable assignments.
2025-09-21 03:44:23 -07:00
Roy 2f21c7524d Add files via upload
Release Please / release-please (push) Has been cancelled
2025-09-20 16:29:14 -07:00
Roy ff5502054d Reduce RestartSec from 300 to 30 seconds
Release Please / release-please (push) Has been cancelled
2025-09-16 02:21:13 -07:00
Roy 80e36750cd Merge pull request #795 from moraroy/dependabot/pip/pytest-cov-gte-6.0.0-and-lt-8
Release Please / release-please (push) Has been cancelled
Update pytest-cov requirement from <7,>=6.0.0 to >=6.0.0,<8
2025-09-14 02:19:11 -07:00
Roy 68d373eec3 Merge pull request #794 from moraroy/dependabot/pip/ruff-gte-0.12.1-and-lt-0.14
Update ruff requirement from <0.13,>=0.12.1 to >=0.12.1,<0.14
2025-09-14 02:18:22 -07:00
dependabot[bot] 3e0f134e0a Update pytest-cov requirement from <7,>=6.0.0 to >=6.0.0,<8
Updates the requirements on [pytest-cov](https://github.com/pytest-dev/pytest-cov) to permit the latest version.
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v6.0.0...v7.0.0)

---
updated-dependencies:
- dependency-name: pytest-cov
  dependency-version: 7.0.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-13 15:02:03 +00:00
dependabot[bot] ece2714274 Update ruff requirement from <0.13,>=0.12.1 to >=0.12.1,<0.14
Updates the requirements on [ruff](https://github.com/astral-sh/ruff) to permit the latest version.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.12.1...0.13.0)

---
updated-dependencies:
- dependency-name: ruff
  dependency-version: 0.13.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-09-13 15:01:59 +00:00
62 changed files with 5200 additions and 8669 deletions
+1
View File
@@ -4,6 +4,7 @@ github: moraroy
patreon: moraroy
open_collective: # Replace with a single Open Collective username
ko_fi: moraroy
buy_me_a_coffee: moraroy
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
liberapay: moraroy
@@ -0,0 +1,85 @@
---
name: "\U0001F41E Bug Report (Linux / SteamOS)"
about: Report a bug or problem that you have experienced with NonSteamLaunchers
title: bug
labels: bug
assignees: ''
---
## 🐞 Describe the Bug
A clear and concise description of the issue youre experiencing with **NonSteamLaunchers (NSL)**.
Include any error messages, crashes, or unexpected behavior.
**Which version are you using?**
- [ ] NonSteamLaunchers.desktop (Desktop Version)
- [ ] Decky Plugin (`NonSteamLaunchersDecky`)
---
## 🔁 To Reproduce
Steps to reproduce the behavior:
1. (Optional) Switch to Desktop Mode.
2. Launch NSL using your selected method above.
3. Select and install one or more launchers (Epic, EA, GOG, etc.)
4. Wait for the installation to complete or for any errors to appear.
5. (If applicable) Return to Gaming Mode and attempt to launch the installed launcher(s).
6. Observe the issue.
*Example:*
> Epic Games Launcher installs successfully but fails to open in Gaming Mode (Proton GE 9-5).
---
## ✅ Expected Behavior
A clear and concise description of what you expected to happen.
Example:
> The Epic Games Launcher should open in Gaming Mode and allow login.
---
## 🖼️ Screenshots / Logs
If applicable, add screenshots or logs to help explain your problem.
**Log locations:**
- Script/Desktop version:
`/home/deck/Downloads/NonSteamLaunchers-install.log`
- Decky Plugin version:
`/home/deck/homebrew/logs/NonSteamLaunchers/`
Please attach relevant sections or errors from the logs if possible.
---
## 🧰 System Information
Please complete the following details:
**Device Type**
- [ ] Steam Deck
- [ ] Desktop PC
- [ ] Laptop
- [ ] Other: (please specify)
**Operating System**
- [ ] SteamOS (Arch-based)
- [ ] Other Linux Distro (please specify)
**Details**
- **SteamOS Version:** e.g. 3.6.5
- **Kernel Version:** (run `uname -r` in Konsole)
- **Proton Version Used:** e.g. Proton GE 9-5 / Proton Experimental
- **Launchers Affected:** Epic / GOG / EA / Battle.net / etc.
- **NSL Version:** (from GitHub release tag, Decky Plugin version, or script header)
- **Install Method:** Script (.sh) / Desktop Launcher (.desktop) / Decky Plugin
---
## 💬 Additional Context
Add any other context about the problem here, such as:
- [ ] Occurs only in Gaming Mode
- [ ] Occurs only in Desktop Mode
- [ ] Reinstalling NSL did not fix the issue
- [ ] Clearing compatibility data did not help
- [ ] I am using a custom Proton build
Provide any temporary fixes or additional details below:
@@ -0,0 +1,55 @@
---
name: "\U0001F4A1 Feature Request (Linux / SteamOS)"
about: Whats the big idea?
title: enhancement
labels: enhancement
assignees: ''
---
## 💭 Is your feature request related to a problem?
A clear and concise description of what the problem is.
Example:
> I'm always frustrated when NonSteamLaunchers creates duplicates in the scanner!
---
## 🚀 Describe the Solution Youd Like
A clear and concise description of what youd like to see added or improved.
Example:
> Add a booster seat feature so NSL can launch into space.
---
## 🔄 Describe Alternatives Youve Considered
List any alternative approaches, workarounds, or existing tools youve tried.
Example:
> I currently use the Decky Plugin version to manage launchers, but it could be better!
---
## 🧩 Scope of Feature
Please indicate what area of NSL your request affects:
- [ ] Desktop (`NonSteamLaunchers.desktop`)
- [ ] Decky Plugin (`NonSteamLaunchersDecky`)
- [ ] Proton / Compatibility Integration
- [ ] UI / User Experience
- [ ] Other (please specify below)
---
## 🧰 System Context
(Optional, but helpful for context)
- **Device:** Steam Deck / Desktop PC / Laptop / Other
- **OS / Distro:** SteamOS (Arch-based) / Other Linux Distro (specify)
- **Current NSL Version:** (from GitHub release tag, Decky Plugin version, or script header)
---
## 💬 Additional Context
Add any other context, mockups, or screenshots related to your suggestion here.
You can also include:
- Why this feature would improve the Linux/Steam Deck experience
- Whether it should be optional or enabled by default
- If it relates to an existing issue or pull request
+9 -5
View File
@@ -12,14 +12,18 @@ permissions:
jobs:
release-please:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Release with release-please
uses: google-github-actions/release-please-action@v4
uses: googleapis/release-please-action@v4
with:
# PAT with write access to the repository
token: ${{ secrets.RELEASE_PLEASE_TOKEN }}
# optional. customize path to release-please-config.json
config-file: release-please-config.json
# optional. customize path to .release-please-manifest.json
manifest-file: .release-please-manifest.json
repo-url: moraroy/NonSteamLaunchers-On-Steam-Deck
fork: false
include-component-in-tag: false
skip-github-release: false
skip-github-pull-request: false
-180
View File
@@ -1,180 +0,0 @@
# __
# /__) _ _ _ _ _/ _
# / ( (- (/ (/ (- _) / _)
# /
"""
Requests HTTP Library
~~~~~~~~~~~~~~~~~~~~~
Requests is an HTTP library, written in Python, for human beings.
Basic GET usage:
>>> import requests
>>> r = requests.get('https://www.python.org')
>>> r.status_code
200
>>> b'Python is a programming language' in r.content
True
... or POST:
>>> payload = dict(key1='value1', key2='value2')
>>> r = requests.post('https://httpbin.org/post', data=payload)
>>> print(r.text)
{
...
"form": {
"key1": "value1",
"key2": "value2"
},
...
}
The other HTTP methods are supported - see `requests.api`. Full documentation
is at <https://requests.readthedocs.io>.
:copyright: (c) 2017 by Kenneth Reitz.
:license: Apache 2.0, see LICENSE for more details.
"""
import warnings
import urllib3
from .exceptions import RequestsDependencyWarning
try:
from charset_normalizer import __version__ as charset_normalizer_version
except ImportError:
charset_normalizer_version = None
try:
from chardet import __version__ as chardet_version
except ImportError:
chardet_version = None
def check_compatibility(urllib3_version, chardet_version, charset_normalizer_version):
urllib3_version = urllib3_version.split(".")
assert urllib3_version != ["dev"] # Verify urllib3 isn't installed from git.
# Sometimes, urllib3 only reports its version as 16.1.
if len(urllib3_version) == 2:
urllib3_version.append("0")
# Check urllib3 for compatibility.
major, minor, patch = urllib3_version # noqa: F811
major, minor, patch = int(major), int(minor), int(patch)
# urllib3 >= 1.21.1
assert major >= 1
if major == 1:
assert minor >= 21
# Check charset_normalizer for compatibility.
if chardet_version:
major, minor, patch = chardet_version.split(".")[:3]
major, minor, patch = int(major), int(minor), int(patch)
# chardet_version >= 3.0.2, < 6.0.0
assert (3, 0, 2) <= (major, minor, patch) < (6, 0, 0)
elif charset_normalizer_version:
major, minor, patch = charset_normalizer_version.split(".")[:3]
major, minor, patch = int(major), int(minor), int(patch)
# charset_normalizer >= 2.0.0 < 4.0.0
assert (2, 0, 0) <= (major, minor, patch) < (4, 0, 0)
else:
raise Exception("You need either charset_normalizer or chardet installed")
def _check_cryptography(cryptography_version):
# cryptography < 1.3.4
try:
cryptography_version = list(map(int, cryptography_version.split(".")))
except ValueError:
return
if cryptography_version < [1, 3, 4]:
warning = "Old version of cryptography ({}) may cause slowdown.".format(
cryptography_version
)
warnings.warn(warning, RequestsDependencyWarning)
# Check imported dependencies for compatibility.
try:
check_compatibility(
urllib3.__version__, chardet_version, charset_normalizer_version
)
except (AssertionError, ValueError):
warnings.warn(
"urllib3 ({}) or chardet ({})/charset_normalizer ({}) doesn't match a supported "
"version!".format(
urllib3.__version__, chardet_version, charset_normalizer_version
),
RequestsDependencyWarning,
)
# Attempt to enable urllib3's fallback for SNI support
# if the standard library doesn't support SNI or the
# 'ssl' library isn't available.
try:
try:
import ssl
except ImportError:
ssl = None
if not getattr(ssl, "HAS_SNI", False):
from urllib3.contrib import pyopenssl
pyopenssl.inject_into_urllib3()
# Check cryptography version
from cryptography import __version__ as cryptography_version
_check_cryptography(cryptography_version)
except ImportError:
pass
# urllib3's DependencyWarnings should be silenced.
from urllib3.exceptions import DependencyWarning
warnings.simplefilter("ignore", DependencyWarning)
# Set default logging handler to avoid "No handler found" warnings.
import logging
from logging import NullHandler
from . import packages, utils
from .__version__ import (
__author__,
__author_email__,
__build__,
__cake__,
__copyright__,
__description__,
__license__,
__title__,
__url__,
__version__,
)
from .api import delete, get, head, options, patch, post, put, request
from .exceptions import (
ConnectionError,
ConnectTimeout,
FileModeWarning,
HTTPError,
JSONDecodeError,
ReadTimeout,
RequestException,
Timeout,
TooManyRedirects,
URLRequired,
)
from .models import PreparedRequest, Request, Response
from .sessions import Session, session
from .status_codes import codes
logging.getLogger(__name__).addHandler(NullHandler())
# FileModeWarnings go off per the default.
warnings.simplefilter("default", FileModeWarning, append=True)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-14
View File
@@ -1,14 +0,0 @@
# .-. .-. .-. . . .-. .-. .-. .-.
# |( |- |.| | | |- `-. | `-.
# ' ' `-' `-`.`-' `-' `-' ' `-'
__title__ = "requests"
__description__ = "Python HTTP for Humans."
__url__ = "https://requests.readthedocs.io"
__version__ = "2.31.0"
__build__ = 0x023100
__author__ = "Kenneth Reitz"
__author_email__ = "me@kennethreitz.org"
__license__ = "Apache 2.0"
__copyright__ = "Copyright Kenneth Reitz"
__cake__ = "\u2728 \U0001f370 \u2728"
-50
View File
@@ -1,50 +0,0 @@
"""
requests._internal_utils
~~~~~~~~~~~~~~
Provides utility functions that are consumed internally by Requests
which depend on extremely few external helpers (such as compat)
"""
import re
from .compat import builtin_str
_VALID_HEADER_NAME_RE_BYTE = re.compile(rb"^[^:\s][^:\r\n]*$")
_VALID_HEADER_NAME_RE_STR = re.compile(r"^[^:\s][^:\r\n]*$")
_VALID_HEADER_VALUE_RE_BYTE = re.compile(rb"^\S[^\r\n]*$|^$")
_VALID_HEADER_VALUE_RE_STR = re.compile(r"^\S[^\r\n]*$|^$")
_HEADER_VALIDATORS_STR = (_VALID_HEADER_NAME_RE_STR, _VALID_HEADER_VALUE_RE_STR)
_HEADER_VALIDATORS_BYTE = (_VALID_HEADER_NAME_RE_BYTE, _VALID_HEADER_VALUE_RE_BYTE)
HEADER_VALIDATORS = {
bytes: _HEADER_VALIDATORS_BYTE,
str: _HEADER_VALIDATORS_STR,
}
def to_native_string(string, encoding="ascii"):
"""Given a string object, regardless of type, returns a representation of
that string in the native string type, encoding and decoding where
necessary. This assumes ASCII unless told otherwise.
"""
if isinstance(string, builtin_str):
out = string
else:
out = string.decode(encoding)
return out
def unicode_is_ascii(u_string):
"""Determine if unicode string only contains ASCII characters.
:param str u_string: unicode string to check. Must be unicode
and not Python 2 `str`.
:rtype: bool
"""
assert isinstance(u_string, str)
try:
u_string.encode("ascii")
return True
except UnicodeEncodeError:
return False
-538
View File
@@ -1,538 +0,0 @@
"""
requests.adapters
~~~~~~~~~~~~~~~~~
This module contains the transport adapters that Requests uses to define
and maintain connections.
"""
import os.path
import socket # noqa: F401
from urllib3.exceptions import ClosedPoolError, ConnectTimeoutError
from urllib3.exceptions import HTTPError as _HTTPError
from urllib3.exceptions import InvalidHeader as _InvalidHeader
from urllib3.exceptions import (
LocationValueError,
MaxRetryError,
NewConnectionError,
ProtocolError,
)
from urllib3.exceptions import ProxyError as _ProxyError
from urllib3.exceptions import ReadTimeoutError, ResponseError
from urllib3.exceptions import SSLError as _SSLError
from urllib3.poolmanager import PoolManager, proxy_from_url
from urllib3.util import Timeout as TimeoutSauce
from urllib3.util import parse_url
from urllib3.util.retry import Retry
from .auth import _basic_auth_str
from .compat import basestring, urlparse
from .cookies import extract_cookies_to_jar
from .exceptions import (
ConnectionError,
ConnectTimeout,
InvalidHeader,
InvalidProxyURL,
InvalidSchema,
InvalidURL,
ProxyError,
ReadTimeout,
RetryError,
SSLError,
)
from .models import Response
from .structures import CaseInsensitiveDict
from .utils import (
DEFAULT_CA_BUNDLE_PATH,
extract_zipped_paths,
get_auth_from_url,
get_encoding_from_headers,
prepend_scheme_if_needed,
select_proxy,
urldefragauth,
)
try:
from urllib3.contrib.socks import SOCKSProxyManager
except ImportError:
def SOCKSProxyManager(*args, **kwargs):
raise InvalidSchema("Missing dependencies for SOCKS support.")
DEFAULT_POOLBLOCK = False
DEFAULT_POOLSIZE = 10
DEFAULT_RETRIES = 0
DEFAULT_POOL_TIMEOUT = None
class BaseAdapter:
"""The Base Transport Adapter"""
def __init__(self):
super().__init__()
def send(
self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
):
"""Sends PreparedRequest object. Returns Response object.
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
:param stream: (optional) Whether to stream the request content.
:param timeout: (optional) How long to wait for the server to send
data before giving up, as a float, or a :ref:`(connect timeout,
read timeout) <timeouts>` tuple.
:type timeout: float or tuple
:param verify: (optional) Either a boolean, in which case it controls whether we verify
the server's TLS certificate, or a string, in which case it must be a path
to a CA bundle to use
:param cert: (optional) Any user-provided SSL certificate to be trusted.
:param proxies: (optional) The proxies dictionary to apply to the request.
"""
raise NotImplementedError
def close(self):
"""Cleans up adapter specific items."""
raise NotImplementedError
class HTTPAdapter(BaseAdapter):
"""The built-in HTTP Adapter for urllib3.
Provides a general-case interface for Requests sessions to contact HTTP and
HTTPS urls by implementing the Transport Adapter interface. This class will
usually be created by the :class:`Session <Session>` class under the
covers.
:param pool_connections: The number of urllib3 connection pools to cache.
:param pool_maxsize: The maximum number of connections to save in the pool.
:param max_retries: The maximum number of retries each connection
should attempt. Note, this applies only to failed DNS lookups, socket
connections and connection timeouts, never to requests where data has
made it to the server. By default, Requests does not retry failed
connections. If you need granular control over the conditions under
which we retry a request, import urllib3's ``Retry`` class and pass
that instead.
:param pool_block: Whether the connection pool should block for connections.
Usage::
>>> import requests
>>> s = requests.Session()
>>> a = requests.adapters.HTTPAdapter(max_retries=3)
>>> s.mount('http://', a)
"""
__attrs__ = [
"max_retries",
"config",
"_pool_connections",
"_pool_maxsize",
"_pool_block",
]
def __init__(
self,
pool_connections=DEFAULT_POOLSIZE,
pool_maxsize=DEFAULT_POOLSIZE,
max_retries=DEFAULT_RETRIES,
pool_block=DEFAULT_POOLBLOCK,
):
if max_retries == DEFAULT_RETRIES:
self.max_retries = Retry(0, read=False)
else:
self.max_retries = Retry.from_int(max_retries)
self.config = {}
self.proxy_manager = {}
super().__init__()
self._pool_connections = pool_connections
self._pool_maxsize = pool_maxsize
self._pool_block = pool_block
self.init_poolmanager(pool_connections, pool_maxsize, block=pool_block)
def __getstate__(self):
return {attr: getattr(self, attr, None) for attr in self.__attrs__}
def __setstate__(self, state):
# Can't handle by adding 'proxy_manager' to self.__attrs__ because
# self.poolmanager uses a lambda function, which isn't pickleable.
self.proxy_manager = {}
self.config = {}
for attr, value in state.items():
setattr(self, attr, value)
self.init_poolmanager(
self._pool_connections, self._pool_maxsize, block=self._pool_block
)
def init_poolmanager(
self, connections, maxsize, block=DEFAULT_POOLBLOCK, **pool_kwargs
):
"""Initializes a urllib3 PoolManager.
This method should not be called from user code, and is only
exposed for use when subclassing the
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
:param connections: The number of urllib3 connection pools to cache.
:param maxsize: The maximum number of connections to save in the pool.
:param block: Block when no free connections are available.
:param pool_kwargs: Extra keyword arguments used to initialize the Pool Manager.
"""
# save these values for pickling
self._pool_connections = connections
self._pool_maxsize = maxsize
self._pool_block = block
self.poolmanager = PoolManager(
num_pools=connections,
maxsize=maxsize,
block=block,
**pool_kwargs,
)
def proxy_manager_for(self, proxy, **proxy_kwargs):
"""Return urllib3 ProxyManager for the given proxy.
This method should not be called from user code, and is only
exposed for use when subclassing the
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
:param proxy: The proxy to return a urllib3 ProxyManager for.
:param proxy_kwargs: Extra keyword arguments used to configure the Proxy Manager.
:returns: ProxyManager
:rtype: urllib3.ProxyManager
"""
if proxy in self.proxy_manager:
manager = self.proxy_manager[proxy]
elif proxy.lower().startswith("socks"):
username, password = get_auth_from_url(proxy)
manager = self.proxy_manager[proxy] = SOCKSProxyManager(
proxy,
username=username,
password=password,
num_pools=self._pool_connections,
maxsize=self._pool_maxsize,
block=self._pool_block,
**proxy_kwargs,
)
else:
proxy_headers = self.proxy_headers(proxy)
manager = self.proxy_manager[proxy] = proxy_from_url(
proxy,
proxy_headers=proxy_headers,
num_pools=self._pool_connections,
maxsize=self._pool_maxsize,
block=self._pool_block,
**proxy_kwargs,
)
return manager
def cert_verify(self, conn, url, verify, cert):
"""Verify a SSL certificate. This method should not be called from user
code, and is only exposed for use when subclassing the
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
:param conn: The urllib3 connection object associated with the cert.
:param url: The requested URL.
:param verify: Either a boolean, in which case it controls whether we verify
the server's TLS certificate, or a string, in which case it must be a path
to a CA bundle to use
:param cert: The SSL certificate to verify.
"""
if url.lower().startswith("https") and verify:
cert_loc = None
# Allow self-specified cert location.
if verify is not True:
cert_loc = verify
if not cert_loc:
cert_loc = extract_zipped_paths(DEFAULT_CA_BUNDLE_PATH)
if not cert_loc or not os.path.exists(cert_loc):
raise OSError(
f"Could not find a suitable TLS CA certificate bundle, "
f"invalid path: {cert_loc}"
)
conn.cert_reqs = "CERT_REQUIRED"
if not os.path.isdir(cert_loc):
conn.ca_certs = cert_loc
else:
conn.ca_cert_dir = cert_loc
else:
conn.cert_reqs = "CERT_NONE"
conn.ca_certs = None
conn.ca_cert_dir = None
if cert:
if not isinstance(cert, basestring):
conn.cert_file = cert[0]
conn.key_file = cert[1]
else:
conn.cert_file = cert
conn.key_file = None
if conn.cert_file and not os.path.exists(conn.cert_file):
raise OSError(
f"Could not find the TLS certificate file, "
f"invalid path: {conn.cert_file}"
)
if conn.key_file and not os.path.exists(conn.key_file):
raise OSError(
f"Could not find the TLS key file, invalid path: {conn.key_file}"
)
def build_response(self, req, resp):
"""Builds a :class:`Response <requests.Response>` object from a urllib3
response. This should not be called from user code, and is only exposed
for use when subclassing the
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`
:param req: The :class:`PreparedRequest <PreparedRequest>` used to generate the response.
:param resp: The urllib3 response object.
:rtype: requests.Response
"""
response = Response()
# Fallback to None if there's no status_code, for whatever reason.
response.status_code = getattr(resp, "status", None)
# Make headers case-insensitive.
response.headers = CaseInsensitiveDict(getattr(resp, "headers", {}))
# Set encoding.
response.encoding = get_encoding_from_headers(response.headers)
response.raw = resp
response.reason = response.raw.reason
if isinstance(req.url, bytes):
response.url = req.url.decode("utf-8")
else:
response.url = req.url
# Add new cookies from the server.
extract_cookies_to_jar(response.cookies, req, resp)
# Give the Response some context.
response.request = req
response.connection = self
return response
def get_connection(self, url, proxies=None):
"""Returns a urllib3 connection for the given URL. This should not be
called from user code, and is only exposed for use when subclassing the
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
:param url: The URL to connect to.
:param proxies: (optional) A Requests-style dictionary of proxies used on this request.
:rtype: urllib3.ConnectionPool
"""
proxy = select_proxy(url, proxies)
if proxy:
proxy = prepend_scheme_if_needed(proxy, "http")
proxy_url = parse_url(proxy)
if not proxy_url.host:
raise InvalidProxyURL(
"Please check proxy URL. It is malformed "
"and could be missing the host."
)
proxy_manager = self.proxy_manager_for(proxy)
conn = proxy_manager.connection_from_url(url)
else:
# Only scheme should be lower case
parsed = urlparse(url)
url = parsed.geturl()
conn = self.poolmanager.connection_from_url(url)
return conn
def close(self):
"""Disposes of any internal state.
Currently, this closes the PoolManager and any active ProxyManager,
which closes any pooled connections.
"""
self.poolmanager.clear()
for proxy in self.proxy_manager.values():
proxy.clear()
def request_url(self, request, proxies):
"""Obtain the url to use when making the final request.
If the message is being sent through a HTTP proxy, the full URL has to
be used. Otherwise, we should only use the path portion of the URL.
This should not be called from user code, and is only exposed for use
when subclassing the
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
:param proxies: A dictionary of schemes or schemes and hosts to proxy URLs.
:rtype: str
"""
proxy = select_proxy(request.url, proxies)
scheme = urlparse(request.url).scheme
is_proxied_http_request = proxy and scheme != "https"
using_socks_proxy = False
if proxy:
proxy_scheme = urlparse(proxy).scheme.lower()
using_socks_proxy = proxy_scheme.startswith("socks")
url = request.path_url
if is_proxied_http_request and not using_socks_proxy:
url = urldefragauth(request.url)
return url
def add_headers(self, request, **kwargs):
"""Add any headers needed by the connection. As of v2.0 this does
nothing by default, but is left for overriding by users that subclass
the :class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
This should not be called from user code, and is only exposed for use
when subclassing the
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
:param request: The :class:`PreparedRequest <PreparedRequest>` to add headers to.
:param kwargs: The keyword arguments from the call to send().
"""
pass
def proxy_headers(self, proxy):
"""Returns a dictionary of the headers to add to any request sent
through a proxy. This works with urllib3 magic to ensure that they are
correctly sent to the proxy, rather than in a tunnelled request if
CONNECT is being used.
This should not be called from user code, and is only exposed for use
when subclassing the
:class:`HTTPAdapter <requests.adapters.HTTPAdapter>`.
:param proxy: The url of the proxy being used for this request.
:rtype: dict
"""
headers = {}
username, password = get_auth_from_url(proxy)
if username:
headers["Proxy-Authorization"] = _basic_auth_str(username, password)
return headers
def send(
self, request, stream=False, timeout=None, verify=True, cert=None, proxies=None
):
"""Sends PreparedRequest object. Returns Response object.
:param request: The :class:`PreparedRequest <PreparedRequest>` being sent.
:param stream: (optional) Whether to stream the request content.
:param timeout: (optional) How long to wait for the server to send
data before giving up, as a float, or a :ref:`(connect timeout,
read timeout) <timeouts>` tuple.
:type timeout: float or tuple or urllib3 Timeout object
:param verify: (optional) Either a boolean, in which case it controls whether
we verify the server's TLS certificate, or a string, in which case it
must be a path to a CA bundle to use
:param cert: (optional) Any user-provided SSL certificate to be trusted.
:param proxies: (optional) The proxies dictionary to apply to the request.
:rtype: requests.Response
"""
try:
conn = self.get_connection(request.url, proxies)
except LocationValueError as e:
raise InvalidURL(e, request=request)
self.cert_verify(conn, request.url, verify, cert)
url = self.request_url(request, proxies)
self.add_headers(
request,
stream=stream,
timeout=timeout,
verify=verify,
cert=cert,
proxies=proxies,
)
chunked = not (request.body is None or "Content-Length" in request.headers)
if isinstance(timeout, tuple):
try:
connect, read = timeout
timeout = TimeoutSauce(connect=connect, read=read)
except ValueError:
raise ValueError(
f"Invalid timeout {timeout}. Pass a (connect, read) timeout tuple, "
f"or a single float to set both timeouts to the same value."
)
elif isinstance(timeout, TimeoutSauce):
pass
else:
timeout = TimeoutSauce(connect=timeout, read=timeout)
try:
resp = conn.urlopen(
method=request.method,
url=url,
body=request.body,
headers=request.headers,
redirect=False,
assert_same_host=False,
preload_content=False,
decode_content=False,
retries=self.max_retries,
timeout=timeout,
chunked=chunked,
)
except (ProtocolError, OSError) as err:
raise ConnectionError(err, request=request)
except MaxRetryError as e:
if isinstance(e.reason, ConnectTimeoutError):
# TODO: Remove this in 3.0.0: see #2811
if not isinstance(e.reason, NewConnectionError):
raise ConnectTimeout(e, request=request)
if isinstance(e.reason, ResponseError):
raise RetryError(e, request=request)
if isinstance(e.reason, _ProxyError):
raise ProxyError(e, request=request)
if isinstance(e.reason, _SSLError):
# This branch is for urllib3 v1.22 and later.
raise SSLError(e, request=request)
raise ConnectionError(e, request=request)
except ClosedPoolError as e:
raise ConnectionError(e, request=request)
except _ProxyError as e:
raise ProxyError(e)
except (_SSLError, _HTTPError) as e:
if isinstance(e, _SSLError):
# This branch is for urllib3 versions earlier than v1.22
raise SSLError(e, request=request)
elif isinstance(e, ReadTimeoutError):
raise ReadTimeout(e, request=request)
elif isinstance(e, _InvalidHeader):
raise InvalidHeader(e, request=request)
else:
raise
return self.build_response(request, resp)
-157
View File
@@ -1,157 +0,0 @@
"""
requests.api
~~~~~~~~~~~~
This module implements the Requests API.
:copyright: (c) 2012 by Kenneth Reitz.
:license: Apache2, see LICENSE for more details.
"""
from . import sessions
def request(method, url, **kwargs):
"""Constructs and sends a :class:`Request <Request>`.
:param method: method for the new :class:`Request` object: ``GET``, ``OPTIONS``, ``HEAD``, ``POST``, ``PUT``, ``PATCH``, or ``DELETE``.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary, list of tuples or bytes to send
in the query string for the :class:`Request`.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to send with the :class:`Request`.
:param cookies: (optional) Dict or CookieJar object to send with the :class:`Request`.
:param files: (optional) Dictionary of ``'name': file-like-objects`` (or ``{'name': file-tuple}``) for multipart encoding upload.
``file-tuple`` can be a 2-tuple ``('filename', fileobj)``, 3-tuple ``('filename', fileobj, 'content_type')``
or a 4-tuple ``('filename', fileobj, 'content_type', custom_headers)``, where ``'content-type'`` is a string
defining the content type of the given file and ``custom_headers`` a dict-like object containing additional headers
to add for the file.
:param auth: (optional) Auth tuple to enable Basic/Digest/Custom HTTP Auth.
:param timeout: (optional) How many seconds to wait for the server to send data
before giving up, as a float, or a :ref:`(connect timeout, read
timeout) <timeouts>` tuple.
:type timeout: float or tuple
:param allow_redirects: (optional) Boolean. Enable/disable GET/OPTIONS/POST/PUT/PATCH/DELETE/HEAD redirection. Defaults to ``True``.
:type allow_redirects: bool
:param proxies: (optional) Dictionary mapping protocol to the URL of the proxy.
:param verify: (optional) Either a boolean, in which case it controls whether we verify
the server's TLS certificate, or a string, in which case it must be a path
to a CA bundle to use. Defaults to ``True``.
:param stream: (optional) if ``False``, the response content will be immediately downloaded.
:param cert: (optional) if String, path to ssl client cert file (.pem). If Tuple, ('cert', 'key') pair.
:return: :class:`Response <Response>` object
:rtype: requests.Response
Usage::
>>> import requests
>>> req = requests.request('GET', 'https://httpbin.org/get')
>>> req
<Response [200]>
"""
# By using the 'with' statement we are sure the session is closed, thus we
# avoid leaving sockets open which can trigger a ResourceWarning in some
# cases, and look like a memory leak in others.
with sessions.Session() as session:
return session.request(method=method, url=url, **kwargs)
def get(url, params=None, **kwargs):
r"""Sends a GET request.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary, list of tuples or bytes to send
in the query string for the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request("get", url, params=params, **kwargs)
def options(url, **kwargs):
r"""Sends an OPTIONS request.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request("options", url, **kwargs)
def head(url, **kwargs):
r"""Sends a HEAD request.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes. If
`allow_redirects` is not provided, it will be set to `False` (as
opposed to the default :meth:`request` behavior).
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
kwargs.setdefault("allow_redirects", False)
return request("head", url, **kwargs)
def post(url, data=None, json=None, **kwargs):
r"""Sends a POST request.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request("post", url, data=data, json=json, **kwargs)
def put(url, data=None, **kwargs):
r"""Sends a PUT request.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request("put", url, data=data, **kwargs)
def patch(url, data=None, **kwargs):
r"""Sends a PATCH request.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) A JSON serializable Python object to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request("patch", url, data=data, **kwargs)
def delete(url, **kwargs):
r"""Sends a DELETE request.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:return: :class:`Response <Response>` object
:rtype: requests.Response
"""
return request("delete", url, **kwargs)
-315
View File
@@ -1,315 +0,0 @@
"""
requests.auth
~~~~~~~~~~~~~
This module contains the authentication handlers for Requests.
"""
import hashlib
import os
import re
import threading
import time
import warnings
from base64 import b64encode
from ._internal_utils import to_native_string
from .compat import basestring, str, urlparse
from .cookies import extract_cookies_to_jar
from .utils import parse_dict_header
CONTENT_TYPE_FORM_URLENCODED = "application/x-www-form-urlencoded"
CONTENT_TYPE_MULTI_PART = "multipart/form-data"
def _basic_auth_str(username, password):
"""Returns a Basic Auth string."""
# "I want us to put a big-ol' comment on top of it that
# says that this behaviour is dumb but we need to preserve
# it because people are relying on it."
# - Lukasa
#
# These are here solely to maintain backwards compatibility
# for things like ints. This will be removed in 3.0.0.
if not isinstance(username, basestring):
warnings.warn(
"Non-string usernames will no longer be supported in Requests "
"3.0.0. Please convert the object you've passed in ({!r}) to "
"a string or bytes object in the near future to avoid "
"problems.".format(username),
category=DeprecationWarning,
)
username = str(username)
if not isinstance(password, basestring):
warnings.warn(
"Non-string passwords will no longer be supported in Requests "
"3.0.0. Please convert the object you've passed in ({!r}) to "
"a string or bytes object in the near future to avoid "
"problems.".format(type(password)),
category=DeprecationWarning,
)
password = str(password)
# -- End Removal --
if isinstance(username, str):
username = username.encode("latin1")
if isinstance(password, str):
password = password.encode("latin1")
authstr = "Basic " + to_native_string(
b64encode(b":".join((username, password))).strip()
)
return authstr
class AuthBase:
"""Base class that all auth implementations derive from"""
def __call__(self, r):
raise NotImplementedError("Auth hooks must be callable.")
class HTTPBasicAuth(AuthBase):
"""Attaches HTTP Basic Authentication to the given Request object."""
def __init__(self, username, password):
self.username = username
self.password = password
def __eq__(self, other):
return all(
[
self.username == getattr(other, "username", None),
self.password == getattr(other, "password", None),
]
)
def __ne__(self, other):
return not self == other
def __call__(self, r):
r.headers["Authorization"] = _basic_auth_str(self.username, self.password)
return r
class HTTPProxyAuth(HTTPBasicAuth):
"""Attaches HTTP Proxy Authentication to a given Request object."""
def __call__(self, r):
r.headers["Proxy-Authorization"] = _basic_auth_str(self.username, self.password)
return r
class HTTPDigestAuth(AuthBase):
"""Attaches HTTP Digest Authentication to the given Request object."""
def __init__(self, username, password):
self.username = username
self.password = password
# Keep state in per-thread local storage
self._thread_local = threading.local()
def init_per_thread_state(self):
# Ensure state is initialized just once per-thread
if not hasattr(self._thread_local, "init"):
self._thread_local.init = True
self._thread_local.last_nonce = ""
self._thread_local.nonce_count = 0
self._thread_local.chal = {}
self._thread_local.pos = None
self._thread_local.num_401_calls = None
def build_digest_header(self, method, url):
"""
:rtype: str
"""
realm = self._thread_local.chal["realm"]
nonce = self._thread_local.chal["nonce"]
qop = self._thread_local.chal.get("qop")
algorithm = self._thread_local.chal.get("algorithm")
opaque = self._thread_local.chal.get("opaque")
hash_utf8 = None
if algorithm is None:
_algorithm = "MD5"
else:
_algorithm = algorithm.upper()
# lambdas assume digest modules are imported at the top level
if _algorithm == "MD5" or _algorithm == "MD5-SESS":
def md5_utf8(x):
if isinstance(x, str):
x = x.encode("utf-8")
return hashlib.md5(x).hexdigest()
hash_utf8 = md5_utf8
elif _algorithm == "SHA":
def sha_utf8(x):
if isinstance(x, str):
x = x.encode("utf-8")
return hashlib.sha1(x).hexdigest()
hash_utf8 = sha_utf8
elif _algorithm == "SHA-256":
def sha256_utf8(x):
if isinstance(x, str):
x = x.encode("utf-8")
return hashlib.sha256(x).hexdigest()
hash_utf8 = sha256_utf8
elif _algorithm == "SHA-512":
def sha512_utf8(x):
if isinstance(x, str):
x = x.encode("utf-8")
return hashlib.sha512(x).hexdigest()
hash_utf8 = sha512_utf8
KD = lambda s, d: hash_utf8(f"{s}:{d}") # noqa:E731
if hash_utf8 is None:
return None
# XXX not implemented yet
entdig = None
p_parsed = urlparse(url)
#: path is request-uri defined in RFC 2616 which should not be empty
path = p_parsed.path or "/"
if p_parsed.query:
path += f"?{p_parsed.query}"
A1 = f"{self.username}:{realm}:{self.password}"
A2 = f"{method}:{path}"
HA1 = hash_utf8(A1)
HA2 = hash_utf8(A2)
if nonce == self._thread_local.last_nonce:
self._thread_local.nonce_count += 1
else:
self._thread_local.nonce_count = 1
ncvalue = f"{self._thread_local.nonce_count:08x}"
s = str(self._thread_local.nonce_count).encode("utf-8")
s += nonce.encode("utf-8")
s += time.ctime().encode("utf-8")
s += os.urandom(8)
cnonce = hashlib.sha1(s).hexdigest()[:16]
if _algorithm == "MD5-SESS":
HA1 = hash_utf8(f"{HA1}:{nonce}:{cnonce}")
if not qop:
respdig = KD(HA1, f"{nonce}:{HA2}")
elif qop == "auth" or "auth" in qop.split(","):
noncebit = f"{nonce}:{ncvalue}:{cnonce}:auth:{HA2}"
respdig = KD(HA1, noncebit)
else:
# XXX handle auth-int.
return None
self._thread_local.last_nonce = nonce
# XXX should the partial digests be encoded too?
base = (
f'username="{self.username}", realm="{realm}", nonce="{nonce}", '
f'uri="{path}", response="{respdig}"'
)
if opaque:
base += f', opaque="{opaque}"'
if algorithm:
base += f', algorithm="{algorithm}"'
if entdig:
base += f', digest="{entdig}"'
if qop:
base += f', qop="auth", nc={ncvalue}, cnonce="{cnonce}"'
return f"Digest {base}"
def handle_redirect(self, r, **kwargs):
"""Reset num_401_calls counter on redirects."""
if r.is_redirect:
self._thread_local.num_401_calls = 1
def handle_401(self, r, **kwargs):
"""
Takes the given response and tries digest-auth, if needed.
:rtype: requests.Response
"""
# If response is not 4xx, do not auth
# See https://github.com/psf/requests/issues/3772
if not 400 <= r.status_code < 500:
self._thread_local.num_401_calls = 1
return r
if self._thread_local.pos is not None:
# Rewind the file position indicator of the body to where
# it was to resend the request.
r.request.body.seek(self._thread_local.pos)
s_auth = r.headers.get("www-authenticate", "")
if "digest" in s_auth.lower() and self._thread_local.num_401_calls < 2:
self._thread_local.num_401_calls += 1
pat = re.compile(r"digest ", flags=re.IGNORECASE)
self._thread_local.chal = parse_dict_header(pat.sub("", s_auth, count=1))
# Consume content and release the original connection
# to allow our new request to reuse the same one.
r.content
r.close()
prep = r.request.copy()
extract_cookies_to_jar(prep._cookies, r.request, r.raw)
prep.prepare_cookies(prep._cookies)
prep.headers["Authorization"] = self.build_digest_header(
prep.method, prep.url
)
_r = r.connection.send(prep, **kwargs)
_r.history.append(r)
_r.request = prep
return _r
self._thread_local.num_401_calls = 1
return r
def __call__(self, r):
# Initialize per-thread state, if needed
self.init_per_thread_state()
# If we have a saved nonce, skip the 401
if self._thread_local.last_nonce:
r.headers["Authorization"] = self.build_digest_header(r.method, r.url)
try:
self._thread_local.pos = r.body.tell()
except AttributeError:
# In the case of HTTPDigestAuth being reused and the body of
# the previous request was a file-like object, pos has the
# file position of the previous body. Ensure it's set to
# None.
self._thread_local.pos = None
r.register_hook("response", self.handle_401)
r.register_hook("response", self.handle_redirect)
self._thread_local.num_401_calls = 1
return r
def __eq__(self, other):
return all(
[
self.username == getattr(other, "username", None),
self.password == getattr(other, "password", None),
]
)
def __ne__(self, other):
return not self == other
-17
View File
@@ -1,17 +0,0 @@
#!/usr/bin/env python
"""
requests.certs
~~~~~~~~~~~~~~
This module returns the preferred default CA certificate bundle. There is
only one — the one from the certifi package.
If you are packaging Requests, e.g., for a Linux distribution or a managed
environment, you can change the definition of where() to return a separately
packaged CA bundle.
"""
from certifi import where
if __name__ == "__main__":
print(where())
-79
View File
@@ -1,79 +0,0 @@
"""
requests.compat
~~~~~~~~~~~~~~~
This module previously handled import compatibility issues
between Python 2 and Python 3. It remains for backwards
compatibility until the next major version.
"""
try:
import chardet
except ImportError:
import charset_normalizer as chardet
import sys
# -------
# Pythons
# -------
# Syntax sugar.
_ver = sys.version_info
#: Python 2.x?
is_py2 = _ver[0] == 2
#: Python 3.x?
is_py3 = _ver[0] == 3
# json/simplejson module import resolution
has_simplejson = False
try:
import simplejson as json
has_simplejson = True
except ImportError:
import json
if has_simplejson:
from simplejson import JSONDecodeError
else:
from json import JSONDecodeError
# Keep OrderedDict for backwards compatibility.
from collections import OrderedDict
from collections.abc import Callable, Mapping, MutableMapping
from http import cookiejar as cookielib
from http.cookies import Morsel
from io import StringIO
# --------------
# Legacy Imports
# --------------
from urllib.parse import (
quote,
quote_plus,
unquote,
unquote_plus,
urldefrag,
urlencode,
urljoin,
urlparse,
urlsplit,
urlunparse,
)
from urllib.request import (
getproxies,
getproxies_environment,
parse_http_list,
proxy_bypass,
proxy_bypass_environment,
)
builtin_str = str
str = str
bytes = bytes
basestring = (str, bytes)
numeric_types = (int, float)
integer_types = (int,)
-561
View File
@@ -1,561 +0,0 @@
"""
requests.cookies
~~~~~~~~~~~~~~~~
Compatibility code to be able to use `cookielib.CookieJar` with requests.
requests.utils imports from here, so be careful with imports.
"""
import calendar
import copy
import time
from ._internal_utils import to_native_string
from .compat import Morsel, MutableMapping, cookielib, urlparse, urlunparse
try:
import threading
except ImportError:
import dummy_threading as threading
class MockRequest:
"""Wraps a `requests.Request` to mimic a `urllib2.Request`.
The code in `cookielib.CookieJar` expects this interface in order to correctly
manage cookie policies, i.e., determine whether a cookie can be set, given the
domains of the request and the cookie.
The original request object is read-only. The client is responsible for collecting
the new headers via `get_new_headers()` and interpreting them appropriately. You
probably want `get_cookie_header`, defined below.
"""
def __init__(self, request):
self._r = request
self._new_headers = {}
self.type = urlparse(self._r.url).scheme
def get_type(self):
return self.type
def get_host(self):
return urlparse(self._r.url).netloc
def get_origin_req_host(self):
return self.get_host()
def get_full_url(self):
# Only return the response's URL if the user hadn't set the Host
# header
if not self._r.headers.get("Host"):
return self._r.url
# If they did set it, retrieve it and reconstruct the expected domain
host = to_native_string(self._r.headers["Host"], encoding="utf-8")
parsed = urlparse(self._r.url)
# Reconstruct the URL as we expect it
return urlunparse(
[
parsed.scheme,
host,
parsed.path,
parsed.params,
parsed.query,
parsed.fragment,
]
)
def is_unverifiable(self):
return True
def has_header(self, name):
return name in self._r.headers or name in self._new_headers
def get_header(self, name, default=None):
return self._r.headers.get(name, self._new_headers.get(name, default))
def add_header(self, key, val):
"""cookielib has no legitimate use for this method; add it back if you find one."""
raise NotImplementedError(
"Cookie headers should be added with add_unredirected_header()"
)
def add_unredirected_header(self, name, value):
self._new_headers[name] = value
def get_new_headers(self):
return self._new_headers
@property
def unverifiable(self):
return self.is_unverifiable()
@property
def origin_req_host(self):
return self.get_origin_req_host()
@property
def host(self):
return self.get_host()
class MockResponse:
"""Wraps a `httplib.HTTPMessage` to mimic a `urllib.addinfourl`.
...what? Basically, expose the parsed HTTP headers from the server response
the way `cookielib` expects to see them.
"""
def __init__(self, headers):
"""Make a MockResponse for `cookielib` to read.
:param headers: a httplib.HTTPMessage or analogous carrying the headers
"""
self._headers = headers
def info(self):
return self._headers
def getheaders(self, name):
self._headers.getheaders(name)
def extract_cookies_to_jar(jar, request, response):
"""Extract the cookies from the response into a CookieJar.
:param jar: cookielib.CookieJar (not necessarily a RequestsCookieJar)
:param request: our own requests.Request object
:param response: urllib3.HTTPResponse object
"""
if not (hasattr(response, "_original_response") and response._original_response):
return
# the _original_response field is the wrapped httplib.HTTPResponse object,
req = MockRequest(request)
# pull out the HTTPMessage with the headers and put it in the mock:
res = MockResponse(response._original_response.msg)
jar.extract_cookies(res, req)
def get_cookie_header(jar, request):
"""
Produce an appropriate Cookie header string to be sent with `request`, or None.
:rtype: str
"""
r = MockRequest(request)
jar.add_cookie_header(r)
return r.get_new_headers().get("Cookie")
def remove_cookie_by_name(cookiejar, name, domain=None, path=None):
"""Unsets a cookie by name, by default over all domains and paths.
Wraps CookieJar.clear(), is O(n).
"""
clearables = []
for cookie in cookiejar:
if cookie.name != name:
continue
if domain is not None and domain != cookie.domain:
continue
if path is not None and path != cookie.path:
continue
clearables.append((cookie.domain, cookie.path, cookie.name))
for domain, path, name in clearables:
cookiejar.clear(domain, path, name)
class CookieConflictError(RuntimeError):
"""There are two cookies that meet the criteria specified in the cookie jar.
Use .get and .set and include domain and path args in order to be more specific.
"""
class RequestsCookieJar(cookielib.CookieJar, MutableMapping):
"""Compatibility class; is a cookielib.CookieJar, but exposes a dict
interface.
This is the CookieJar we create by default for requests and sessions that
don't specify one, since some clients may expect response.cookies and
session.cookies to support dict operations.
Requests does not use the dict interface internally; it's just for
compatibility with external client code. All requests code should work
out of the box with externally provided instances of ``CookieJar``, e.g.
``LWPCookieJar`` and ``FileCookieJar``.
Unlike a regular CookieJar, this class is pickleable.
.. warning:: dictionary operations that are normally O(1) may be O(n).
"""
def get(self, name, default=None, domain=None, path=None):
"""Dict-like get() that also supports optional domain and path args in
order to resolve naming collisions from using one cookie jar over
multiple domains.
.. warning:: operation is O(n), not O(1).
"""
try:
return self._find_no_duplicates(name, domain, path)
except KeyError:
return default
def set(self, name, value, **kwargs):
"""Dict-like set() that also supports optional domain and path args in
order to resolve naming collisions from using one cookie jar over
multiple domains.
"""
# support client code that unsets cookies by assignment of a None value:
if value is None:
remove_cookie_by_name(
self, name, domain=kwargs.get("domain"), path=kwargs.get("path")
)
return
if isinstance(value, Morsel):
c = morsel_to_cookie(value)
else:
c = create_cookie(name, value, **kwargs)
self.set_cookie(c)
return c
def iterkeys(self):
"""Dict-like iterkeys() that returns an iterator of names of cookies
from the jar.
.. seealso:: itervalues() and iteritems().
"""
for cookie in iter(self):
yield cookie.name
def keys(self):
"""Dict-like keys() that returns a list of names of cookies from the
jar.
.. seealso:: values() and items().
"""
return list(self.iterkeys())
def itervalues(self):
"""Dict-like itervalues() that returns an iterator of values of cookies
from the jar.
.. seealso:: iterkeys() and iteritems().
"""
for cookie in iter(self):
yield cookie.value
def values(self):
"""Dict-like values() that returns a list of values of cookies from the
jar.
.. seealso:: keys() and items().
"""
return list(self.itervalues())
def iteritems(self):
"""Dict-like iteritems() that returns an iterator of name-value tuples
from the jar.
.. seealso:: iterkeys() and itervalues().
"""
for cookie in iter(self):
yield cookie.name, cookie.value
def items(self):
"""Dict-like items() that returns a list of name-value tuples from the
jar. Allows client-code to call ``dict(RequestsCookieJar)`` and get a
vanilla python dict of key value pairs.
.. seealso:: keys() and values().
"""
return list(self.iteritems())
def list_domains(self):
"""Utility method to list all the domains in the jar."""
domains = []
for cookie in iter(self):
if cookie.domain not in domains:
domains.append(cookie.domain)
return domains
def list_paths(self):
"""Utility method to list all the paths in the jar."""
paths = []
for cookie in iter(self):
if cookie.path not in paths:
paths.append(cookie.path)
return paths
def multiple_domains(self):
"""Returns True if there are multiple domains in the jar.
Returns False otherwise.
:rtype: bool
"""
domains = []
for cookie in iter(self):
if cookie.domain is not None and cookie.domain in domains:
return True
domains.append(cookie.domain)
return False # there is only one domain in jar
def get_dict(self, domain=None, path=None):
"""Takes as an argument an optional domain and path and returns a plain
old Python dict of name-value pairs of cookies that meet the
requirements.
:rtype: dict
"""
dictionary = {}
for cookie in iter(self):
if (domain is None or cookie.domain == domain) and (
path is None or cookie.path == path
):
dictionary[cookie.name] = cookie.value
return dictionary
def __contains__(self, name):
try:
return super().__contains__(name)
except CookieConflictError:
return True
def __getitem__(self, name):
"""Dict-like __getitem__() for compatibility with client code. Throws
exception if there are more than one cookie with name. In that case,
use the more explicit get() method instead.
.. warning:: operation is O(n), not O(1).
"""
return self._find_no_duplicates(name)
def __setitem__(self, name, value):
"""Dict-like __setitem__ for compatibility with client code. Throws
exception if there is already a cookie of that name in the jar. In that
case, use the more explicit set() method instead.
"""
self.set(name, value)
def __delitem__(self, name):
"""Deletes a cookie given a name. Wraps ``cookielib.CookieJar``'s
``remove_cookie_by_name()``.
"""
remove_cookie_by_name(self, name)
def set_cookie(self, cookie, *args, **kwargs):
if (
hasattr(cookie.value, "startswith")
and cookie.value.startswith('"')
and cookie.value.endswith('"')
):
cookie.value = cookie.value.replace('\\"', "")
return super().set_cookie(cookie, *args, **kwargs)
def update(self, other):
"""Updates this jar with cookies from another CookieJar or dict-like"""
if isinstance(other, cookielib.CookieJar):
for cookie in other:
self.set_cookie(copy.copy(cookie))
else:
super().update(other)
def _find(self, name, domain=None, path=None):
"""Requests uses this method internally to get cookie values.
If there are conflicting cookies, _find arbitrarily chooses one.
See _find_no_duplicates if you want an exception thrown if there are
conflicting cookies.
:param name: a string containing name of cookie
:param domain: (optional) string containing domain of cookie
:param path: (optional) string containing path of cookie
:return: cookie.value
"""
for cookie in iter(self):
if cookie.name == name:
if domain is None or cookie.domain == domain:
if path is None or cookie.path == path:
return cookie.value
raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")
def _find_no_duplicates(self, name, domain=None, path=None):
"""Both ``__get_item__`` and ``get`` call this function: it's never
used elsewhere in Requests.
:param name: a string containing name of cookie
:param domain: (optional) string containing domain of cookie
:param path: (optional) string containing path of cookie
:raises KeyError: if cookie is not found
:raises CookieConflictError: if there are multiple cookies
that match name and optionally domain and path
:return: cookie.value
"""
toReturn = None
for cookie in iter(self):
if cookie.name == name:
if domain is None or cookie.domain == domain:
if path is None or cookie.path == path:
if toReturn is not None:
# if there are multiple cookies that meet passed in criteria
raise CookieConflictError(
f"There are multiple cookies with name, {name!r}"
)
# we will eventually return this as long as no cookie conflict
toReturn = cookie.value
if toReturn:
return toReturn
raise KeyError(f"name={name!r}, domain={domain!r}, path={path!r}")
def __getstate__(self):
"""Unlike a normal CookieJar, this class is pickleable."""
state = self.__dict__.copy()
# remove the unpickleable RLock object
state.pop("_cookies_lock")
return state
def __setstate__(self, state):
"""Unlike a normal CookieJar, this class is pickleable."""
self.__dict__.update(state)
if "_cookies_lock" not in self.__dict__:
self._cookies_lock = threading.RLock()
def copy(self):
"""Return a copy of this RequestsCookieJar."""
new_cj = RequestsCookieJar()
new_cj.set_policy(self.get_policy())
new_cj.update(self)
return new_cj
def get_policy(self):
"""Return the CookiePolicy instance used."""
return self._policy
def _copy_cookie_jar(jar):
if jar is None:
return None
if hasattr(jar, "copy"):
# We're dealing with an instance of RequestsCookieJar
return jar.copy()
# We're dealing with a generic CookieJar instance
new_jar = copy.copy(jar)
new_jar.clear()
for cookie in jar:
new_jar.set_cookie(copy.copy(cookie))
return new_jar
def create_cookie(name, value, **kwargs):
"""Make a cookie from underspecified parameters.
By default, the pair of `name` and `value` will be set for the domain ''
and sent on every request (this is sometimes called a "supercookie").
"""
result = {
"version": 0,
"name": name,
"value": value,
"port": None,
"domain": "",
"path": "/",
"secure": False,
"expires": None,
"discard": True,
"comment": None,
"comment_url": None,
"rest": {"HttpOnly": None},
"rfc2109": False,
}
badargs = set(kwargs) - set(result)
if badargs:
raise TypeError(
f"create_cookie() got unexpected keyword arguments: {list(badargs)}"
)
result.update(kwargs)
result["port_specified"] = bool(result["port"])
result["domain_specified"] = bool(result["domain"])
result["domain_initial_dot"] = result["domain"].startswith(".")
result["path_specified"] = bool(result["path"])
return cookielib.Cookie(**result)
def morsel_to_cookie(morsel):
"""Convert a Morsel object into a Cookie containing the one k/v pair."""
expires = None
if morsel["max-age"]:
try:
expires = int(time.time() + int(morsel["max-age"]))
except ValueError:
raise TypeError(f"max-age: {morsel['max-age']} must be integer")
elif morsel["expires"]:
time_template = "%a, %d-%b-%Y %H:%M:%S GMT"
expires = calendar.timegm(time.strptime(morsel["expires"], time_template))
return create_cookie(
comment=morsel["comment"],
comment_url=bool(morsel["comment"]),
discard=False,
domain=morsel["domain"],
expires=expires,
name=morsel.key,
path=morsel["path"],
port=None,
rest={"HttpOnly": morsel["httponly"]},
rfc2109=False,
secure=bool(morsel["secure"]),
value=morsel.value,
version=morsel["version"] or 0,
)
def cookiejar_from_dict(cookie_dict, cookiejar=None, overwrite=True):
"""Returns a CookieJar from a key/value dictionary.
:param cookie_dict: Dict of key/values to insert into CookieJar.
:param cookiejar: (optional) A cookiejar to add the cookies to.
:param overwrite: (optional) If False, will not replace cookies
already in the jar with new ones.
:rtype: CookieJar
"""
if cookiejar is None:
cookiejar = RequestsCookieJar()
if cookie_dict is not None:
names_from_jar = [cookie.name for cookie in cookiejar]
for name in cookie_dict:
if overwrite or (name not in names_from_jar):
cookiejar.set_cookie(create_cookie(name, cookie_dict[name]))
return cookiejar
def merge_cookies(cookiejar, cookies):
"""Add cookies to cookiejar and returns a merged CookieJar.
:param cookiejar: CookieJar object to add the cookies to.
:param cookies: Dictionary or CookieJar object to be added.
:rtype: CookieJar
"""
if not isinstance(cookiejar, cookielib.CookieJar):
raise ValueError("You can only merge into CookieJar")
if isinstance(cookies, dict):
cookiejar = cookiejar_from_dict(cookies, cookiejar=cookiejar, overwrite=False)
elif isinstance(cookies, cookielib.CookieJar):
try:
cookiejar.update(cookies)
except AttributeError:
for cookie_in_jar in cookies:
cookiejar.set_cookie(cookie_in_jar)
return cookiejar
-141
View File
@@ -1,141 +0,0 @@
"""
requests.exceptions
~~~~~~~~~~~~~~~~~~~
This module contains the set of Requests' exceptions.
"""
from urllib3.exceptions import HTTPError as BaseHTTPError
from .compat import JSONDecodeError as CompatJSONDecodeError
class RequestException(IOError):
"""There was an ambiguous exception that occurred while handling your
request.
"""
def __init__(self, *args, **kwargs):
"""Initialize RequestException with `request` and `response` objects."""
response = kwargs.pop("response", None)
self.response = response
self.request = kwargs.pop("request", None)
if response is not None and not self.request and hasattr(response, "request"):
self.request = self.response.request
super().__init__(*args, **kwargs)
class InvalidJSONError(RequestException):
"""A JSON error occurred."""
class JSONDecodeError(InvalidJSONError, CompatJSONDecodeError):
"""Couldn't decode the text into json"""
def __init__(self, *args, **kwargs):
"""
Construct the JSONDecodeError instance first with all
args. Then use it's args to construct the IOError so that
the json specific args aren't used as IOError specific args
and the error message from JSONDecodeError is preserved.
"""
CompatJSONDecodeError.__init__(self, *args)
InvalidJSONError.__init__(self, *self.args, **kwargs)
class HTTPError(RequestException):
"""An HTTP error occurred."""
class ConnectionError(RequestException):
"""A Connection error occurred."""
class ProxyError(ConnectionError):
"""A proxy error occurred."""
class SSLError(ConnectionError):
"""An SSL error occurred."""
class Timeout(RequestException):
"""The request timed out.
Catching this error will catch both
:exc:`~requests.exceptions.ConnectTimeout` and
:exc:`~requests.exceptions.ReadTimeout` errors.
"""
class ConnectTimeout(ConnectionError, Timeout):
"""The request timed out while trying to connect to the remote server.
Requests that produced this error are safe to retry.
"""
class ReadTimeout(Timeout):
"""The server did not send any data in the allotted amount of time."""
class URLRequired(RequestException):
"""A valid URL is required to make a request."""
class TooManyRedirects(RequestException):
"""Too many redirects."""
class MissingSchema(RequestException, ValueError):
"""The URL scheme (e.g. http or https) is missing."""
class InvalidSchema(RequestException, ValueError):
"""The URL scheme provided is either invalid or unsupported."""
class InvalidURL(RequestException, ValueError):
"""The URL provided was somehow invalid."""
class InvalidHeader(RequestException, ValueError):
"""The header value provided was somehow invalid."""
class InvalidProxyURL(InvalidURL):
"""The proxy URL provided is invalid."""
class ChunkedEncodingError(RequestException):
"""The server declared chunked encoding but sent an invalid chunk."""
class ContentDecodingError(RequestException, BaseHTTPError):
"""Failed to decode response content."""
class StreamConsumedError(RequestException, TypeError):
"""The content for this response was already consumed."""
class RetryError(RequestException):
"""Custom retries logic failed"""
class UnrewindableBodyError(RequestException):
"""Requests encountered an error when trying to rewind a body."""
# Warnings
class RequestsWarning(Warning):
"""Base warning for Requests."""
class FileModeWarning(RequestsWarning, DeprecationWarning):
"""A file was opened in text mode, but Requests determined its binary length."""
class RequestsDependencyWarning(RequestsWarning):
"""An imported dependency doesn't match the expected version range."""
-134
View File
@@ -1,134 +0,0 @@
"""Module containing bug report helper(s)."""
import json
import platform
import ssl
import sys
import idna
import urllib3
from . import __version__ as requests_version
try:
import charset_normalizer
except ImportError:
charset_normalizer = None
try:
import chardet
except ImportError:
chardet = None
try:
from urllib3.contrib import pyopenssl
except ImportError:
pyopenssl = None
OpenSSL = None
cryptography = None
else:
import cryptography
import OpenSSL
def _implementation():
"""Return a dict with the Python implementation and version.
Provide both the name and the version of the Python implementation
currently running. For example, on CPython 3.10.3 it will return
{'name': 'CPython', 'version': '3.10.3'}.
This function works best on CPython and PyPy: in particular, it probably
doesn't work for Jython or IronPython. Future investigation should be done
to work out the correct shape of the code for those platforms.
"""
implementation = platform.python_implementation()
if implementation == "CPython":
implementation_version = platform.python_version()
elif implementation == "PyPy":
implementation_version = "{}.{}.{}".format(
sys.pypy_version_info.major,
sys.pypy_version_info.minor,
sys.pypy_version_info.micro,
)
if sys.pypy_version_info.releaselevel != "final":
implementation_version = "".join(
[implementation_version, sys.pypy_version_info.releaselevel]
)
elif implementation == "Jython":
implementation_version = platform.python_version() # Complete Guess
elif implementation == "IronPython":
implementation_version = platform.python_version() # Complete Guess
else:
implementation_version = "Unknown"
return {"name": implementation, "version": implementation_version}
def info():
"""Generate information for a bug report."""
try:
platform_info = {
"system": platform.system(),
"release": platform.release(),
}
except OSError:
platform_info = {
"system": "Unknown",
"release": "Unknown",
}
implementation_info = _implementation()
urllib3_info = {"version": urllib3.__version__}
charset_normalizer_info = {"version": None}
chardet_info = {"version": None}
if charset_normalizer:
charset_normalizer_info = {"version": charset_normalizer.__version__}
if chardet:
chardet_info = {"version": chardet.__version__}
pyopenssl_info = {
"version": None,
"openssl_version": "",
}
if OpenSSL:
pyopenssl_info = {
"version": OpenSSL.__version__,
"openssl_version": f"{OpenSSL.SSL.OPENSSL_VERSION_NUMBER:x}",
}
cryptography_info = {
"version": getattr(cryptography, "__version__", ""),
}
idna_info = {
"version": getattr(idna, "__version__", ""),
}
system_ssl = ssl.OPENSSL_VERSION_NUMBER
system_ssl_info = {"version": f"{system_ssl:x}" if system_ssl is not None else ""}
return {
"platform": platform_info,
"implementation": implementation_info,
"system_ssl": system_ssl_info,
"using_pyopenssl": pyopenssl is not None,
"using_charset_normalizer": chardet is None,
"pyOpenSSL": pyopenssl_info,
"urllib3": urllib3_info,
"chardet": chardet_info,
"charset_normalizer": charset_normalizer_info,
"cryptography": cryptography_info,
"idna": idna_info,
"requests": {
"version": requests_version,
},
}
def main():
"""Pretty-print the bug information as JSON."""
print(json.dumps(info(), sort_keys=True, indent=2))
if __name__ == "__main__":
main()
-33
View File
@@ -1,33 +0,0 @@
"""
requests.hooks
~~~~~~~~~~~~~~
This module provides the capabilities for the Requests hooks system.
Available hooks:
``response``:
The response generated from a Request.
"""
HOOKS = ["response"]
def default_hooks():
return {event: [] for event in HOOKS}
# TODO: response is the only one
def dispatch_hook(key, hooks, hook_data, **kwargs):
"""Dispatches a hook dictionary on a given piece of data."""
hooks = hooks or {}
hooks = hooks.get(key)
if hooks:
if hasattr(hooks, "__call__"):
hooks = [hooks]
for hook in hooks:
_hook_data = hook(hook_data, **kwargs)
if _hook_data is not None:
hook_data = _hook_data
return hook_data
File diff suppressed because it is too large Load Diff
-28
View File
@@ -1,28 +0,0 @@
import sys
try:
import chardet
except ImportError:
import warnings
import charset_normalizer as chardet
warnings.filterwarnings("ignore", "Trying to detect", module="charset_normalizer")
# This code exists for backwards compatibility reasons.
# I don't like it either. Just look the other way. :)
for package in ("urllib3", "idna"):
locals()[package] = __import__(package)
# This traversal is apparently necessary such that the identities are
# preserved (requests.packages.urllib3.* is urllib3.*)
for mod in list(sys.modules):
if mod == package or mod.startswith(f"{package}."):
sys.modules[f"requests.packages.{mod}"] = sys.modules[mod]
target = chardet.__name__
for mod in list(sys.modules):
if mod == target or mod.startswith(f"{target}."):
target = target.replace(target, "chardet")
sys.modules[f"requests.packages.{target}"] = sys.modules[mod]
# Kinda cool, though, right?
-833
View File
@@ -1,833 +0,0 @@
"""
requests.sessions
~~~~~~~~~~~~~~~~~
This module provides a Session object to manage and persist settings across
requests (cookies, auth, proxies).
"""
import os
import sys
import time
from collections import OrderedDict
from datetime import timedelta
from ._internal_utils import to_native_string
from .adapters import HTTPAdapter
from .auth import _basic_auth_str
from .compat import Mapping, cookielib, urljoin, urlparse
from .cookies import (
RequestsCookieJar,
cookiejar_from_dict,
extract_cookies_to_jar,
merge_cookies,
)
from .exceptions import (
ChunkedEncodingError,
ContentDecodingError,
InvalidSchema,
TooManyRedirects,
)
from .hooks import default_hooks, dispatch_hook
# formerly defined here, reexposed here for backward compatibility
from .models import ( # noqa: F401
DEFAULT_REDIRECT_LIMIT,
REDIRECT_STATI,
PreparedRequest,
Request,
)
from .status_codes import codes
from .structures import CaseInsensitiveDict
from .utils import ( # noqa: F401
DEFAULT_PORTS,
default_headers,
get_auth_from_url,
get_environ_proxies,
get_netrc_auth,
requote_uri,
resolve_proxies,
rewind_body,
should_bypass_proxies,
to_key_val_list,
)
# Preferred clock, based on which one is more accurate on a given system.
if sys.platform == "win32":
preferred_clock = time.perf_counter
else:
preferred_clock = time.time
def merge_setting(request_setting, session_setting, dict_class=OrderedDict):
"""Determines appropriate setting for a given request, taking into account
the explicit setting on that request, and the setting in the session. If a
setting is a dictionary, they will be merged together using `dict_class`
"""
if session_setting is None:
return request_setting
if request_setting is None:
return session_setting
# Bypass if not a dictionary (e.g. verify)
if not (
isinstance(session_setting, Mapping) and isinstance(request_setting, Mapping)
):
return request_setting
merged_setting = dict_class(to_key_val_list(session_setting))
merged_setting.update(to_key_val_list(request_setting))
# Remove keys that are set to None. Extract keys first to avoid altering
# the dictionary during iteration.
none_keys = [k for (k, v) in merged_setting.items() if v is None]
for key in none_keys:
del merged_setting[key]
return merged_setting
def merge_hooks(request_hooks, session_hooks, dict_class=OrderedDict):
"""Properly merges both requests and session hooks.
This is necessary because when request_hooks == {'response': []}, the
merge breaks Session hooks entirely.
"""
if session_hooks is None or session_hooks.get("response") == []:
return request_hooks
if request_hooks is None or request_hooks.get("response") == []:
return session_hooks
return merge_setting(request_hooks, session_hooks, dict_class)
class SessionRedirectMixin:
def get_redirect_target(self, resp):
"""Receives a Response. Returns a redirect URI or ``None``"""
# Due to the nature of how requests processes redirects this method will
# be called at least once upon the original response and at least twice
# on each subsequent redirect response (if any).
# If a custom mixin is used to handle this logic, it may be advantageous
# to cache the redirect location onto the response object as a private
# attribute.
if resp.is_redirect:
location = resp.headers["location"]
# Currently the underlying http module on py3 decode headers
# in latin1, but empirical evidence suggests that latin1 is very
# rarely used with non-ASCII characters in HTTP headers.
# It is more likely to get UTF8 header rather than latin1.
# This causes incorrect handling of UTF8 encoded location headers.
# To solve this, we re-encode the location in latin1.
location = location.encode("latin1")
return to_native_string(location, "utf8")
return None
def should_strip_auth(self, old_url, new_url):
"""Decide whether Authorization header should be removed when redirecting"""
old_parsed = urlparse(old_url)
new_parsed = urlparse(new_url)
if old_parsed.hostname != new_parsed.hostname:
return True
# Special case: allow http -> https redirect when using the standard
# ports. This isn't specified by RFC 7235, but is kept to avoid
# breaking backwards compatibility with older versions of requests
# that allowed any redirects on the same host.
if (
old_parsed.scheme == "http"
and old_parsed.port in (80, None)
and new_parsed.scheme == "https"
and new_parsed.port in (443, None)
):
return False
# Handle default port usage corresponding to scheme.
changed_port = old_parsed.port != new_parsed.port
changed_scheme = old_parsed.scheme != new_parsed.scheme
default_port = (DEFAULT_PORTS.get(old_parsed.scheme, None), None)
if (
not changed_scheme
and old_parsed.port in default_port
and new_parsed.port in default_port
):
return False
# Standard case: root URI must match
return changed_port or changed_scheme
def resolve_redirects(
self,
resp,
req,
stream=False,
timeout=None,
verify=True,
cert=None,
proxies=None,
yield_requests=False,
**adapter_kwargs,
):
"""Receives a Response. Returns a generator of Responses or Requests."""
hist = [] # keep track of history
url = self.get_redirect_target(resp)
previous_fragment = urlparse(req.url).fragment
while url:
prepared_request = req.copy()
# Update history and keep track of redirects.
# resp.history must ignore the original request in this loop
hist.append(resp)
resp.history = hist[1:]
try:
resp.content # Consume socket so it can be released
except (ChunkedEncodingError, ContentDecodingError, RuntimeError):
resp.raw.read(decode_content=False)
if len(resp.history) >= self.max_redirects:
raise TooManyRedirects(
f"Exceeded {self.max_redirects} redirects.", response=resp
)
# Release the connection back into the pool.
resp.close()
# Handle redirection without scheme (see: RFC 1808 Section 4)
if url.startswith("//"):
parsed_rurl = urlparse(resp.url)
url = ":".join([to_native_string(parsed_rurl.scheme), url])
# Normalize url case and attach previous fragment if needed (RFC 7231 7.1.2)
parsed = urlparse(url)
if parsed.fragment == "" and previous_fragment:
parsed = parsed._replace(fragment=previous_fragment)
elif parsed.fragment:
previous_fragment = parsed.fragment
url = parsed.geturl()
# Facilitate relative 'location' headers, as allowed by RFC 7231.
# (e.g. '/path/to/resource' instead of 'http://domain.tld/path/to/resource')
# Compliant with RFC3986, we percent encode the url.
if not parsed.netloc:
url = urljoin(resp.url, requote_uri(url))
else:
url = requote_uri(url)
prepared_request.url = to_native_string(url)
self.rebuild_method(prepared_request, resp)
# https://github.com/psf/requests/issues/1084
if resp.status_code not in (
codes.temporary_redirect,
codes.permanent_redirect,
):
# https://github.com/psf/requests/issues/3490
purged_headers = ("Content-Length", "Content-Type", "Transfer-Encoding")
for header in purged_headers:
prepared_request.headers.pop(header, None)
prepared_request.body = None
headers = prepared_request.headers
headers.pop("Cookie", None)
# Extract any cookies sent on the response to the cookiejar
# in the new request. Because we've mutated our copied prepared
# request, use the old one that we haven't yet touched.
extract_cookies_to_jar(prepared_request._cookies, req, resp.raw)
merge_cookies(prepared_request._cookies, self.cookies)
prepared_request.prepare_cookies(prepared_request._cookies)
# Rebuild auth and proxy information.
proxies = self.rebuild_proxies(prepared_request, proxies)
self.rebuild_auth(prepared_request, resp)
# A failed tell() sets `_body_position` to `object()`. This non-None
# value ensures `rewindable` will be True, allowing us to raise an
# UnrewindableBodyError, instead of hanging the connection.
rewindable = prepared_request._body_position is not None and (
"Content-Length" in headers or "Transfer-Encoding" in headers
)
# Attempt to rewind consumed file-like object.
if rewindable:
rewind_body(prepared_request)
# Override the original request.
req = prepared_request
if yield_requests:
yield req
else:
resp = self.send(
req,
stream=stream,
timeout=timeout,
verify=verify,
cert=cert,
proxies=proxies,
allow_redirects=False,
**adapter_kwargs,
)
extract_cookies_to_jar(self.cookies, prepared_request, resp.raw)
# extract redirect url, if any, for the next loop
url = self.get_redirect_target(resp)
yield resp
def rebuild_auth(self, prepared_request, response):
"""When being redirected we may want to strip authentication from the
request to avoid leaking credentials. This method intelligently removes
and reapplies authentication where possible to avoid credential loss.
"""
headers = prepared_request.headers
url = prepared_request.url
if "Authorization" in headers and self.should_strip_auth(
response.request.url, url
):
# If we get redirected to a new host, we should strip out any
# authentication headers.
del headers["Authorization"]
# .netrc might have more auth for us on our new host.
new_auth = get_netrc_auth(url) if self.trust_env else None
if new_auth is not None:
prepared_request.prepare_auth(new_auth)
def rebuild_proxies(self, prepared_request, proxies):
"""This method re-evaluates the proxy configuration by considering the
environment variables. If we are redirected to a URL covered by
NO_PROXY, we strip the proxy configuration. Otherwise, we set missing
proxy keys for this URL (in case they were stripped by a previous
redirect).
This method also replaces the Proxy-Authorization header where
necessary.
:rtype: dict
"""
headers = prepared_request.headers
scheme = urlparse(prepared_request.url).scheme
new_proxies = resolve_proxies(prepared_request, proxies, self.trust_env)
if "Proxy-Authorization" in headers:
del headers["Proxy-Authorization"]
try:
username, password = get_auth_from_url(new_proxies[scheme])
except KeyError:
username, password = None, None
# urllib3 handles proxy authorization for us in the standard adapter.
# Avoid appending this to TLS tunneled requests where it may be leaked.
if not scheme.startswith('https') and username and password:
headers["Proxy-Authorization"] = _basic_auth_str(username, password)
return new_proxies
def rebuild_method(self, prepared_request, response):
"""When being redirected we may want to change the method of the request
based on certain specs or browser behavior.
"""
method = prepared_request.method
# https://tools.ietf.org/html/rfc7231#section-6.4.4
if response.status_code == codes.see_other and method != "HEAD":
method = "GET"
# Do what the browsers do, despite standards...
# First, turn 302s into GETs.
if response.status_code == codes.found and method != "HEAD":
method = "GET"
# Second, if a POST is responded to with a 301, turn it into a GET.
# This bizarre behaviour is explained in Issue 1704.
if response.status_code == codes.moved and method == "POST":
method = "GET"
prepared_request.method = method
class Session(SessionRedirectMixin):
"""A Requests session.
Provides cookie persistence, connection-pooling, and configuration.
Basic Usage::
>>> import requests
>>> s = requests.Session()
>>> s.get('https://httpbin.org/get')
<Response [200]>
Or as a context manager::
>>> with requests.Session() as s:
... s.get('https://httpbin.org/get')
<Response [200]>
"""
__attrs__ = [
"headers",
"cookies",
"auth",
"proxies",
"hooks",
"params",
"verify",
"cert",
"adapters",
"stream",
"trust_env",
"max_redirects",
]
def __init__(self):
#: A case-insensitive dictionary of headers to be sent on each
#: :class:`Request <Request>` sent from this
#: :class:`Session <Session>`.
self.headers = default_headers()
#: Default Authentication tuple or object to attach to
#: :class:`Request <Request>`.
self.auth = None
#: Dictionary mapping protocol or protocol and host to the URL of the proxy
#: (e.g. {'http': 'foo.bar:3128', 'http://host.name': 'foo.bar:4012'}) to
#: be used on each :class:`Request <Request>`.
self.proxies = {}
#: Event-handling hooks.
self.hooks = default_hooks()
#: Dictionary of querystring data to attach to each
#: :class:`Request <Request>`. The dictionary values may be lists for
#: representing multivalued query parameters.
self.params = {}
#: Stream response content default.
self.stream = False
#: SSL Verification default.
#: Defaults to `True`, requiring requests to verify the TLS certificate at the
#: remote end.
#: If verify is set to `False`, requests will accept any TLS certificate
#: presented by the server, and will ignore hostname mismatches and/or
#: expired certificates, which will make your application vulnerable to
#: man-in-the-middle (MitM) attacks.
#: Only set this to `False` for testing.
self.verify = True
#: SSL client certificate default, if String, path to ssl client
#: cert file (.pem). If Tuple, ('cert', 'key') pair.
self.cert = None
#: Maximum number of redirects allowed. If the request exceeds this
#: limit, a :class:`TooManyRedirects` exception is raised.
#: This defaults to requests.models.DEFAULT_REDIRECT_LIMIT, which is
#: 30.
self.max_redirects = DEFAULT_REDIRECT_LIMIT
#: Trust environment settings for proxy configuration, default
#: authentication and similar.
self.trust_env = True
#: A CookieJar containing all currently outstanding cookies set on this
#: session. By default it is a
#: :class:`RequestsCookieJar <requests.cookies.RequestsCookieJar>`, but
#: may be any other ``cookielib.CookieJar`` compatible object.
self.cookies = cookiejar_from_dict({})
# Default connection adapters.
self.adapters = OrderedDict()
self.mount("https://", HTTPAdapter())
self.mount("http://", HTTPAdapter())
def __enter__(self):
return self
def __exit__(self, *args):
self.close()
def prepare_request(self, request):
"""Constructs a :class:`PreparedRequest <PreparedRequest>` for
transmission and returns it. The :class:`PreparedRequest` has settings
merged from the :class:`Request <Request>` instance and those of the
:class:`Session`.
:param request: :class:`Request` instance to prepare with this
session's settings.
:rtype: requests.PreparedRequest
"""
cookies = request.cookies or {}
# Bootstrap CookieJar.
if not isinstance(cookies, cookielib.CookieJar):
cookies = cookiejar_from_dict(cookies)
# Merge with session cookies
merged_cookies = merge_cookies(
merge_cookies(RequestsCookieJar(), self.cookies), cookies
)
# Set environment's basic authentication if not explicitly set.
auth = request.auth
if self.trust_env and not auth and not self.auth:
auth = get_netrc_auth(request.url)
p = PreparedRequest()
p.prepare(
method=request.method.upper(),
url=request.url,
files=request.files,
data=request.data,
json=request.json,
headers=merge_setting(
request.headers, self.headers, dict_class=CaseInsensitiveDict
),
params=merge_setting(request.params, self.params),
auth=merge_setting(auth, self.auth),
cookies=merged_cookies,
hooks=merge_hooks(request.hooks, self.hooks),
)
return p
def request(
self,
method,
url,
params=None,
data=None,
headers=None,
cookies=None,
files=None,
auth=None,
timeout=None,
allow_redirects=True,
proxies=None,
hooks=None,
stream=None,
verify=None,
cert=None,
json=None,
):
"""Constructs a :class:`Request <Request>`, prepares it and sends it.
Returns :class:`Response <Response>` object.
:param method: method for the new :class:`Request` object.
:param url: URL for the new :class:`Request` object.
:param params: (optional) Dictionary or bytes to be sent in the query
string for the :class:`Request`.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) json to send in the body of the
:class:`Request`.
:param headers: (optional) Dictionary of HTTP Headers to send with the
:class:`Request`.
:param cookies: (optional) Dict or CookieJar object to send with the
:class:`Request`.
:param files: (optional) Dictionary of ``'filename': file-like-objects``
for multipart encoding upload.
:param auth: (optional) Auth tuple or callable to enable
Basic/Digest/Custom HTTP Auth.
:param timeout: (optional) How long to wait for the server to send
data before giving up, as a float, or a :ref:`(connect timeout,
read timeout) <timeouts>` tuple.
:type timeout: float or tuple
:param allow_redirects: (optional) Set to True by default.
:type allow_redirects: bool
:param proxies: (optional) Dictionary mapping protocol or protocol and
hostname to the URL of the proxy.
:param stream: (optional) whether to immediately download the response
content. Defaults to ``False``.
:param verify: (optional) Either a boolean, in which case it controls whether we verify
the server's TLS certificate, or a string, in which case it must be a path
to a CA bundle to use. Defaults to ``True``. When set to
``False``, requests will accept any TLS certificate presented by
the server, and will ignore hostname mismatches and/or expired
certificates, which will make your application vulnerable to
man-in-the-middle (MitM) attacks. Setting verify to ``False``
may be useful during local development or testing.
:param cert: (optional) if String, path to ssl client cert file (.pem).
If Tuple, ('cert', 'key') pair.
:rtype: requests.Response
"""
# Create the Request.
req = Request(
method=method.upper(),
url=url,
headers=headers,
files=files,
data=data or {},
json=json,
params=params or {},
auth=auth,
cookies=cookies,
hooks=hooks,
)
prep = self.prepare_request(req)
proxies = proxies or {}
settings = self.merge_environment_settings(
prep.url, proxies, stream, verify, cert
)
# Send the request.
send_kwargs = {
"timeout": timeout,
"allow_redirects": allow_redirects,
}
send_kwargs.update(settings)
resp = self.send(prep, **send_kwargs)
return resp
def get(self, url, **kwargs):
r"""Sends a GET request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
kwargs.setdefault("allow_redirects", True)
return self.request("GET", url, **kwargs)
def options(self, url, **kwargs):
r"""Sends a OPTIONS request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
kwargs.setdefault("allow_redirects", True)
return self.request("OPTIONS", url, **kwargs)
def head(self, url, **kwargs):
r"""Sends a HEAD request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
kwargs.setdefault("allow_redirects", False)
return self.request("HEAD", url, **kwargs)
def post(self, url, data=None, json=None, **kwargs):
r"""Sends a POST request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param json: (optional) json to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
return self.request("POST", url, data=data, json=json, **kwargs)
def put(self, url, data=None, **kwargs):
r"""Sends a PUT request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
return self.request("PUT", url, data=data, **kwargs)
def patch(self, url, data=None, **kwargs):
r"""Sends a PATCH request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param data: (optional) Dictionary, list of tuples, bytes, or file-like
object to send in the body of the :class:`Request`.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
return self.request("PATCH", url, data=data, **kwargs)
def delete(self, url, **kwargs):
r"""Sends a DELETE request. Returns :class:`Response` object.
:param url: URL for the new :class:`Request` object.
:param \*\*kwargs: Optional arguments that ``request`` takes.
:rtype: requests.Response
"""
return self.request("DELETE", url, **kwargs)
def send(self, request, **kwargs):
"""Send a given PreparedRequest.
:rtype: requests.Response
"""
# Set defaults that the hooks can utilize to ensure they always have
# the correct parameters to reproduce the previous request.
kwargs.setdefault("stream", self.stream)
kwargs.setdefault("verify", self.verify)
kwargs.setdefault("cert", self.cert)
if "proxies" not in kwargs:
kwargs["proxies"] = resolve_proxies(request, self.proxies, self.trust_env)
# It's possible that users might accidentally send a Request object.
# Guard against that specific failure case.
if isinstance(request, Request):
raise ValueError("You can only send PreparedRequests.")
# Set up variables needed for resolve_redirects and dispatching of hooks
allow_redirects = kwargs.pop("allow_redirects", True)
stream = kwargs.get("stream")
hooks = request.hooks
# Get the appropriate adapter to use
adapter = self.get_adapter(url=request.url)
# Start time (approximately) of the request
start = preferred_clock()
# Send the request
r = adapter.send(request, **kwargs)
# Total elapsed time of the request (approximately)
elapsed = preferred_clock() - start
r.elapsed = timedelta(seconds=elapsed)
# Response manipulation hooks
r = dispatch_hook("response", hooks, r, **kwargs)
# Persist cookies
if r.history:
# If the hooks create history then we want those cookies too
for resp in r.history:
extract_cookies_to_jar(self.cookies, resp.request, resp.raw)
extract_cookies_to_jar(self.cookies, request, r.raw)
# Resolve redirects if allowed.
if allow_redirects:
# Redirect resolving generator.
gen = self.resolve_redirects(r, request, **kwargs)
history = [resp for resp in gen]
else:
history = []
# Shuffle things around if there's history.
if history:
# Insert the first (original) request at the start
history.insert(0, r)
# Get the last request made
r = history.pop()
r.history = history
# If redirects aren't being followed, store the response on the Request for Response.next().
if not allow_redirects:
try:
r._next = next(
self.resolve_redirects(r, request, yield_requests=True, **kwargs)
)
except StopIteration:
pass
if not stream:
r.content
return r
def merge_environment_settings(self, url, proxies, stream, verify, cert):
"""
Check the environment and merge it with some settings.
:rtype: dict
"""
# Gather clues from the surrounding environment.
if self.trust_env:
# Set environment's proxies.
no_proxy = proxies.get("no_proxy") if proxies is not None else None
env_proxies = get_environ_proxies(url, no_proxy=no_proxy)
for (k, v) in env_proxies.items():
proxies.setdefault(k, v)
# Look for requests environment configuration
# and be compatible with cURL.
if verify is True or verify is None:
verify = (
os.environ.get("REQUESTS_CA_BUNDLE")
or os.environ.get("CURL_CA_BUNDLE")
or verify
)
# Merge all the kwargs.
proxies = merge_setting(proxies, self.proxies)
stream = merge_setting(stream, self.stream)
verify = merge_setting(verify, self.verify)
cert = merge_setting(cert, self.cert)
return {"proxies": proxies, "stream": stream, "verify": verify, "cert": cert}
def get_adapter(self, url):
"""
Returns the appropriate connection adapter for the given URL.
:rtype: requests.adapters.BaseAdapter
"""
for (prefix, adapter) in self.adapters.items():
if url.lower().startswith(prefix.lower()):
return adapter
# Nothing matches :-/
raise InvalidSchema(f"No connection adapters were found for {url!r}")
def close(self):
"""Closes all adapters and as such the session"""
for v in self.adapters.values():
v.close()
def mount(self, prefix, adapter):
"""Registers a connection adapter to a prefix.
Adapters are sorted in descending order by prefix length.
"""
self.adapters[prefix] = adapter
keys_to_move = [k for k in self.adapters if len(k) < len(prefix)]
for key in keys_to_move:
self.adapters[key] = self.adapters.pop(key)
def __getstate__(self):
state = {attr: getattr(self, attr, None) for attr in self.__attrs__}
return state
def __setstate__(self, state):
for attr, value in state.items():
setattr(self, attr, value)
def session():
"""
Returns a :class:`Session` for context-management.
.. deprecated:: 1.0.0
This method has been deprecated since version 1.0.0 and is only kept for
backwards compatibility. New code should use :class:`~requests.sessions.Session`
to create a session. This may be removed at a future date.
:rtype: Session
"""
return Session()
-128
View File
@@ -1,128 +0,0 @@
r"""
The ``codes`` object defines a mapping from common names for HTTP statuses
to their numerical codes, accessible either as attributes or as dictionary
items.
Example::
>>> import requests
>>> requests.codes['temporary_redirect']
307
>>> requests.codes.teapot
418
>>> requests.codes['\o/']
200
Some codes have multiple names, and both upper- and lower-case versions of
the names are allowed. For example, ``codes.ok``, ``codes.OK``, and
``codes.okay`` all correspond to the HTTP status code 200.
"""
from .structures import LookupDict
_codes = {
# Informational.
100: ("continue",),
101: ("switching_protocols",),
102: ("processing",),
103: ("checkpoint",),
122: ("uri_too_long", "request_uri_too_long"),
200: ("ok", "okay", "all_ok", "all_okay", "all_good", "\\o/", ""),
201: ("created",),
202: ("accepted",),
203: ("non_authoritative_info", "non_authoritative_information"),
204: ("no_content",),
205: ("reset_content", "reset"),
206: ("partial_content", "partial"),
207: ("multi_status", "multiple_status", "multi_stati", "multiple_stati"),
208: ("already_reported",),
226: ("im_used",),
# Redirection.
300: ("multiple_choices",),
301: ("moved_permanently", "moved", "\\o-"),
302: ("found",),
303: ("see_other", "other"),
304: ("not_modified",),
305: ("use_proxy",),
306: ("switch_proxy",),
307: ("temporary_redirect", "temporary_moved", "temporary"),
308: (
"permanent_redirect",
"resume_incomplete",
"resume",
), # "resume" and "resume_incomplete" to be removed in 3.0
# Client Error.
400: ("bad_request", "bad"),
401: ("unauthorized",),
402: ("payment_required", "payment"),
403: ("forbidden",),
404: ("not_found", "-o-"),
405: ("method_not_allowed", "not_allowed"),
406: ("not_acceptable",),
407: ("proxy_authentication_required", "proxy_auth", "proxy_authentication"),
408: ("request_timeout", "timeout"),
409: ("conflict",),
410: ("gone",),
411: ("length_required",),
412: ("precondition_failed", "precondition"),
413: ("request_entity_too_large",),
414: ("request_uri_too_large",),
415: ("unsupported_media_type", "unsupported_media", "media_type"),
416: (
"requested_range_not_satisfiable",
"requested_range",
"range_not_satisfiable",
),
417: ("expectation_failed",),
418: ("im_a_teapot", "teapot", "i_am_a_teapot"),
421: ("misdirected_request",),
422: ("unprocessable_entity", "unprocessable"),
423: ("locked",),
424: ("failed_dependency", "dependency"),
425: ("unordered_collection", "unordered"),
426: ("upgrade_required", "upgrade"),
428: ("precondition_required", "precondition"),
429: ("too_many_requests", "too_many"),
431: ("header_fields_too_large", "fields_too_large"),
444: ("no_response", "none"),
449: ("retry_with", "retry"),
450: ("blocked_by_windows_parental_controls", "parental_controls"),
451: ("unavailable_for_legal_reasons", "legal_reasons"),
499: ("client_closed_request",),
# Server Error.
500: ("internal_server_error", "server_error", "/o\\", ""),
501: ("not_implemented",),
502: ("bad_gateway",),
503: ("service_unavailable", "unavailable"),
504: ("gateway_timeout",),
505: ("http_version_not_supported", "http_version"),
506: ("variant_also_negotiates",),
507: ("insufficient_storage",),
509: ("bandwidth_limit_exceeded", "bandwidth"),
510: ("not_extended",),
511: ("network_authentication_required", "network_auth", "network_authentication"),
}
codes = LookupDict(name="status_codes")
def _init():
for code, titles in _codes.items():
for title in titles:
setattr(codes, title, code)
if not title.startswith(("\\", "/")):
setattr(codes, title.upper(), code)
def doc(code):
names = ", ".join(f"``{n}``" for n in _codes[code])
return "* %d: %s" % (code, names)
global __doc__
__doc__ = (
__doc__ + "\n" + "\n".join(doc(code) for code in sorted(_codes))
if __doc__ is not None
else None
)
_init()
-99
View File
@@ -1,99 +0,0 @@
"""
requests.structures
~~~~~~~~~~~~~~~~~~~
Data structures that power Requests.
"""
from collections import OrderedDict
from .compat import Mapping, MutableMapping
class CaseInsensitiveDict(MutableMapping):
"""A case-insensitive ``dict``-like object.
Implements all methods and operations of
``MutableMapping`` as well as dict's ``copy``. Also
provides ``lower_items``.
All keys are expected to be strings. The structure remembers the
case of the last key to be set, and ``iter(instance)``,
``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()``
will contain case-sensitive keys. However, querying and contains
testing is case insensitive::
cid = CaseInsensitiveDict()
cid['Accept'] = 'application/json'
cid['aCCEPT'] == 'application/json' # True
list(cid) == ['Accept'] # True
For example, ``headers['content-encoding']`` will return the
value of a ``'Content-Encoding'`` response header, regardless
of how the header name was originally stored.
If the constructor, ``.update``, or equality comparison
operations are given keys that have equal ``.lower()``s, the
behavior is undefined.
"""
def __init__(self, data=None, **kwargs):
self._store = OrderedDict()
if data is None:
data = {}
self.update(data, **kwargs)
def __setitem__(self, key, value):
# Use the lowercased key for lookups, but store the actual
# key alongside the value.
self._store[key.lower()] = (key, value)
def __getitem__(self, key):
return self._store[key.lower()][1]
def __delitem__(self, key):
del self._store[key.lower()]
def __iter__(self):
return (casedkey for casedkey, mappedvalue in self._store.values())
def __len__(self):
return len(self._store)
def lower_items(self):
"""Like iteritems(), but with all lowercase keys."""
return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items())
def __eq__(self, other):
if isinstance(other, Mapping):
other = CaseInsensitiveDict(other)
else:
return NotImplemented
# Compare insensitively
return dict(self.lower_items()) == dict(other.lower_items())
# Copy is required
def copy(self):
return CaseInsensitiveDict(self._store.values())
def __repr__(self):
return str(dict(self.items()))
class LookupDict(dict):
"""Dictionary lookup object."""
def __init__(self, name=None):
self.name = name
super().__init__()
def __repr__(self):
return f"<lookup '{self.name}'>"
def __getitem__(self, key):
# We allow fall-through here, so values default to None
return self.__dict__.get(key, None)
def get(self, key, default=None):
return self.__dict__.get(key, default)
File diff suppressed because it is too large Load Diff
-25
View File
@@ -1,25 +0,0 @@
"""
The MIT License (MIT)
Copyright (c) 2015-present Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from .steamgrid import *
from .enums import *
from .asset import *
from .game import *
from .author import *
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
-220
View File
@@ -1,220 +0,0 @@
"""
The MIT License (MIT)
Copyright (c) 2015-present Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from typing import Tuple, Iterator, Any
from .http import HTTPClient
from .author import Author
from .enums import AssetType
__all__ = (
'Grid',
'Hero',
'Logo',
'Icon',
)
class Asset:
"""Base class for all assets.
Depending on the way this object was created, some of the attributes can
have a value of ``None``.
.. container:: operations
.. describe:: x == y
Checks if two asset are the same.
.. describe:: x != y
Checks if two asset are not the same.
.. describe:: iter(x)
Returns an iterator of ``(field, value)`` pairs. This allows this class
to be used as an iterable in list/dict/etc constructions.
.. describe:: str(x)
Returns a string representation of the asset.
Attributes
-----------
id: :class:`str`
The asset's ID.
author: :class:`Author`
The author of the asset.
score: :class:`int`
The asset's score.
width: :class:`int`
The asset's width.
height: :class:`int`
The asset's width.
style: :class:`str`
The style of the asset.
notes: Optional[:class:`str`]
Notes about the asset.
mime: :class:`str`
The MIME type of the asset.
language: :class:`str`
The language of the asset.
url: :class:`str`
The URL of the asset.
thumbnail: :class:`str`
The URL of the asset's thumbnail.
type: :class:`AssetType`
The type of the asset.
"""
__slots__: Tuple[str, ...] = (
'_payload',
'_http',
'id',
'score',
'width',
'height',
'style',
'_nsfw',
'_humor',
'notes',
'mime',
'language',
'url',
'thumbnail',
'_lock',
'_epilepsy',
'type',
'author'
)
def __init__(self, payload: dict, type: AssetType, http: HTTPClient) -> None:
self._payload = payload
self._http = http
self._from_data(payload)
self.type = type
def _from_data(self, asset: dict):
self.id = asset.get('id')
self.author: Author = Author(asset['author'])
self.score = asset.get('score')
self.width = asset.get('width')
self.height = asset.get('height')
self.style = asset.get('style')
self._nsfw = asset.get('nsfw')
self._humor = asset.get('humor')
self.notes = asset.get('notes', None)
self.mime = asset.get('mime')
self.language = asset.get('language')
self.url = asset.get('url')
self.thumbnail = asset.get('thumb')
self._lock = asset.get('lock')
self._epilepsy = asset.get('epilepsy')
self.upvotes = asset.get('upvotes')
self.downvotes = asset.get('downvotes')
def __str__(self) -> str:
return self.url
def __eq__(self, other) -> bool:
return self.id == other.id
def __ne__(self, other) -> bool:
return self.id != other.id
def __iter__(self) -> Iterator[Tuple[str, Any]]:
for attr in self.__slots__:
if attr[0] != '_':
value = getattr(self, attr, None)
if value is not None:
yield (attr, value)
def to_json(self) -> dict:
return self._payload
def is_lock(self) -> bool:
""":class:`bool`: Returns whether the asset is locked."""
return self.lock
def is_humor(self) -> bool:
""":class:`bool`: Returns whether the asset is a humor asset."""
return self.humor
def is_nsfw(self) -> bool:
""":class:`bool`: Returns whether the asset is NSFW."""
return self.nsfw
def is_epilepsy(self) -> bool:
""":class:`bool`: Returns whether the asset is epilepsy-inducing."""
return self.is_epilepsy
class Grid(Asset):
def __init__(self, payload: dict, http: HTTPClient) -> None:
super().__init__(
payload,
AssetType.Grid,
http
)
def __repr__(self) -> str:
return f'<Grid id={self.id} height={self.height} width={self.width} author={self.author.name}>'
def delete(self) -> None:
"""Delete the grid."""
self._http.delete_grid([self.id])
class Hero(Asset):
def __init__(self, payload: dict, http: HTTPClient) -> None:
super().__init__(
payload,
AssetType.Hero,
http
)
def __repr__(self) -> str:
return f'<Hero id={self.id} height={self.height} width={self.width} author={self.author.name}>'
def delete(self) -> None:
"""Delete the hero."""
self._http.delete_hero([self.id])
class Logo(Asset):
def __init__(self, payload: dict, http: HTTPClient) -> None:
super().__init__(
payload,
AssetType.Logo,
http
)
def __repr__(self) -> str:
return f'<Logo id={self.id} height={self.height} width={self.width} author={self.author.name}>'
def delete(self) -> None:
"""Delete the logo."""
self._http.delete_logo([self.id])
class Icon(Asset):
def __init__(self, payload: dict, http: HTTPClient) -> None:
super().__init__(
payload,
AssetType.Icon,
http
)
def __repr__(self) -> str:
return f'<Icon id={self.id} height={self.height} width={self.width} author={self.author.name}>'
def delete(self) -> None:
"""Delete the icon."""
self._http.delete_icon([self.id])
-93
View File
@@ -1,93 +0,0 @@
"""
The MIT License (MIT)
Copyright (c) 2015-present Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from typing import Iterator, Tuple, Any
__all__ = (
'Author',
)
class Author:
"""Represents a custom author.
Depending on the way this object was created, some of the attributes can
have a value of ``None``.
.. container:: operations
.. describe:: x == y
Checks if two author are the same.
.. describe:: x != y
Checks if two author are not the same.
.. describe:: iter(x)
Returns an iterator of ``(field, value)`` pairs. This allows this class
to be used as an iterable in list/dict/etc constructions.
.. describe:: str(x)
Returns a string representation of the author.
Attributes
-----------
name: :class:`str`
The name of the author.
steam64: :class:`str`
The author's steam64 ID.
avatar: :class:`str`
The author's avatar URL.
"""
__slots__: Tuple[str, ...] = (
'_payload',
'name',
'steam64',
'avatar',
)
def __init__(self, payload: dict) -> None:
self._payload = payload
self._from_data(payload)
def _from_data(self, author: dict):
self.name = author.get('name')
self.steam64 = author.get('steam64')
self.avatar = author.get('avatar')
def __str__(self) -> str:
return self.name
def __repr__(self) -> str:
return f'<Author name={self.name} steam64={self.steam64}>'
def __eq__(self, other) -> bool:
return self.name == other.name
def __ne__(self, other) -> bool:
return self.name != other.name
def __iter__(self) -> Iterator[Tuple[str, Any]]:
for attr in self.__slots__:
if attr[0] != '_':
value = getattr(self, attr, None)
if value is not None:
yield (attr, value)
def to_json(self) -> dict:
""":class:`dict`: Returns a JSON-compatible representation of the author."""
return self._payload
-75
View File
@@ -1,75 +0,0 @@
"""
The MIT License (MIT)
Copyright (c) 2015-present Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from enum import Enum
__all__ = [
'PlatformType',
'StyleType',
'MimeType',
'ImageType',
]
class PlatformType(Enum):
Steam = 'steam'
Origin = 'origin'
Egs = 'egs'
Bnet = 'bnet'
Uplay = 'uplay'
Flashpoint = 'flashpoint'
Eshop = 'eshop'
def __str__(self) -> str:
return self.name
class StyleType(Enum):
Alternate = 'alternate'
Blurred = 'blurred'
White_logo = 'white_logo'
Material = 'material'
No_logo = 'no_logo'
def __str__(self) -> str:
return self.name
class MimeType(Enum):
PNG = 'image/png'
JPEG = 'image/jpeg'
WEBP = 'image/webp'
def __str__(self) -> str:
return self.name
class ImageType(Enum):
Static = 'static '
Animated = 'animated'
def __str__(self) -> str:
return self.name
class AssetType(Enum):
Grid = 'grids'
Hero = 'heroes'
Logo = 'logoes'
Icon = 'icons'
def __str__(self) -> str:
return self.name
-103
View File
@@ -1,103 +0,0 @@
"""
The MIT License (MIT)
Copyright (c) 2015-present Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from datetime import datetime
from typing import Iterator, Tuple, Any
__all__ = (
'Game',
)
class Game:
"""Represents a custom game.
Depending on the way this object was created, some of the attributes can
have a value of ``None``.
.. container:: operations
.. describe:: x == y
Checks if two game are the same.
.. describe:: x != y
Checks if two game are not the same.
.. describe:: iter(x)
Returns an iterator of ``(field, value)`` pairs. This allows this class
to be used as an iterable in list/dict/etc constructions.
.. describe:: str(x)
Returns a string representation of the game.
Attributes
-----------
name: :class:`str`
The name of the game.
id: :class:`int`
The game's ID.
types: List[:class:`str`]
List of game types.
verified: :class:`bool`
Whether an game is verified or not.
release_date: Optional[:class:`datetime`]
The release date of the game.
"""
__slots__ = (
'_payload',
'id',
'name',
'types',
'verified',
'release_date',
'_release_date'
)
def __init__(self, payload: dict) -> None:
self._payload = payload
self._from_data(payload)
def _from_data(self, game: dict):
self.name = game.get('name')
self.id = game.get('id')
self.types = game.get('types')
self.verified = game.get('verified')
self._release_date = game.get('release_date', None)
self.release_date = datetime.fromtimestamp(game['release_date']) if self._release_date else None
def __str__(self) -> str:
return self.name
def __repr__(self) -> str:
return f'<Game id={self.id} name={self.name}>'
def __eq__(self, other) -> bool:
return self.id == other.id
def __ne__(self, other) -> bool:
return self.id != other.id
def __iter__(self) -> Iterator[Tuple[str, Any]]:
for attr in self.__slots__:
if attr[0] != '_':
value = getattr(self, attr, None)
if value is not None:
yield (attr, value)
def to_json(self) -> dict:
""":class:`dict`: Returns a JSON-compatible representation of the author."""
return self._payload
-158
View File
@@ -1,158 +0,0 @@
"""
The MIT License (MIT)
Copyright (c) 2015-present Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
import requests
from typing import List
class HTTPException(Exception):
"""Exception raised when the HTTP request fails."""
pass
class HTTPClient:
BASE_URL = 'https://www.steamgriddb.com/api/v2'
def __init__(self, auth_key: str):
self.session = requests.Session()
self.auth_key = auth_key
self.session.headers.update({'Authorization': 'Bearer ' + self.auth_key})
def get(self, endpoint: str, queries: dict = None) -> dict:
if queries:
responce = self.session.get(endpoint, params=queries)
else:
responce = self.session.get(endpoint)
try:
payload = responce.json()
except requests.exceptions.JSONDecodeError:
raise Exception('Responce JSON Decode Error')
if not payload['success']:
error_context = payload['errors'][0]
raise HTTPException(f'API Error: ({responce.status_code}) {error_context}')
return payload['data'] if payload else None
def post(self, endpoint: str, body: dict = None) -> dict:
responce = self.session.post(endpoint, data=body)
try:
payload = responce.json()
except requests.exceptions.JSONDecodeError:
raise Exception('Responce JSON Decode Error')
is_success = payload.get('success', None)
if not is_success:
error_context = payload['errors'][0] if payload else ''
raise HTTPException(f'API Error: ({responce.status_code}) {error_context}')
return payload['data'] if payload else None
def delete(self, endpoint: str) -> dict:
responce = self.session.delete(endpoint)
try:
payload = responce.json()
except requests.exceptions.JSONDecodeError:
raise Exception('Responce JSON Decode Error')
if not payload['success']:
error_context = payload['errors'][0]
raise HTTPException(f'API Error: ({responce.status_code}) {error_context}')
return payload['data'] if payload else None
def get_game(self, game_id: int, request_type: str) -> dict:
if request_type == 'steam':
url = self.BASE_URL + '/games/steam/' + str(game_id)
elif request_type == 'game':
url = self.BASE_URL + '/games/id/' + str(game_id)
return self.get(url)
def get_grid(
self,
game_ids: List[int],
request_type: str,
platform: str = None,
queries: dict = None
) -> List[dict]:
if request_type == 'game':
url = self.BASE_URL + '/grids/game/' + str(game_ids[0])
elif request_type == 'platform':
url = self.BASE_URL + '/grids/' + platform + '/' + ','.join(str(i) for i in game_ids)
return self.get(url, queries)
def delete_grid(self, grid_ids: List[int]):
url = self.BASE_URL + '/grids/' + ','.join(str(i) for i in grid_ids)
self.delete(url)
def get_hero(
self,
game_ids: List[int],
request_type: str,
platform: str = None,
queries: dict = None
) -> List[dict]:
if request_type == 'game':
url = self.BASE_URL + '/heroes/game/' + str(game_ids[0])
elif request_type == 'platform':
url = self.BASE_URL + '/heroes/' + platform + '/' + ','.join(str(i) for i in game_ids)
return self.get(url, queries)
def delete_hero(self, hero_ids: List[int]):
url = self.BASE_URL + '/heroes/' + ','.join(str(i) for i in hero_ids)
self.delete(url)
def get_logo(
self,
game_ids: List[int],
request_type: str,
platform: str = None,
queries: dict = None
) -> List[dict]:
if request_type == 'game':
url = self.BASE_URL + '/logos/game/' + str(game_ids[0])
elif request_type == 'platform':
url = self.BASE_URL + '/logos/' + platform + '/' + ','.join(str(i) for i in game_ids)
return self.get(url, queries)
def delete_logo(self, logo_ids: List[int]):
url = self.BASE_URL + '/logos/' + ','.join(str(i) for i in logo_ids)
self.delete(url)
def get_icon(self, game_ids: List[int], request_type: str, platform: str = None, queries: dict = None) -> List[dict]:
if request_type == 'game':
url = self.BASE_URL + '/icons/game/' + str(game_ids[0])
elif request_type == 'platform':
url = self.BASE_URL + '/icons/' + platform + '/' + ','.join(str(i) for i in game_ids)
return self.get(url, queries)
def delete_icon(self, logo_ids: List[int]):
url = self.BASE_URL + '/icons/' + ','.join(str(i) for i in logo_ids)
self.delete(url)
def search_games(self, term: str) -> List[dict]:
url = self.BASE_URL + '/search/autocomplete/' + term
return self.get(url)
-802
View File
@@ -1,802 +0,0 @@
"""
The MIT License (MIT)
Copyright (c) 2015-present Rapptz
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
"""
from typing import List, Optional
from .http import HTTPClient
from .game import Game
from .enums import (
StyleType,
MimeType,
ImageType,
PlatformType
)
from .asset import *
__all__ = (
'SteamGridDB',
)
class SteamGridDB:
"""Represents a custom author.
Attributes
-----------
auth_key: :class:`str`
The auth key of the steamgriddb for authorization.
"""
__slots__ = ('_http')
def __init__(self, auth_key: str) -> None:
self._http = HTTPClient(auth_key)
def auth_key(self) -> str:
""":class:`str`: Returns the auth key of the steamgriddb.
Returns
--------
:class:`str`
The auth key of the steamgriddb.
"""
return self._http.auth_key
def get_game_by_gameid(
self,
game_id: int,
) -> Optional[Game]:
""":class:`Game`: Returns a game by game id.
Parameters
-----------
game_id: :class:`int`
The game id of the game.
Raises
--------
TypeError
If the game_id is not an integer.
HTTPException
If the game_id is not found.
Returns
--------
:class:`Game`
The game that was fetched.
"""
if not isinstance(game_id, int):
raise TypeError('\'game_id\' must be an integer.')
payload = self._http.get_game(game_id, 'game')
return Game(payload) if payload != [] else None
def get_game_by_steam_appid(
self,
app_id: int,
) -> Optional[Game]:
""":class:`Game`: Returns a game by steam app id.
Parameters
-----------
app_id: :class:`int`
The steam app id of the game.
Raises
--------
TypeError
If the app_id is not an integer.
HTTPException
If the app_id is not found.
Returns
--------
:class:`Game`
The game that was fetched.
"""
if not isinstance(app_id, int):
raise TypeError('\'app_id\' must be an integer.')
payload = self._http.get_game(app_id, 'steam')
return Game(payload) if payload != [] else None
def get_grids_by_gameid(
self,
game_ids: List[int],
styles: List[StyleType] = [],
mimes: List[MimeType] = [],
types: List[ImageType] = [],
is_nsfw: bool = False,
is_humor: bool = False,
) -> Optional[List[Grid]]:
"""Optional[List[:class:`Grid`]] Returns a list of grids by game id.
Parameters
-----------
game_ids: List[:class:`int`]
The game ids of the games.
styles: List[:class:`StyleType`]
The styles of the grids. Defaults to all styles.
mimes: List[:class:`MimeType`]
The mimes of the grids. Defaults to all mimes.
types: List[:class:`ImageType`]
The types of the grids. Defaults to all types.
is_nsfw: :class:`bool`
Whether or not the grids are NSFW. Defaults to False.
is_humor: :class:`bool`
Whether or not the grids are humor. Defaults to False.
Raises
--------
TypeError
If one of the parameters is not of the correct type.
HTTPException
If the game_id is not found.
Returns
--------
Optional[List[:class:`Grid`]]
The grids that were fetched.
"""
if not isinstance(game_ids, List):
raise TypeError('\'game_ids\' must be a list of integers.')
if not isinstance(styles, List):
raise TypeError('\'styles\' must be a list of StyleType.')
if not isinstance(mimes, List):
raise TypeError('\'mimes\' must be a list of MimeType.')
if not isinstance(types, List):
raise TypeError('\'types\' must be a list of ImageType.')
if not isinstance(is_nsfw, bool):
raise TypeError('\'is_nsfw\' must be a boolean.')
if not isinstance(is_humor, bool):
raise TypeError('\'is_humor\' must be a boolean.')
queries = {
'styles': ','.join(i.value for i in styles),
'mimes': ','.join(i.value for i in mimes),
'types': ','.join(i.value for i in types),
'nsfw': str(is_nsfw).lower(),
'humor': str(is_humor).lower(),
}
payloads = self._http.get_grid(game_ids, 'game', queries=queries)
if payloads != []:
return [Grid(payload, self._http) for payload in payloads]
return None
def get_grids_by_platform(
self,
game_ids: List[int],
platform: PlatformType,
styles: List[StyleType] = [],
mimes: List[MimeType] = [],
types: List[ImageType] = [],
is_nsfw: bool = False,
is_humor: bool = False,
) -> Optional[List[Grid]]:
"""Optional[List[:class:`Grid`]] Returns a list of grids by platform.
Parameters
-----------
game_ids: List[:class:`int`]
The game ids of the games.
platform: :class:`PlatformType`
The platform type of the grids.
styles: List[:class:`StyleType`]
The styles of the grids. Defaults to all styles.
mimes: List[:class:`MimeType`]
The mimes of the grids. Defaults to all mimes.
types: List[:class:`ImageType`]
The types of the grids. Defaults to all types.
is_nsfw: :class:`bool`
Whether or not the grids are NSFW. Defaults to False.
is_humor: :class:`bool`
Whether or not the grids are humor. Defaults to False.
Raises
--------
TypeError
If one of the parameters is not of the correct type.
HTTPException
If there is an error with the request.
Returns
--------
Optional[List[:class:`Grid`]]
The grids that were fetched.
"""
if not isinstance(game_ids, List):
raise TypeError('\'game_ids\' must be a list of integers.')
if not isinstance(platform, PlatformType):
raise TypeError('\'platform\' must be a PlatformType.')
if not isinstance(styles, List):
raise TypeError('\'styles\' must be a list of StyleType.')
if not isinstance(mimes, List):
raise TypeError('\'mimes\' must be a list of MimeType.')
if not isinstance(types, List):
raise TypeError('\'types\' must be a list of ImageType.')
if not isinstance(is_nsfw, bool):
raise TypeError('\'is_nsfw\' must be a boolean.')
if not isinstance(is_humor, bool):
raise TypeError('\'is_humor\' must be a boolean.')
queries = {
'styles': ','.join(str(i) for i in styles),
'mimes': ','.join(str(i) for i in mimes),
'types': ','.join(str(i) for i in types),
'nsfw': str(is_nsfw).lower(),
'humor': str(is_humor).lower(),
}
payloads = self._http.get_grid(
game_ids,
'platform',
platform=platform.value,
queries=queries
)
if payloads != []:
return [Grid(payload, self._http) for payload in payloads]
return None
def get_heroes_by_gameid(
self,
game_ids: List[int],
styles: List[StyleType] = [],
mimes: List[MimeType] = [],
types: List[ImageType] = [],
is_nsfw: bool = False,
is_humor: bool = False,
) -> Optional[List[Hero]]:
"""Optional[List[:class:`Hero`]] Returns a list of heroes by game id.
Parameters
-----------
game_ids: List[:class:`int`]
The game ids of the games.
styles: List[:class:`StyleType`]
The styles of the heroes. Defaults to all styles.
mimes: List[:class:`MimeType`]
The mimes of the heroes. Defaults to all mimes.
types: List[:class:`ImageType`]
The types of the heroes. Defaults to all types.
is_nsfw: :class:`bool`
Whether or not the heroes are NSFW. Defaults to False.
is_humor: :class:`bool`
Whether or not the heroes are humor. Defaults to False.
Raises
--------
TypeError
If one of the parameters is not of the correct type.
HTTPException
If there is an error with the request.
Returns
--------
Optional[List[:class:`Hero`]]
The heroes that were fetched.
"""
if not isinstance(game_ids, List):
raise TypeError('\'game_ids\' must be a list of integers.')
if not isinstance(styles, List):
raise TypeError('\'styles\' must be a list of StyleType.')
if not isinstance(mimes, List):
raise TypeError('\'mimes\' must be a list of MimeType.')
if not isinstance(types, List):
raise TypeError('\'types\' must be a list of ImageType.')
if not isinstance(is_nsfw, bool):
raise TypeError('\'is_nsfw\' must be a boolean.')
if not isinstance(is_humor, bool):
raise TypeError('\'is_humor\' must be a boolean.')
queries = {
'styles': ','.join(i.value for i in styles),
'mimes': ','.join(i.value for i in mimes),
'types': ','.join(i.value for i in types),
'nsfw': str(is_nsfw).lower(),
'humor': str(is_humor).lower(),
}
payloads = self._http.get_hero(game_ids, 'game', queries=queries)
if payloads != []:
return [Hero(payload, self._http) for payload in payloads]
return None
def get_heroes_by_platform(
self,
game_ids: List[int],
platform: PlatformType,
styles: List[StyleType] = [],
mimes: List[MimeType] = [],
types: List[ImageType] = [],
is_nsfw: bool = False,
is_humor: bool = False,
) -> Optional[List[Hero]]:
"""Optional[List[:class:`Hero`]] Returns a list of heroes by platform.
Parameters
-----------
game_ids: List[:class:`int`]
The game ids of the games.
platform: :class:`PlatformType`
The platform type of the heroes.
styles: List[:class:`StyleType`]
The styles of the heroes. Defaults to all styles.
mimes: List[:class:`MimeType`]
The mimes of the heroes. Defaults to all mimes.
types: List[:class:`ImageType`]
The types of the heroes. Defaults to all types.
is_nsfw: :class:`bool`
Whether or not the heroes are NSFW. Defaults to False.
is_humor: :class:`bool`
Whether or not the heroes are humor. Defaults to False.
Raises
--------
TypeError
If one of the parameters is not of the correct type.
HTTPException
If there is an error with the request.
Returns
--------
Optional[List[:class:`Hero`]]
The heroes that were fetched.
"""
if not isinstance(game_ids, List):
raise TypeError('\'game_ids\' must be a list of integers.')
if not isinstance(platform, PlatformType):
raise TypeError('\'platform\' must be a PlatformType.')
if not isinstance(styles, List):
raise TypeError('\'styles\' must be a list of StyleType.')
if not isinstance(mimes, List):
raise TypeError('\'mimes\' must be a list of MimeType.')
if not isinstance(types, List):
raise TypeError('\'types\' must be a list of ImageType.')
if not isinstance(is_nsfw, bool):
raise TypeError('\'is_nsfw\' must be a boolean.')
if not isinstance(is_humor, bool):
raise TypeError('\'is_humor\' must be a boolean.')
queries = {
'styles': ','.join(str(i) for i in styles),
'mimes': ','.join(str(i) for i in mimes),
'types': ','.join(str(i) for i in types),
'nsfw': str(is_nsfw).lower(),
'humor': str(is_humor).lower(),
}
payloads = self._http.get_hero(
game_ids,
'platform',
platform=platform.value,
queries=queries
)
if payloads != []:
return [Grid(payload, self._http) for payload in payloads]
return None
def get_logos_by_gameid(
self,
game_ids: List[int],
styles: List[StyleType] = [],
mimes: List[MimeType] = [],
types: List[ImageType] = [],
is_nsfw: bool = False,
is_humor: bool = False,
) -> Optional[List[Logo]]:
"""Optional[List[:class:`Logo`]] Returns a list of logos by game id.
Parameters
-----------
game_ids: List[:class:`int`]
The game ids of the games.
styles: List[:class:`StyleType`]
The styles of the logos. Defaults to all styles.
mimes: List[:class:`MimeType`]
The mimes of the logos. Defaults to all mimes.
types: List[:class:`ImageType`]
The types of the logos. Defaults to all types.
is_nsfw: :class:`bool`
Whether or not the logos are NSFW. Defaults to False.
is_humor: :class:`bool`
Whether or not the logos are humor. Defaults to False.
Raises
--------
TypeError
If one of the parameters is not of the correct type.
HTTPException
If there is an error with the request.
Returns
--------
Optional[List[:class:`Logo`]]
The logos that were fetched.
"""
if not isinstance(game_ids, List):
raise TypeError('\'game_ids\' must be a list of integers.')
if not isinstance(styles, List):
raise TypeError('\'styles\' must be a list of StyleType.')
if not isinstance(mimes, List):
raise TypeError('\'mimes\' must be a list of MimeType.')
if not isinstance(types, List):
raise TypeError('\'types\' must be a list of ImageType.')
if not isinstance(is_nsfw, bool):
raise TypeError('\'is_nsfw\' must be a boolean.')
if not isinstance(is_humor, bool):
raise TypeError('\'is_humor\' must be a boolean.')
queries = {
'styles': ','.join(i.value for i in styles),
'mimes': ','.join(i.value for i in mimes),
'types': ','.join(i.value for i in types),
'nsfw': str(is_nsfw).lower(),
'humor': str(is_humor).lower(),
}
payloads = self._http.get_logo(game_ids, 'game', queries=queries)
if payloads != []:
return [Logo(payload, self._http) for payload in payloads]
return None
def get_logos_by_platform(
self,
game_ids: List[int],
platform: PlatformType,
styles: List[StyleType] = [],
mimes: List[MimeType] = [],
types: List[ImageType] = [],
is_nsfw: bool = False,
is_humor: bool = False,
) -> Optional[List[Logo]]:
"""Optional[List[:class:`Logo`]] Returns a list of logos by platform.
Parameters
-----------
game_ids: List[:class:`int`]
The game ids of the games.
platform: :class:`PlatformType`
The platform type of the logos.
styles: List[:class:`StyleType`]
The styles of the logos. Defaults to all styles.
mimes: List[:class:`MimeType`]
The mimes of the logos. Defaults to all mimes.
types: List[:class:`ImageType`]
The types of the logos. Defaults to all types.
"""
if not isinstance(game_ids, List):
raise TypeError('\'game_ids\' must be a list of integers.')
if not isinstance(platform, PlatformType):
raise TypeError('\'platform\' must be a PlatformType.')
if not isinstance(styles, List):
raise TypeError('\'styles\' must be a list of StyleType.')
if not isinstance(mimes, List):
raise TypeError('\'mimes\' must be a list of MimeType.')
if not isinstance(types, List):
raise TypeError('\'types\' must be a list of ImageType.')
if not isinstance(is_nsfw, bool):
raise TypeError('\'is_nsfw\' must be a boolean.')
if not isinstance(is_humor, bool):
raise TypeError('\'is_humor\' must be a boolean.')
queries = {
'styles': ','.join(str(i) for i in styles),
'mimes': ','.join(str(i) for i in mimes),
'types': ','.join(str(i) for i in types),
'nsfw': str(is_nsfw).lower(),
'humor': str(is_humor).lower(),
}
payloads = self._http.get_logo(
game_ids,
'platform',
platform=platform.value,
queries=queries
)
if payloads != []:
return [Logo(payload, self._http) for payload in payloads]
return None
def get_icons_by_gameid(
self,
game_ids: List[int],
styles: List[StyleType] = [],
mimes: List[MimeType] = [],
types: List[ImageType] = [],
is_nsfw: bool = False,
is_humor: bool = False,
) -> Optional[List[Icon]]:
"""Optional[List[:class:`Icon`]] Returns a list of icons by game id.
Parameters
-----------
game_ids: List[:class:`int`]
The game ids of the games.
styles: List[:class:`StyleType`]
The styles of the icons. Defaults to all styles.
mimes: List[:class:`MimeType`]
The mimes of the icons. Defaults to all mimes.
types: List[:class:`ImageType`]
The types of the icons. Defaults to all types.
is_nsfw: :class:`bool`
Whether or not the icons are NSFW. Defaults to False.
is_humor: :class:`bool`
Whether or not the icons are humor. Defaults to False.
Raises
--------
TypeError
If one of the parameters is not of the correct type.
HTTPException
If there is an error with the request.
Returns
--------
Optional[List[:class:`Icon`]]
The icons that were fetched.
"""
if not isinstance(game_ids, List):
raise TypeError('\'game_ids\' must be a list of integers.')
if not isinstance(styles, List):
raise TypeError('\'styles\' must be a list of StyleType.')
if not isinstance(mimes, List):
raise TypeError('\'mimes\' must be a list of MimeType.')
if not isinstance(types, List):
raise TypeError('\'types\' must be a list of ImageType.')
if not isinstance(is_nsfw, bool):
raise TypeError('\'is_nsfw\' must be a boolean.')
if not isinstance(is_humor, bool):
raise TypeError('\'is_humor\' must be a boolean.')
queries = {
'styles': ','.join(i.value for i in styles),
'mimes': ','.join(i.value for i in mimes),
'types': ','.join(i.value for i in types),
'nsfw': str(is_nsfw).lower(),
'humor': str(is_humor).lower(),
}
payloads = self._http.get_icon(game_ids, 'game', queries=queries)
if payloads != []:
return [Icon(payload, self._http) for payload in payloads]
return None
def get_icons_by_platform(
self,
game_ids: List[int],
platform: PlatformType,
styles: List[StyleType] = [],
mimes: List[MimeType] = [],
types: List[ImageType] = [],
is_nsfw: bool = False,
is_humor: bool = False,
) -> Optional[List[Icon]]:
"""Optional[List[:class:`Icon`]] Returns a list of icons by platform.
Parameters
-----------
game_ids: List[:class:`int`]
The game ids of the games.
platform: :class:`PlatformType`
The platform type of the icons.
styles: List[:class:`StyleType`]
The styles of the icons. Defaults to all styles.
mimes: List[:class:`MimeType`]
The mimes of the icons. Defaults to all mimes.
types: List[:class:`ImageType`]
The types of the icons. Defaults to all types.
is_nsfw: :class:`bool`
Whether or not the icons are NSFW. Defaults to False.
is_humor: :class:`bool`
Whether or not the icons are humor. Defaults to False.
Raises
--------
TypeError
If one of the parameters is not of the correct type.
HTTPException
If there is an error with the request.
Returns
--------
Optional[List[:class:`Icon`]]
The icons that were fetched.
"""
if not isinstance(game_ids, List):
raise TypeError('\'game_ids\' must be a list of integers.')
if not isinstance(platform, PlatformType):
raise TypeError('\'platform\' must be a PlatformType.')
if not isinstance(styles, List):
raise TypeError('\'styles\' must be a list of StyleType.')
if not isinstance(mimes, List):
raise TypeError('\'mimes\' must be a list of MimeType.')
if not isinstance(types, List):
raise TypeError('\'types\' must be a list of ImageType.')
if not isinstance(is_nsfw, bool):
raise TypeError('\'is_nsfw\' must be a boolean.')
if not isinstance(is_humor, bool):
raise TypeError('\'is_humor\' must be a boolean.')
queries = {
'styles': ','.join(str(i) for i in styles),
'mimes': ','.join(str(i) for i in mimes),
'types': ','.join(str(i) for i in types),
'nsfw': str(is_nsfw).lower(),
'humor': str(is_humor).lower(),
}
payloads = self._http.get_icon(
game_ids,
'platform',
platform=platform.value,
queries=queries
)
if payloads != []:
return [Icon(payload, self._http) for payload in payloads]
return None
def delete_grid(
self,
grid_ids: List[int],
) -> None:
"""Deletes list of grid images from the website.
Parameters
-----------
grid_ids: List[:class:`int`]
The grid ids to delete.
Raises
--------
TypeError
If one of the parameters is not of the correct type.
HTTPException
If there is an error with the request.
"""
if not isinstance(grid_ids, List):
raise TypeError('\'grid_ids\' must be a list of integers.')
self._http.delete_grid(grid_ids)
def delete_hero(
self,
hero_ids: List[int],
) -> None:
"""Deletes list of hero images from the website.
Parameters
-----------
hero_ids: List[:class:`int`]
The hero ids to delete.
Raises
--------
TypeError
If one of the parameters is not of the correct type.
HTTPException
If there is an error with the request.
"""
if not isinstance(hero_ids, List):
raise TypeError('\'hero_ids\' must be a list of integers.')
self._http.delete_hero(hero_ids)
def delete_logo(
self,
logo_ids: List[int],
) -> None:
"""Deletes list of logo images from the website.
Parameters
-----------
logo_ids: List[:class:`int`]
The logo ids to delete.
Raises
--------
TypeError
If one of the parameters is not of the correct type.
HTTPException
If there is an error with the request.
"""
if not isinstance(logo_ids, List):
raise TypeError('\'logo_ids\' must be a list of integers.')
self._http.delete_logo(logo_ids)
def delete_icon(
self,
icon_ids: List[int],
) -> None:
"""Deletes list of icon images from the website.
Parameters
-----------
icon_ids: List[:class:`int`]
The icon ids to delete.
Raises
--------
TypeError
If one of the parameters is not of the correct type.
HTTPException
If there is an error with the request.
"""
if not isinstance(icon_ids, List):
raise TypeError('\'icon_ids\' must be a list of integers.')
self._http.delete_icon(icon_ids)
def search_game(
self,
term: str
) -> Optional[List[Game]]:
"""Searches for games on the website.
Parameters
-----------
term: :class:`str`
The term to search for.
Raises
--------
TypeError
If one of the parameters is not of the correct type.
HTTPException
If there is an error with the request.
Returns
--------
Optional[List[:class:`Game`]]
The list of games that match the search term.
"""
if not isinstance(term, str):
raise TypeError('\'term\' must be a string.')
payloads = self._http.search_games(term)
return [Game(payload) for payload in payloads]
def set_auth_key(
self,
auth_key: str
) -> None:
"""Sets the new auth key for the API.
Parameters
-----------
auth_key: :class:`str`
The new auth key to set.
Raises
--------
TypeError
If one of the parameters is not of the correct type.
HTTPException
If there is an error with the request.
ValueError
If the auth key is not valid format.
"""
if not isinstance(auth_key, str):
raise TypeError('\'auth_key\' must be a string.')
if len(auth_key) != 32:
raise ValueError('\'auth_key\' must be a 32-character string.')
self._http.session.headers['Authorization'] = f'Bearer {auth_key}'
+3844 -811
View File
File diff suppressed because it is too large Load Diff
+6 -1
View File
@@ -36,7 +36,7 @@ show_update_message() {
# Set URLs and paths
REPO_URL="https://github.com/moraroy/NonSteamLaunchersDecky/archive/refs/heads/main.zip"
GITHUB_URL="https://raw.githubusercontent.com/moraroy/NonSteamLaunchersDecky/refs/heads/main/package.json"
GITHUB_URL="https://raw.githubusercontent.com/moraroy/NonSteamLaunchersDecky/main/package.json"
LOCAL_DIR="${logged_in_home}/homebrew/plugins/NonSteamLaunchers"
# Ask the user
@@ -136,6 +136,11 @@ fi
set -x
cd "$LOCAL_DIR"
# Ask to switch to Game Mode
zenity --question --text="Plugin installed or updated. Do you want to switch to Game Mode now?" --title="Switch to Game Mode?" --ok-label="Yes" --cancel-label="No"
if [ $? -eq 0 ]; then
+1133 -817
View File
File diff suppressed because it is too large Load Diff
+58 -28
View File
@@ -5,9 +5,18 @@
<p align="center">
<img src="https://github-readme-stats.vercel.app/api?username=moraroy&theme=transparent&show_icons=true&hide_border=true&count_private=true" width="48%" />
<img src="https://github-readme-streak-stats.herokuapp.com/?user=moraroy&theme=transparent&hide_border=true" width="48%" />
<img src="https://github-readme-stats.vercel.app/api/top-langs/?username=moraroy&theme=transparent&show_icons=true&hide_border=true&layout=compact" width="48%" />
<img
src="https://github-stats-extended.vercel.app/api?username=moraroy&theme=transparent&show_icons=true&hide_border=true&count_private=true"
width="48%"
/>
<img
src="https://streak-stats.demolab.com?user=moraroy&theme=transparent&hide_border=true"
width="48%"
/>
<img
src="https://github-stats-extended.vercel.app/api/top-langs/?username=moraroy&theme=transparent&hide_border=true&layout=compact"
width="48%"
/>
</p>
@@ -20,7 +29,8 @@
NonSteamLaunchers 🚀
</h1>
This script installs the latest UMU & GE-Proton and installs NonSteamLaunchers under one unique Proton prefix folder in your compatdata folder path called "NonSteamLaunchers" and adds them to your Steam Library. It will also add the games automatically in real time and will attempt to remove the games from your library in real time when you uninstall a game from a launcher. Collections for your games will also be created per launcher.
This script installs the latest UMU & GE-Proton and installs NonSteamLaunchers under one unique Proton prefix folder in your compatdata folder path called "NonSteamLaunchers" and adds them to your Steam Library. It will also add the games automatically in real time and will attempt to remove the games from your library in real time when you uninstall a game from a launcher. Collections for your games, launchers and web shortcuts will also be created/removed per launcher. Play time is tracked for all non steam games as well as boot videos downloaded per shortcut if any exist. Non-Downloadable Game theme music is also applied to your library. Metadata cards are also applied to your library automatically to give your non steam pages some life including Player Count if there are any on Steam!
Special ".desktop" files will be created per shortcut allowing you to run the games and launchers outside of Steam as well.
NSL can be used on Desktop or in Game Mode, and don't you worry,
Local Saves and Cloud saves are supported, as well as multiplayer/online support (because you're using the launchers). Obviously, certain anticheat games will not work on linux enviroments; this is on a game to game basis.
@@ -28,7 +38,7 @@ Local Saves and Cloud saves are supported, as well as multiplayer/online support
Features ✅
</h1>
- Automatic installation of the most popular launchers in your Steam Deck 🎮
- Automatic installation of the most popular launchers for your Steam Deck and Steam Machine on SteamOS 🎮
- Handle automatically the download and installation of your chosen launchers and the games, artwork included! ⌚️
@@ -44,10 +54,15 @@ Features ✅
- RemotePlayWhatever is also bundled with NSL to allow for local and co-op play between non steam games, this is created by m4Engi, here is the repo [here](https://github.com/m4dEngi/RemotePlayWhatever)
- Ludusavi is also pre-installed and setup for NSL for your games save backups. Not all games will work with this yet so bare this in mind when deleted or uninstalling games that are arent backed up yet, here is the repo [here](https://github.com/mtkennerly/ludusavi)
- Ludusavi is also pre-installed and setup for NSL for your games save backups. Not all games will work with this yet so bear this in mind when deleted or uninstalling games that are arent backed up yet, here is the repo [here](https://github.com/mtkennerly/ludusavi)
In both versions of NonSteamLaunchers, Desktop or Decky, NSL will back up your games saves here automatically ```/home/deck/NSLGameSaves``` The Desktop Version only does this once, at the start of when the script is opened and you see the main options list. The decky plugin version does this on every manual scan that you do.
-Pressing "Update Proton GE" in both the Desktop version or the Decky Plugin version, will give you the latest version of Proton GE and UMU. A patch will also be applied to allow Game Streaming from Discord in Game Mode.
- Pressing the "Music Button" on the top left of your game page will enable/disble the Game Theme Feature. This simply attempts to play your games theme music in the client! You can even change the music if you dont like it with the paste button!. Clicking the button will hide and disable the feature.
- [UMU Launcher](https://github.com/Open-Wine-Components/umu-launcher) is automatically used and is processed for each game and Launcher. Proton GE will be used where necessary.
### Notes
@@ -73,6 +88,8 @@ just to name a few!...there are much more videos and articles out there just wan
- [Goldenoptic Gaming](https://www.youtube.com/watch?v=dMnUn3U0dPE)
- [Deck Ready](https://www.youtube.com/watch?v=9Ap_suofBV8&t=196s) (starting at 3:16)
- [Steam Deck Checker](https://www.youtube.com/watch?v=vFRllG15jjs)
- [SteamDeckHQ](https://www.tiktok.com/@steamdeckhq/video/7579970230265384223)
- [ChoiTech](https://www.youtube.com/watch?v=ucrVWJNQ2rc)
## Articles
- [Gaming On Linux - Non-Steam Launchers Tool for Installing Popular Game Stores](https://www.gamingonlinux.com/2025/01/nonsteamlaunchers-tool-for-installing-popular-game-stores-working-on-better-desktop-linux-support/)
@@ -81,12 +98,20 @@ just to name a few!...there are much more videos and articles out there just wan
- [Dexerto - Non-Steam Launchers on Steam Deck](https://www.dexerto.com/tech/nonsteamlaunchers-steam-deck-2808063/)
- [MSN - Steam Deck: How to Install Epic Games Launcher with Decky Loader](https://www.msn.com/en-ca/news/technology/steam-deck-how-to-install-epic-games-launcher-with-decky-loader/ar-BB1pW1Ht)
- [PCMAG - How to Install Third-Party Game Launchers on Steam Deck](https://www.pcmag.com/how-to/steam-deck-install-third-party-game-launchers)
- [dadwithadeck - How to install Non-Steam Game Launchers on Steam Deck with NonSteamLaunchers](https://dadwithadeck.com/2025/11/24/how-to-install-non-steam-game-launchers-on-steam-deck-with-nonsteamlaunchers/)
<p align="center">
▶️ **YouTube Tutorial** 🡺🡺🡺 <a href="https://youtu.be/sxMmI8I9G_g">Watch here</a> 🡸🡸🡸 ▶️
▶️ <b>YouTube Tutorials</b> 🡺🡺🡺
<a href="https://youtu.be/sxMmI8I9G_g">Decky Plugin</a> |
<a href="https://www.youtube.com/watch?v=HHdw4u5enrc">GamingOnSteam</a>
🡸🡸🡸 ▶️
</p>
<p align="center">
📖 **Step-by-step Article** 🡺🡺🡺 <a href="https://steamdeckhq.com/news/nonsteamlaunchers-adds-scan-support-launchers">here</a> 🡸🡸🡸 📖
📖 <b>Step-by-step Articles</b> 🡺🡺🡺
<a href="https://steamdeckhq.com/news/nonsteamlaunchers-adds-scan-support-launchers">SteamDeckHQ Guide</a> |
<a href="https://gamingonsteam.com/2026/04/30/the-ultimate-guide-to-installing-any-launcher-on-your-steam-deck-nonsteamlaunchers/">GamingOnSteam Guide</a>
🡸🡸🡸 📖
</p>
---
@@ -110,19 +135,14 @@ Currently Working On 👷‍♂️
**Windows Installation Steps**:
1. **Sign in to GitHub** and go to this [link](https://github.com/SteamDeckHomebrew/decky-loader/actions/workflows/build-win.yml).
1. Download the current Windows version of Decky Loader here [link](https://nightly.link/SteamDeckHomebrew/decky-loader/workflows/build-win/main/PluginLoader%20Win.zip)
2. Choose the latest link or whichever version works for you.
2. Download **NSLPluginWindows.exe** from [here](https://github.com/moraroy/NonSteamLaunchersDecky/releases).
3. Scroll down to **"Artifacts"** and download **"PluginLoader Win"**. This is a zip file that you need to extract on your Windows machine. Make sure you're signed in to see the download link.
3. Run **NSLPluginWindows.exe** first. This will also create the necessary cef debugging file for Decky Loader.
4. Download **NSLPluginWindows.exe** from [here](https://github.com/moraroy/NonSteamLaunchersDecky/releases).
5. Run **NSLPluginWindows.exe** first. This will also create the necessary cef debugging file for Decky Loader.
6. Run either **No_console.exe** or **Plugin Loader.exe**, depending on your preference.
7. Go into **Game Mode** or **Big Picture Mode** to see the Decky Loader plugin and NonSteamLaunchers.
4. Run either No_console.exe or Plugin Loader.exe. You can also press Win + R, type shell:startup, and press Enter, then place the .exe there to launch Decky Loader on boot.
5. Go into **Game Mode** or **Big Picture Mode** to see the Decky Loader plugin and NonSteamLaunchers.
This setup will automatically add all your non-Steam games with artwork, correctly formatted for Windows. Only scanning will work; nothing else will function, so you can either auto-scan or manually scan your games.
@@ -150,16 +170,19 @@ How to Run 🏃‍♂️
+ Select your launchers and hit OK. This will install your selected launchers. (Optionally, check "separate app ids", to install each launcher in its own prefix)
+ ❤️ = this will send and recieves any notes you have created to the community using the ```#nsl``` tag at the beginning of your note.
+ Uninstall = uninstalls the specific launchers and possibly its games too, each launcher uninstallation is different.
+ 🔍 = Pressing the magnifying glass will stop the NSL Sanner and prompt you to restart it if needed. When you open NSL the scanner only runs once. So restarting it activates the real time service file for active scanning.
+ Start Fresh = Wipes all of NSL, all the preixes, launchers, games, etc. Shortcuts will remain, and your game save backups, if any, at ```/home/deck/NSLGameSaves``` will not be deleted.
+ 🔍 = Pressing the magnifying glass will stop the NSL Scanner and prompt you to restart it if needed. When you open NSL the scanner will update from online then auto scan. So restarting it activates the real time service file for active scanning.
+ Start Fresh = Wipes all of NSL, all the prefixes, launchers, games, etc. Shortcuts will remain, and your game save backups, if any, at ```/home/deck/NSLGameSaves``` will not be deleted. This essentially "Uninstalls" NonSteamLaunchers.
+ Move to SD Card = moves each prefix to your SD Card, this is legacy code and probably still needs work.
+ Update Proton GE = this will update and install Proton GE and UMU if you dont have it already, the script attempts to do this on each launcher install but you can do it manually and help the script before hand if you want.
+ 🖥️ Off = this simply turns off your screen, useful if your doing long downloads to save battery.
+ NSLGameSaves = this will inject your game saves from ```/home/eck/NSLGameSaves``` into its correct locations using ludusavi into your launchers. Use this if you pressed "Start Fresh" and have downloaded your launchers again, dont download your games until you have pressed this button.
+ NSLGameSaves = this will inject your game saves from ```/home/deck/NSLGameSaves``` into its correct locations using ludusavi into your launchers. Use this if you pressed "Start Fresh" and have downloaded your launchers again, dont download your games until you have pressed this button.
+ README = opens up this read me file.
To stop the NSLGameScanner.service, open up NSL and hit "🔍" it will then ask you if you want to restart it, click no, and that's it.
<h1 align="center">
Command Lines 🫡
</h1>
@@ -176,11 +199,11 @@ The NSL script can be called from online via bash, heres an example of it instal
- The "Move to SD Card" function can only be called in this format
```/bin/bash -c 'curl -Ls https://raw.githubusercontent.com/moraroy/NonSteamLaunchers-On-Steam-Deck/main/NonSteamLaunchers.sh | nohup /bin/bash -s -- "Move to SD Card" "EpicGamesLauncher"```
```/bin/bash -c 'curl -Ls https://raw.githubusercontent.com/moraroy/NonSteamLaunchers-On-Steam-Deck/main/NonSteamLaunchers.sh | nohup /bin/bash -s -- "Move to SD Card" "EpicGamesLauncher"'```
- The format of "EpicGamesLauncher" comes from the user choosing to either "Separate App ID's" or use the default installation prefix "NonSteamLaunchers" in the compatdata folder. This would be named differently for each launcher. Otherwise the command line would then only be
```/bin/bash -c 'curl -Ls https://raw.githubusercontent.com/moraroy/NonSteamLaunchers-On-Steam-Deck/main/NonSteamLaunchers.sh | nohup /bin/bash -s -- "Move to SD Card" "NonSteamLaunchers"```
```/bin/bash -c 'curl -Ls https://raw.githubusercontent.com/moraroy/NonSteamLaunchers-On-Steam-Deck/main/NonSteamLaunchers.sh | nohup /bin/bash -s -- "Move to SD Card" "NonSteamLaunchers"'```
@@ -198,7 +221,7 @@ Supported Stores 🛍
- GOG Galaxy ✔️
- Humble Games Collection ✔️
- IndieGala ✔️
- Itch.io ✔️
- itch.io ✔️
- Legacy Games ✔️
- Rockstar Games Launcher ✔️
- Ubisoft Connect ✔️
@@ -217,9 +240,13 @@ Supported Stores 🛍
- VFUN Launcher ✔️
- Tempo Launcher ✔️
- Antstream Arcade ✔️
- Hytale ✔️
- Big Fish Games Manager ✔️
- Gryphlink ✔️
- RemotePlayWhatever ✔️
- NVIDIA GeForce NOW (Native Linux) ✔️
- STOVE Client ✔️
- Moonlight Game Streaming ✔️
<h1 align="center">
Supported Streaming Sites for games and as well as any website. 🌐
@@ -254,6 +281,7 @@ Supported Streaming Sites for games and as well as any website. 🌐
- Plex ✔️
- Apple TV+ ✔️
- Crunchyroll ✔️
- Super Monkey Ball Online ✔️
<h1 align="left">
@@ -273,17 +301,19 @@ Finds Games Automatically
- HoYoPlay 🎮 💾 Full SD Card Support
- Game Jolt Client 🎮 💾 Full SD Card Support
- Minecraft Launcher 🎮
- Waydroid Apps 🎮
- Waydroid Apps 🎮 📜 Your own script Support
- Humble Games Collection 🎮 💾 Full SD Card Support
- NVIDIA GeForce NOW (Native) - You must "Favorite" the game
- NVIDIA GeForce NOW (Native Linux App) - You must "Favorite" the game with the heart. Old favorites will not be picked up, you need to re-favorite.
## Chrome Bookmarks
The scanner will pick these up automatically. But for Geforce Now only, change the name of the bookmark to your actual game name. Or you can press "Play" then Use "Ctrl + D". As long as the game name is in the Bookmark Name.
The scanner will pick these up automatically. But in Chrome for Geforce Now and Boosteroid Cloud Gaming you need to change the name of the bookmark to your actual game name. Or you can press "Play" then Use "Ctrl + D" to edit the bookmarks for the Chrome Browser. As long as the game name is in the Bookmark Name.
- Xbox Game Pass
- GeForce Now
- Amazon Luna
- Boosteroid Cloud Gaming
To stop the NSLGameScanner.service, open up NSL and hit "🔍" it will then ask you if you want to restart it, click no, and that's it.
## Waydroid Detection
If you're not in Official SteamOS or not using [this script](https://github.com/ryanrudolfoba/SteamOS-Waydroid-Installer) to install your Waydroid, you can put your own script for the App launch with the name "waydroid-cage.sh" in your own home directory and the scanner will do the rest. A good references for writing your own Script is in [here](https://github.com/SwallowKOR/cachyos-waydroid-gamemode).
-87
View File
@@ -1,87 +0,0 @@
#!/bin/bash
logged_in_user=$(logname 2>/dev/null || whoami)
logged_in_home=$(eval echo "~${logged_in_user}")
PLUGIN_DIR="$logged_in_home/homebrew/plugins/SDH-GameThemeMusic"
BIN_DIR="${PLUGIN_DIR}/bin"
MAIN_PY_PATH="${PLUGIN_DIR}/main.py"
YTDLP_PATH="${BIN_DIR}/yt-dlp"
MAIN_PY_URL="https://raw.githubusercontent.com/moraroy/SDH-GameThemeMusic/main/main.py"
YTDLP_URL="https://github.com/yt-dlp/yt-dlp/releases/download/2025.06.09/yt-dlp"
switch_to_game_mode() {
qdbus org.kde.Shutdown /Shutdown org.kde.Shutdown.logout
}
show_message() {
zenity --notification --text="$1" --timeout=2
}
zenity --question --text="Do you want to patch the SDH-GameThemeMusic Decky Plugin?" \
--title="Patch SDH_GameThemeMusic?" --ok-label="Yes" --cancel-label="No"
if [ $? -ne 0 ]; then
show_message "Patch cancelled by user."
exit 0
fi
password=$(zenity --password --title="Authentication Required")
if [ -z "$password" ]; then
show_message "Authentication cancelled."
exit 1
fi
if ! echo "$password" | sudo -S -v >/dev/null 2>&1; then
zenity --error --text="Incorrect password or sudo access denied."
exit 1
fi
if ! echo "$password" | sudo -S mkdir -p "$BIN_DIR"; then
echo "Failed to create plugin directory $BIN_DIR"
exit 1
fi
if ! echo "$password" | sudo -S test -d "$PLUGIN_DIR" || ! echo "$password" | sudo -S test -d "$BIN_DIR"; then
echo "Plugin or bin directory does not exist after creation."
exit 1
fi
echo "$password" | sudo -S chmod u+w "$PLUGIN_DIR" "$BIN_DIR" 2>/dev/null
echo "$password" | sudo -S rm -f "$MAIN_PY_PATH" "$YTDLP_PATH"
if ! curl -fsSL "$MAIN_PY_URL" -o "/tmp/main.py"; then
show_message "Failed to download main.py"
exit 1
fi
if ! curl -fsSL "$YTDLP_URL" -o "/tmp/yt-dlp"; then
show_message "Failed to download yt-dlp binary"
exit 1
fi
if ! echo "$password" | sudo -S mv /tmp/main.py "$MAIN_PY_PATH" || ! echo "$password" | sudo -S mv /tmp/yt-dlp "$YTDLP_PATH"; then
echo "Failed to move new files into place."
exit 1
fi
if ! echo "$password" | sudo -S test -f "$MAIN_PY_PATH" || ! echo "$password" | sudo -S test -f "$YTDLP_PATH"; then
echo "One or both plugin files missing after move."
exit 1
fi
echo "$password" | sudo -S chmod 644 "$MAIN_PY_PATH"
echo "$password" | sudo -S chmod 755 "$YTDLP_PATH"
echo "$password" | sudo -S chmod u-w "$PLUGIN_DIR" "$BIN_DIR" 2>/dev/null
zenity --notification --text="Patch completed successfully." --timeout=2
zenity --question --text="Plugin patched. Do you want to switch to Game Mode now? You may have to go to the Plugin Settings and Reload the SDH-GameThemeMusic plugin if it doesn't work at first." \
--title="Switch to Game Mode?" --ok-label="Yes" --cancel-label="No"
if [ $? -eq 0 ]; then
switch_to_game_mode
else
show_message "Remaining in Desktop Mode."
fi
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

After

Width:  |  Height:  |  Size: 15 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 201 KiB

+4 -4
View File
@@ -23,13 +23,13 @@ dev = [
"hypothesis[cli]>=6.88.4,<7",
"icecream>=2.1.3,<3",
"ipython>=9.0.2,<10",
"pytest>=8.0.2,<9",
"pytest>=8.0.2,<10",
"pytest-asyncio>=1.0.0,<2",
"pytest-cov>=6.0.0,<7",
"pytest-cov>=6.0.0,<8",
"pytest-datafiles>=3.0.0,<4",
"pytest-xdist>=3.4.0,<4",
"rich>=14.0.0,<15",
"ruff>=0.12.1,<0.13",
"rich>=14.0.0,<16",
"ruff>=0.12.1,<0.16",
]
[build-system]
+5 -5
View File
@@ -1,8 +1,8 @@
certifi==2025.8.3
charset-normalizer==3.4.3
idna==3.10
certifi==2026.5.20
charset-normalizer==3.4.7
idna==3.18
python-decouple==3.8
python-steamgriddb==1.0.5
requests==2.32.5
urllib3==2.5.0
requests==2.34.2
urllib3==2.7.0
vdf==3.4