Skip to content

Commit

Permalink
1.13.0 Update
Browse files Browse the repository at this point in the history
  • Loading branch information
PRO100KatYT committed Dec 31, 2023
1 parent 64a2601 commit a2d0632
Show file tree
Hide file tree
Showing 4 changed files with 101 additions and 60 deletions.
6 changes: 3 additions & 3 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -631,8 +631,8 @@ to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.

Fortnite StW Claimer for Daily Rewards, Research Points and free Llamas.
Copyright (C) 2022 PRO100KatYT
Fortnite StW Claimer for Research Points and free Llamas, Manager for your Daily Quests, and much more.
Copyright (C) 2024 PRO100KatYT

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
Expand All @@ -652,7 +652,7 @@ Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:

Save the World Claimer Copyright (C) 2022 PRO100KatYT
Save the World Claimer Copyright (C) 2024 PRO100KatYT
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
Expand Down
83 changes: 50 additions & 33 deletions SaveTheWorldClaimer.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
version = "1.12.0"
configVersion = "1.10.1"
print(f"Fortnite Save the World Claimer v{version} by PRO100KatYT\n")
versionNum = 31
versionStr = "1.13.0"
configVersion = "1.13.0"

import os
import sys
Expand All @@ -19,7 +19,6 @@
else: os.system('cls')
subprocess.call([sys.executable, os.path.realpath(__file__)] + sys.argv[1:])


# Default program language value.
language = "en"

Expand All @@ -45,15 +44,12 @@ class autoRecycling:
bShowDateTime = "false"

# Get the current date and time and neatly format it | by Salty-Coder :)
def getDateTimeString():
dateTimeObj = datetime.now()
return f"[{dateTimeObj.year:04d}/{dateTimeObj.month:02d}/{dateTimeObj.day:02d} {dateTimeObj.hour:02d}:{dateTimeObj.minute:02d}:{dateTimeObj.second:02d}]"
def getDateTimeString(): return datetime.now().strftime("[%Y/%m/%d %H:%M:%S]")

# Get the next time program is going to run by Salty-Coder
def nextrun(loopMinutes):
now = datetime.now()
nextrun = now + timedelta(minutes = loopMinutes)
return f"{nextrun.year:04d}/{nextrun.month:02d}/{nextrun.day:02d} {nextrun.hour:02d}:{nextrun.minute:02d}:{nextrun.second:02d}"
nextrun = datetime.now() + timedelta(minutes=loopMinutes)
return nextrun.strftime("%Y/%m/%d %H:%M:%S")

# Load the stringlist.json file.
stringListPath = os.path.join(os.path.split(os.path.abspath(__file__))[0], "stringlist.json")
Expand All @@ -66,18 +62,15 @@ def nextrun(loopMinutes):
exit()

# Get a string in currently selected language.
def getString(string):
try: return stringList['Strings'][language][f'{string}']
except: return stringList['Strings']['en'][f'{string}']
def getString(string): return stringList['Strings'].get(language, stringList['Strings']['en']).get(string, '')

# Get a correct plural word depending on the int.
def getPluralWord(string, number):
if number == 1: plural = 'one'
elif 2 <= number < 5: plural = 'few'
elif number == 0 or number >= 5: plural = 'many'
else: plural = 'other'
try: return stringList['Strings'][language]['words'][string][plural]
except KeyError: return stringList['Strings']['en']['words'][string][plural]
return stringList['Strings'].get(language, 'en')['words'][string][plural]

# Error with a custom message.
def customError(text):
Expand Down Expand Up @@ -105,23 +98,41 @@ def requestText(request, bCheckForErrors):

# Send token request.
def reqTokenText(loginLink, altLoginLink, authHeader):
count = 0
while True:
count += 1
if count > 1: loginLink = altLoginLink
webbrowser.open_new_tab(loginLink)
print(getString("reqtoken.message").format(loginLink))
reqToken = requestText(session.post(links.getOAuth.format("token"), headers={"Authorization": f"basic {authHeader}"}, data={"grant_type": "authorization_code", "code": input(getString("reqtoken.insertcode"))}), False)
if not "errorMessage" in reqToken: break
if "errorMessage" not in reqToken: return reqToken
else: input(getString("reqtoken.error").format(reqToken['errorMessage']))
return reqToken
loginLink = altLoginLink

# Print a message with or without the date and time.
# Print a message with or without the date and time. Send the message to Discord if the webhook url is specified in config.ini.
webhookUrl = "" # A value will be assigned in the config part of the program.
def message(string):
global webhookUrl
if bShowDateTime == "true":
string = string.replace("\n", "\n"+" "*((len(getDateTimeString()))+1))
print(f"{getDateTimeString()} {string}")
else: print(string)
lines = string.split("\n")
for i in range(len(lines)): # fix for when a message starts with newlines
if lines[i].strip() != "":
lines[i] = f"{getDateTimeString()} {lines[i]}"
break
string = "\n".join(lines)
print(string)
if not webhookUrl: return
try: session.post(webhookUrl, data=json.dumps({"content": string}), headers={"Content-Type": "application/json"})
except Exception as e:
print(getString("webhook.error").format(e))
webhookUrl = ""

# Check if there is a newer version of this program available.
def checkUpdate():
if bCheckForUpdates == "false": return
try:
getJson = (session.get("https://raw.githubusercontent.com/PRO100KatYT/SaveTheWorldClaimer/main/SaveTheWorldClaimer.py").text).splitlines()[0:2]
latestVerNum = int(getJson[0].split("=")[1].strip())
latestVerStr = getJson[1].split("=")[1].strip().strip('"')
if latestVerNum > versionNum: message(getString("updatechecker.message").format(latestVerStr))
except: []

# Create and/or read the config.ini file.
config, configPath = [ConfigParser(), os.path.join(os.path.split(os.path.abspath(__file__))[0], "config.ini")]
Expand All @@ -144,15 +155,16 @@ def message(string):
iSkip_Tutorial = validInput(getString("config.setup.bskiptutorial"), boolValues)
iLoop_Time = validInput(getString("config.setup.looptime"), "digit")
iShow_Date_Time = validInput(getString("config.setup.datetime"), boolValues)
else: iLanguage, iItemsLanguage, iSpend_Research_Points, iOpen_Free_Llamas, iRecycle_Weapons, iRecycle_Traps, iRetire_Survivors, iRetire_Defenders, iRetire_Heroes, iSkip_Tutorial, iLoop_Time, iShow_Date_Time = ["en", "en", "lowest", "true", "uncommon", "uncommon", "rare", "rare", "uncommon", "false", 0, "false"]
with open(configPath, "w") as configFile: configFile.write(getString("config.configfile").format(', '.join(langValues), iLanguage, ', '.join(itemLangValues), iItemsLanguage, iSpend_Research_Points, iOpen_Free_Llamas, iSkip_Tutorial, iRecycle_Weapons, iRecycle_Traps, iRetire_Survivors, iRetire_Defenders, iRetire_Heroes, iLoop_Time, iShow_Date_Time, configVersion))
iCheck_For_Updates = validInput(getString("config.setup.checkupdates"), boolValues)
else: iLanguage, iItemsLanguage, iSpend_Research_Points, iOpen_Free_Llamas, iRecycle_Weapons, iRecycle_Traps, iRetire_Survivors, iRetire_Defenders, iRetire_Heroes, iSkip_Tutorial, iLoop_Time, iShow_Date_Time, iCheck_For_Updates = ["en", "en", "lowest", "true", "uncommon", "uncommon", "rare", "rare", "uncommon", "false", 0, "false", "true"]
with open(configPath, "w", encoding="utf-8") as configFile: configFile.write(getString("config.configfile").format(', '.join(langValues), iLanguage, ', '.join(itemLangValues), iItemsLanguage, iSpend_Research_Points, iOpen_Free_Llamas, iSkip_Tutorial, iRecycle_Weapons, iRecycle_Traps, iRetire_Survivors, iRetire_Defenders, iRetire_Heroes, iLoop_Time, iShow_Date_Time, iCheck_For_Updates, configVersion))
print(getString("config.setup.success"))
config.read(configPath)
try: configVer = config['Config_Version']['Version']
except: customError(getString("config.readerror"))
if configVer != f"STWC_{configVersion}": customError(getString("config.versionerror"))
try:
language, itemsLang, spendAutoResearch, bOpenFreeLlamas, bSkipTutorial, loopMinutes, bShowDateTime, bSkipMainMenu = [config['StW_Claimer_Config']['Language'].lower(), config['StW_Claimer_Config']['ItemsLanguage'].lower(), config['StW_Claimer_Config']['Spend_Research_Points'].lower(), config['StW_Claimer_Config']['Open_Free_Llamas'].lower(), config['StW_Claimer_Config']['Skip_Tutorial'].lower(), config['Loop']['Loop_Minutes'], config['Misc']['Show_Date_Time'].lower(), config['Misc']['Skip_Main_Menu'].lower()]
language, itemsLang, spendAutoResearch, bOpenFreeLlamas, bSkipTutorial, loopMinutes, bShowDateTime, bCheckForUpdates, webhookUrl, bSkipMainMenu = [config['StW_Claimer_Config']['Language'].lower(), config['StW_Claimer_Config']['ItemsLanguage'].lower(), config['StW_Claimer_Config']['Spend_Research_Points'].lower(), config['StW_Claimer_Config']['Open_Free_Llamas'].lower(), config['StW_Claimer_Config']['Skip_Tutorial'].lower(), config['Loop']['Loop_Minutes'], config['Misc']['Show_Date_Time'].lower(), config['Misc']['Check_For_Updates'].lower(), config['Misc']['Discord_Webhook_URL'], config['Misc']['Skip_Main_Menu'].lower()]
autoRecycling.itemRarities = {"weapon": autoRecycling.rarities[config['Automatic_Recycle/Retire']['Recycle_Weapons'].lower()].split(", "), "trap": autoRecycling.rarities[config['Automatic_Recycle/Retire']['Recycle_Traps'].lower()].split(", "), "survivor": autoRecycling.rarities[config['Automatic_Recycle/Retire']['Retire_Survivors'].lower()].split(", "), "defender": autoRecycling.rarities[config['Automatic_Recycle/Retire']['Retire_Defenders'].lower()].split(", "), "hero": autoRecycling.rarities[config['Automatic_Recycle/Retire']['Retire_Heroes'].lower()].split(", ")}
except: customError(getString("config.readerror"))
checkValuesJson = {"Language": {"value": language, "validValues": langValues}, "ItemsLanguage": {"value": itemsLang, "validValues": itemLangValues}, "Spend_Research_Points": {"value": spendAutoResearch, "validValues": ["off", "lowest", "everyten"]}, "Open_Free_Llamas": {"value": bOpenFreeLlamas, "validValues": boolValues}, "Skip_Tutorial": {"value": bSkipTutorial, "validValues": boolValues}, "Show_Date_Time": {"value": bShowDateTime, "validValues": boolValues}, "Skip_Main_Menu": {"value": bSkipMainMenu, "validValues": boolValues}}
Expand Down Expand Up @@ -516,15 +528,20 @@ def main():
message(f"{resourcesMessage}")

# Start the program.
message(f"Fortnite Save the World Claimer v{versionStr} by PRO100KatYT\n")
checkUpdate()
if bSkipMainMenu == "false": menu()
if loopMinutes > 0:
while True:
main()
if str(loopMinutes).endswith(".0"): loopMinutes = int(str(loopMinutes).split(".")[0])
while True:
main()
if loopMinutes > 0:
loopMinutes = int(loopMinutes) if str(loopMinutes).endswith(".0") else loopMinutes
minutesWord = getPluralWord("minutes", loopMinutes)
print(getString("loop.message").format(loopMinutes, minutesWord, nextrun(loopMinutes)))
time.sleep(loopMinutes * 60)
else: main()
else:
if bSkipMainMenu == "true": break
whatToDo = validInput(getString("noloop.input"), ["0", ""])
if not whatToDo: break
menu()

input(getString("exit.pressenter"))
exit()
60 changes: 38 additions & 22 deletions readme.md
Original file line number Diff line number Diff line change
@@ -1,52 +1,63 @@
# Fortnite Save the World Research Points & free Llama Claimer

<div align="center">
<h1>Fortnite Save the World Research Points & free Llama Claimer</h1>

This program allows you to claim Research Points, open free Llamas, manage your Daily Quests and even skip the tutorial mission without opening the game.

NOTE: Due to Epic Games removing the Daily Reward system from Fortnite, the program is no longer able to claim these rewards. Read more [here](https://www.fortnite.com/news/changes-coming-to-fortnite-save-the-worlds-daily-reward-system-in-v25-10).

[![](https://img.shields.io/badge/python-3.9.5+-blue.svg)](https://www.python.org/downloads/)

[Features](#features)
[Changelog](#changelog)
[How to use it?](#how-to-use-it)
[Found a bug?](#found-a-bug)
</div>

---

### Features:
- Multiple account support with two login methods: refresh token and device auth.
- You can see more info about them and choose the method when adding an account.
- New Daily Quest getting and displaying information about them.
- Claiming new Daily Quests and displaying information about them.
- The program will display their progress and earnable rewards.
- Daily Quest replacing
- You can access the Daily Quest manager in the Main Menu.
- Research Points claiming.
- Claiming and auto spending Research Points.
- You can choose the method or toggle auto spending via config.
- Tutorial mission skipping (Unlocks the Save the World music pack even if you don't own StW).
- Automatic Research Points spending.
- You can choose the method or toggle this feature via the config.ini file.
- Claiming free Llamas.
- You can toggle this feature via the config.ini file.
- Automatic free Llama loot recycling.
- You can toggle this feature via the config.ini file.
- Claiming free Llamas and automatic free Llama loot recycling.
- You can toggle and adjust these features via config.
- Program Looping.
- You can set the time (in minutes) after the program will run again in the config file.
- You can set the time (in minutes) after the program will run again in config.
- The looping is set to 0 (disabled) by default.
- Discord Webhook integration
- You can set the Webhook URL in config.
- Multi-language support.
- Currently supported languages the program can be displayed in are: English and Polish.
- 15 languages support for Fortnite item names.

---

### Changelog:
What's new in the 1.12.0 update:
- New feature: Daily Quest replacing! Suggested [here](https://github.com/PRO100KatYT/SaveTheWorldClaimer/issues/25).
- You can access the Daily Quest manager in the Main Menu.
- Corrected the V-Buck and X-Ray Ticket rewards quantity information for Soldier, Constructor, Outlander and Ninja Mission Specialist daily quests.
What's new in the 1.13.0 update:
- Highly requested feature: Discord Webhook!
- This feature allows the program’s messages to be sent to a text channel in your Discord server.
- You can set the Webhook URL in config.
- Suggested by Salty-Coder, Lucy-2077, probablypablito, darzex and Playeereq.
- Update checker at the start of the program (toggleable via config).
- When the Main Program execution ends and `Skip_Main_Menu` is set to `false` in config:
- You are now able to go back to the Main Menu by typing `0` in the input and pressing ENTER.
- If the input is left blank, the program will stop, just like before.
- When the Main Program execution ends and `Skip_Main_Menu` is set to `true` in config, the program will no longer ask to press ENTER to exit. ([Issue #29](https://github.com/PRO100KatYT/SaveTheWorldClaimer/issues/29))
- Fixed date and time being displayed at the wrong line of text when a message begins with newlines.
- Tweaked the program's code a little bit.

What's new in the 1.11.0 & 1.11.1 updates:
- 🫡 Removed the Daily Rewards claiming due to Epic Games removing them from Fortnite.
- New feature: The program will now get today's daily quest for you & list all your current active ones with their progress and earnable rewards.
- Rewritten some parts of the code. If you encounter any new issues, please report them [here](https://github.com/PRO100KatYT/SaveTheWorldClaimer/issues/new "Click here if you want to open an issue.").
---

### How to use it?
- Install `Python 3.9.5` or newer.

- If you didn't do it yet, the program will try to automatically install the `requests` module. If the program fails to do it, install it using the `pip install requests` console command.

- After starting the SaveTheWorldClaimer.py for the first time (or after deleting the config.ini file) you will be asked if you want to start the config setup process (recommended) or use the default config values. If you want to start the setup, type 1, if no, type 2.
- After starting the SaveTheWorldClaimer.py for the first time (or after deleting config.ini) you will be asked if you want to start config setup process (recommended) or use the default config values. If you want to start the setup, type 1, if no, type 2.

- Next, you will be asked if you are logged into your Epic account in your browser. If yes, type 1, if no, type 2.

Expand Down Expand Up @@ -78,4 +89,9 @@ Feel free to [open an issue](https://github.com/PRO100KatYT/SaveTheWorldClaimer/

---

### Looking for Daily Login Rewards?
Read more [here](https://www.fortnite.com/news/changes-coming-to-fortnite-save-the-worlds-daily-reward-system-in-v25-10).

---

If you want to receive notifications about free llamas, I recommend joining [the r/FORTnITE discord server](https://discord.gg/PjqZaDmV8D "Here is the link :D") and giving yourself the freellamas role on the #role-assignment channel.
Loading

0 comments on commit a2d0632

Please sign in to comment.