I will provide you the whole codebase of a warframe market programming project that was used to find deals on the website. The project was coded by a beginner and we have to bring it to the highest standards. In short, the idea is not to rewrite the provided code with better syntax, the real idea is to write it from scratch while maintaining functionality and better coding standards, adding a GUI, making it more efficient, etc.

``````
------ readme.md ------
``````
## UPDATE
The project has been pretty much dead for the last 2 years, I did some small changes due to certain issues arising to make the project remain functional, but that's about it until WFM implements new endpoints so third party apps don't have to bombard them with requests. Hopefully when that happens their performance and speed will be as great as it once was and I'll update the project with the new functionality.

DO NOT USE THE SLOW MARKET SCANS AS OF NOW, not only it will take hours to complete but also saturate WFM servers, this includes, but is not limited to, slow riven searches and relic scans.

# Warframe Market deal finder

A personal project created as a compilation of warframe.market tools to learn python.

It's able to scan the market in order to find the best items to convert into ducats, into endo, spare parts to sell in sets, best relics to buy and sell... overall good deals.

It also comes with an experimental deal finder for Rivens, being able to provide a rating based on its stats and grades by using a profile system.

There're several search modes, depending on the speed and accuracy that is needed, however the execution speed is greatly limited (+95%) by the API limits of the website. The recommended modes are the fast search (1-2 minutes, around 8000 rivens) and the slow one (25-30 minutes, 25000-50000 rivens).

## Customization

The user can customize the wanted weapons, rolls and many other settings by editing the files inside the config folder.

The weapon type order used when editing rolls or weapons is this one: 0 primaries, 1 shotguns, 2 pistols, 3 melees, 4 archguns.

To add or remove weapons edit wantedWeapons.csv. To add or remove rolls edit wantedRolls.json

## How to use

It currently uses Python 3.10.7, install the library ratelimit using the command "pip3 install ratelimit" and execute the main file using "python deal_finder.py" on the project folder.


## TODO

As of now there're several things to do, which I may continue some time in the future.
- Add support for rivenhunter.com and riven.market.
   - Translation system for URLs of WFM to the other websites. DONE but RH requests are blocked.
   - Multiprocessing for each website in order to shorten waiting times. 
   - Slight editions on request processing and riven object creation.
- Update the GUI to make the tool accessible for normal people.
   - Finish the remaining screens with Sigma. 
   - Add functionality to the UI. 
- Update ducat values when updating the database.
- Current weightings for the different profiles are mostly picked by gut feeling, a mathematical procedure should be implemented.
- Upgrade the code's quality, which is a hard task as it is mostly spaghetti right now.

------ src/ducanator.py ------
``````
"""Module used to scan the market in search of good ducat/plat ratio items"""

import os
from datetime import datetime

import src.settings as settings
import src.utils as utils

trades = {}


def use_ducanator(limit):
    """It starts the search based on the limit the user has set"""
    os.chdir(settings.path + "\\config")
    get_orders(limit)


def seller_prof(seller):
    """Method used to get the value of a seller"""
    global trades
    if seller not in trades:
        seller_orders = utils.get_wfm_request(
            "profile/" + seller + "/orders?include=profile"
        )["sell_orders"]
        seller_orders = sorted(seller_orders, key=lambda ord: ord["platinum"])
        item_num = 0
        trader = {}
        trader["items"] = []
        trader["price"] = 0
        trader["ducats"] = 0
        for sell in seller_orders:
            if item_num < 6 and "ducats" in sell["item"]:
                item_num += sell["quantity"]
                trader["items"].append(
                    {
                        "name": sell["item"]["url_name"],
                        "price": sell["platinum"],
                        "ducats": sell["item"]["ducats"],
                        "quantity": sell["quantity"],
                    }
                )
                trader["price"] += sell["platinum"]
                trader["ducats"] += sell["item"]["ducats"]
        if item_num >= 6:
            if len(trader["items"]) > 1:
                trades[seller] = trader
                trades[seller]["value"] = (
                    trades[seller]["ducats"] / trades[seller]["price"]
                )
                trades = dict(sorted(trades.items(), key=lambda ord: ord[1]["value"]))


def get_orders(limit):
    """Method used to get the ducats/plat value of the items.
    A dict is created in order to save the data of each item,
    some other useful data is also stored"""

    ducat_dict = {}

    ducat_list = open("ducat_list.csv", "r", encoding="utf-8")

    # Creates a dict with the item name as key and the ducat value as value
    for line in ducat_list.readlines():
        item_name, ducats = line.replace("\n", "").split(",")
        if item_name not in ducat_dict:
            ducat_dict[item_name] = {}
        ducat_dict[item_name]["ducats"] = int(ducats)
        item_data = ducat_dict[item_name]

        # Gets the orders for the items in the dict and calculates the ducat/plat value
        result = utils.get_wfm_request("items/" + item_name + "/orders")["orders"]
        result = [
            x
            for x in result
            if x["user"]["status"] == "ingame"
            and x["user"]["reputation"] > 1
            and x["order_type"] == "sell"
        ]
        if len(result) <= 1:
            continue
        result = sorted(result, key=lambda ord: ord["platinum"])[0]
        item_data["price"] = result["platinum"]
        item_data["seller"] = result["user"]["ingame_name"]
        item_data["quantity"] = result["quantity"]
        item_data["value"] = item_data["ducats"] / item_data["price"]

        # Checks if the item is worth buying and prints its data on real time.
        if item_data["value"] >= limit:
            line = (
                datetime.now().strftime("%H:%M:%S")
                + " item: "
                + item_name.title().replace("_", " ")
                + " ducats/plat: "
                + str(item_data["value"])
                + " price: "
                + str(item_data["price"])
                + " quantity: "
                + str(item_data["quantity"])
                + " seller: "
                + item_data["seller"]
            )
            print(line)

            # Checks the best possible traders for said item and updates their inventory.
            seller_prof(item_data["seller"])
            utils.wfm_string(item_name, item_data["seller"], item_data["price"])
            if len(trades) >= 6:
                print("Best current trades:\n")
                for i in range(1, 7):
                    line = (
                        "Seller: "
                        + str(list(trades)[-i])
                        + " ducats/plat: "
                        + str(trades[list(trades)[-i]]["value"])
                        + " price: "
                        + str(trades[list(trades)[-i]]["price"])
                        + " ducats: "
                        + str(trades[list(trades)[-i]]["ducats"])
                        + "\n"
                    )
                    print(line)

    # Prints the best possible trades on a wfm like string along with the quantities of the items.
    for item in trades[list(trades)[-1]]["items"]:
        utils.wfm_string(item["name"], str(list(trades)[-1]), item["price"])
        print(str(item["quantity"]) + " units of " + item["name"] + "\n")

    ducat_list.close()


def retrieve_list():
    """Method used to get the list of prime parts or sets of wfm."""

    string_set = set()
    result = utils.get_wfm_request("items")
    if result:
        item_list = [item["url_name"] for item in result["items"]]
        result_file = open("item_list.csv", "w", encoding="utf-8")
        for item in item_list:
            if "prime" in item and "set" not in item and "primed" not in item:
                if item + "\n" not in string_set:
                    result_file.write(item + "\n")
                string_set.add(item + "\n")
        result_file.close()


def retrieve_ducats():
    """Method used to get the value in ducats of the item list."""

    item_list = open("item_list.csv", "r", encoding="utf-8")
    ducat_list = open("ducat_list.csv", "w", encoding="utf-8")
    string_set = set()
    lines = item_list.readlines()
    url_list = []
    for line in lines:
        url_list.append("items/" + line.replace("\n", ""))
    for url in url_list:
        result = utils.get_wfm_request(url)
        if result:
            result = result["item"]["items_in_set"]
            for set_item in result:
                if set_item["url_name"] not in string_set:
                    if set_item["ducats"] >= 45:
                        ducat_list.write(
                            set_item["url_name"] + "," + str(set_item["ducats"]) + "\n"
                        )
                        string_set.add(set_item["url_name"])
    item_list.close()
    ducat_list.close()


def update_ducats():
    """Method used to update the item and ducat lists"""

    os.chdir(settings.path + "\\config")
    retrieve_list()
    retrieve_ducats()

``````
------ src/request_processor.py ------
``````
"""Process the auction data from warframe.market and converts
it into a list of riven class objects.
If the user chooses to use the scan mode it will actively check if the auctions are specific
rolls and their price is below the user's price."""

import src.riven_stuff.riven as riven
import src.settings as settings


def process_data(response):
    """Process the auction data from warframe.market and converts
    it into a list of riven class objects.
    If the user chooses to use the scan mode it will actively check if the auctions are specific
    rolls and their price is below the user's price."""

    riven_list = {}
    for auction in response:
        if auction["closed"] is True or "ingame_name" not in auction["owner"]:
            continue
        if (
            auction["item"]["weapon_url_name"]
            + auction["item"]["name"]
            + auction["owner"]["ingame_name"]
            in riven_list
        ):
            continue
        # converts the dict of riven stats to list in order to get the negative in the last spot.
        # if there's any way to fast check if there's a negative in the stats while using a dict
        # then this would be innecessary.
        stats = [list() for x in range(4)]
        for i, stat in enumerate(auction["item"]["attributes"]):
            stats[i].extend([stat["positive"], stat["url_name"], stat["value"]])
        # removes empty positions of stats.
        stats = list(filter(None, stats))
        # sorts the stats list to put the negative stat at the end.
        stats.sort(key=lambda x: x[0] if len(x) > 0 else False, reverse=True)

        name = auction["item"]["weapon_url_name"].replace("_", " ").title()
        if len([i for i in settings.weapons if i["name"] == name]) == 0:
            continue

        # adds the riven into the riven_list.
        riv = riven.Riven(
            auction["item"]["weapon_url_name"],
            auction["item"]["name"],
            auction["starting_price"],
            auction["buyout_price"],
            auction["owner"]["ingame_name"],
            auction["item"]["polarity"],
            auction["item"]["re_rolls"],
            auction["item"]["mastery_level"],
            auction["item"]["mod_rank"],
            stats,
        )
        riven_list[
            auction["item"]["weapon_url_name"]
            + auction["item"]["name"]
            + auction["owner"]["ingame_name"]
        ] = riv
    return riven_list


def export_txt(results):
    """Writes every riven message on their respective folders
    each riven has its own different paths depending on the weapon and the rating."""

    content_dict = {}
    for riv in results:
        for path in riv.paths:
            if path in content_dict:
                content_dict[path] += riv.message
            else:
                content_dict[path] = riv.message
    
    for path, message in content_dict.items():
        ducat_list = open(path, "w", encoding="utf-8")
        ducat_list.write(message)

``````
------ src/riven_stuff/riven.py ------
``````
"""Module used to store riven-related data and means to calculate it.
Logic and data should be separated but I'm too mongolic to do it yet"""

import src.riven_stuff.riven_rater as riven_rater
import src.settings as settings
import src.utils as utils


class Riven:
    """class used to store all riven-related data and means to calculate it."""

    def __init__(
        self,
        weapon="",
        name="",
        initial_price=-1,
        buyout_price=-1,
        seller="",
        polarity="",
        rerolls=-1,
        mastery_rank=-1,
        riven_rank=-1,
        stats=[],
    ):
        self.weapon_name = weapon  # name of the weapon.
        self.name = name.capitalize()  # name of the riven.
        self.initial_price = initial_price  # starting price of the auction.
        self.buyout_price = buyout_price  # buyout price of the auction.
        self.seller = seller  # name of the seller.
        self.stats = stats  # stats of the riven.
        self.polarity = polarity.capitalize()  # polarity of the riven.
        self.rerolls = rerolls  # number of rerolls.
        self.mastery_rank = mastery_rank  # mastery rank needed to use the riven.
        self.riven_rank = riven_rank  # rank of the riven
        self.buyout_price = (
            buyout_price if buyout_price else 99999
        )  # buyout price of the riven.
        self.riven_type = utils.get_weapon_type(self.weapon_name)
        self.weapon = self.get_stats()
        self.outdated = False  # indicates if the stats are outdated or wrong.
        self.wanted_weapon = (
            self.is_wanted()
        )  # indicates if the riven is in the wanted weapons list.
        self.wanted_roll = False
        self.grades = self.get_grades()  # checks stats grades.
        self.riven_rate = (
            self.check_stats()
        )  # riven rating based on check_stats algorithm.
        self.paths = self.prepare_paths()
        self.real_value = (max(0, self.riven_rate) / self.buyout_price * 100) ** (
            max(0, self.riven_rate) / 100
        )
        self.message = self.riv_to_text()

    def is_wanted(self):
        """checks if the weapon is wanted for godrolls, specific rolls and unrolleds."""

        if self.weapon_name in settings.wanted_unroll:
            return True
        if any(self.weapon_name == x["weapon"] for x in settings.wanted_rv):
            return True
        if any(self.weapon_name == y for x in settings.wanted_rv for y in x):
            return True
        return False

    def get_stats(self):
        """Finds the best version of the weapon, useful to weight stats when rating rivens"""
        name = self.weapon_name.replace("_", " ").title()
        family = [w for w in settings.weapons if w["name"] == name][0]["family"]
        versions = [w for w in settings.weapons if w["family"] == family]
        sorted(versions, key=lambda w: w["disposition"])
        self.disposition = [float(v["disposition"]) for v in versions]

        return versions[0]

    def rate_stats(self):
        """Rates the stats of the riven based on the profiles of the weapon."""
        self.scores = riven_rater.rate_riven(self)
        return self.scores[0][1]

    def check_stats(self):
        """Punctuation system. it checks how good the riven stats are against
        settings. wanted or decent combinations of its types."""

        punctuation = self.rate_stats()

        if self.weapon_name in settings.wanted_rv:
            for wanted_roll in settings.wanted_rv[self.weapon_name]:
                stats = [stat[1] for stat in self.stats]
                if utils.compare_stats(list(wanted_roll["stats"].values()), stats):
                    if self.stats[-1][0] is False:
                        self.wanted_roll = True
                    break

        return punctuation

    def calculate_grade(self, stat, dispo, fakerank):
        """Gets the proper grade of the stat,
        trying every disposition and also checks for fake ranks"""

        # gets the base value of the stat based on the weapon type.
        # find the value of the stat in settings.stat_list
        res = abs(
            float(
                [x for x in settings.stat_list if x["wfm_name"] == stat[1]][0][
                    "value" + str(self.riven_type)
                ]
            )
            * float(dispo)
        )
        res = (res / 9) * (self.riven_rank + 1) if not fakerank else res
        # if there is negative.
        if self.stats[-1][0] is False:
            # if the stat is negative.
            if stat[0] is False:
                res *= 0.5 if len(self.stats) == 3 else 0.7575
            else:
                res *= 1.25 if len(self.stats) == 3 else 0.947
        else:
            # if there is no negative and 3 positives.
            if len(self.stats) == 3:
                res *= 0.7575
        # puts res in a 0-1 scale. formula is (value - minimum) / (maximum - minimum).
        res = (abs(stat[2]) - res * 0.9) / (res * 1.1 - res * 0.9) if res != 0 else 0
        # above calculations should be changed based on the new scale
        # however it's easier to work with this one.
        if stat[1] == "range":
            if 1.1 > res > 1:
                res = 1
            elif 0 > res > -0.1:
                res = 0
        return res

    def calculate_grades(self, fakerank):
        """Calculates the grade of the stat of a riven.
        if the grades aren't compatible with a given disposition it tries with other ones,
        if the weapon has any variant.
        if there is no disposition to which grades are good it returns the closest one
        based on a distance system.
        the formula is: base stat value based on weapon type * disposition * stat system.
        the system calculates res based on these rules:
        if the weapon has 3 positives and a negative the positives are
        weighted *0.947 and the negative *0.7575.
        if the weapon has 2 positives and a negative the positives are
        weighted *1.25 and the negative *0.5.
        if the weapon has 3 positives and no negative the positives are weighted *0.7575.
        if the weapon has 2 positives and no negative the positives stay the same."""

        grades = [0, 0, 0, 0]
        best_distance = 9999

        for dispo in self.disposition:
            distance, buen_grade = 0, 0
            grades_aux = []
            for stat in self.stats:
                res = self.calculate_grade(stat, dispo, fakerank)
                grades_aux.append(res)
                # if res is within the 0-1 scale then the grade is good.
                # otherwise we get the distance to said range.
                if 0 < res < 1:
                    buen_grade += 1
                else:
                    distance += abs(res) if res < 0 else res - 1
            # if all the stats have grades within 0-1 scale it returns them.
            # else if the distance is less than the current best
            # distance it updates the grades and the distance.
            if buen_grade == len(self.stats):
                self.used_disposition = dispo
                return grades_aux
            if distance < best_distance:
                grades = grades_aux
                best_distance = distance

        if best_distance > 30 and fakerank is False:
            return self.calculate_grades(True)

        if any(0 > grade or grade > 1.1 for grade in grades):
            self.outdated = True

        self.used_disposition = self.disposition[0]
        return grades

    def get_grades(self):
        """This module normalizes the grades then calculates
        its punctuation based on simodeus bot."""

        grades = [
            utils.scale_range(grade, 0, 1, -10, 10)
            for grade in self.calculate_grades(False)
        ]
        self.grade_letters = []
        for grade in grades:
            if grade > 9.5:
                self.grade_letters.append("S")
            elif grade > 7.5:
                self.grade_letters.append("A+")
            elif grade > 5.5:
                self.grade_letters.append("A")
            elif grade > 3.5:
                self.grade_letters.append("A-")
            elif grade > 1.5:
                self.grade_letters.append("B+")
            elif grade > -1.5:
                self.grade_letters.append("B")
            elif grade > -3.5:
                self.grade_letters.append("B-")
            elif grade > -5.5:
                self.grade_letters.append("C+")
            elif grade > -7.5:
                self.grade_letters.append("C")
            elif grade > -9.5:
                self.grade_letters.append("C-")
            else:
                self.grade_letters.append("F")
        return grades

    def prepare_paths(self):
        """Creates the paths and files that the riven will write its information in"""

        paths = []
        if self.wanted_weapon is True:
            path = settings.wanted_path
        else:
            path = settings.unwanted_path
        paths.append(path + "\\All rivens.txt")
        if self.rerolls == 0:
            paths.append(path + "\\Unrolleds\\Unrolled list.txt")
            paths.append(
                path
                + "\\Unrolleds\\Unrolled "
                + self.weapon_name.capitalize().replace("_", " ")
                + ".txt"
            )
        if self.wanted_roll:
            paths.append(path + "\\Wanted rolls\\Wanted rolls.txt")
            paths.append(
                path
                + "\\Wanted rolls\\Wanted "
                + self.weapon_name.capitalize().replace("_", " ")
                + ".txt"
            )
        if self.riven_rate >= 95:
            paths.append(path + "\\95-100. Godrolls\\Godrolls.txt")
            paths.append(
                path
                + "\\95-100. Godrolls\\"
                + self.weapon_name.capitalize().replace("_", " ")
                + " Godrolls.txt"
            )
        elif self.riven_rate >= 85:
            paths.append(path + "\\85-95. Good rivens\\Good rivens.txt")
            paths.append(
                path
                + "\\85-95. Good rivens\\Good "
                + self.weapon_name.capitalize().replace("_", " ")
                + ".txt"
            )
        elif self.riven_rate >= 70:
            paths.append(path + "\\70-85. Decent rivens\\Decent rivens.txt")
            paths.append(
                path
                + "\\70-85. Decent rivens\\Decent "
                + self.weapon_name.capitalize().replace("_", " ")
                + ".txt"
            )
        elif self.riven_rate >= 60:
            paths.append(path + "\\60-70. Usable rivens\\Usable rivens.txt")
            paths.append(
                path
                + "\\60-70. Usable rivens\\Usable "
                + self.weapon_name.capitalize().replace("_", " ")
                + ".txt"
            )
        elif self.riven_rate >= 40:
            paths.append(path + "\\40-60. Bad rivens\\Bad rivens.txt")
            paths.append(
                path
                + "\\40-60. Bad rivens\\Bad "
                + self.weapon_name.capitalize().replace("_", " ")
                + ".txt"
            )
        elif self.riven_rate < 40:
            paths.append(path + "\\Trashcan\\Trashcan.txt")
            paths.append(
                path
                + "\\Trashcan\\"
                + self.weapon_name.capitalize().replace("_", " ")
                + " Trash.txt"
            )
        return paths

    def riv_to_text(self):
        """Writes the riven data in specified folders depending on the riven caracteristics."""
        message = (
            "Name: "
            + self.weapon_name.capitalize().replace("_", " ")
            + " "
            + self.name
            + "\n"
        )
        # riven rating.
        message += "Experimental riven rating: " + str(round(self.riven_rate, 2)) + "\n"

        if len(self.stats) == 3 and not self.stats[-1][0]:
            message += "Two stats!\n"

        # best rating profile
        message += "Best profile: " + self.scores[0][0] + "\n"
        # other profiles
        for profile, rating in self.scores[1:]:
            message += profile + ": " + str(rating) + ", "
        # if outdated.
        if self.outdated:
            message += "\nThe stats of this riven don't match its rank, they may be outdated or wrong.\n"
        # stats and grades.
        message += "\nRiven stats: \n"
        for i, stat in enumerate(self.stats):
            message += (
                [x for x in settings.stat_list if x["wfm_name"] == stat[1]][0]["name"]
                + ":  "
                + str(stat[2])
                + "\n"
            )
            if self.outdated is False:
                message += (
                    "\t\tGrade: "
                    + self.grade_letters[i]
                    + " ("
                    + str(round(self.grades[i], 2))
                    + "%)\n"
                )

        message += (
            "\nPolarity: "
            + self.polarity
            + "   Rerolls: "
            + str(self.rerolls)
            + "   MR: "
            + str(self.mastery_rank)
            + "\n"
        )
        message += "Riven rank : " + str(self.riven_rank) + "\n"
        if self.buyout_price != 99999:
            message += (
                "Initial price:  "
                + str(self.initial_price)
                + "   Buyout price: "
                + str(self.buyout_price)
                + "\n"
            )
        else:
            message += (
                "Initial price:  "
                + str(self.initial_price)
                + "   Buyout price: "
                + " infinite \n"
            )
        message += "Seller: " + self.seller + "\n"
        message += "Value/Plat: " + str(round(self.real_value, 2)) + "\n"
        message += "Used disposition: "
        message += str(round(self.used_disposition, 2)) + "\n"
        message += "Real disposition: "
        message += (
            str(round(self.disposition[0], 2))
            + "\n--------------------------------------------------\n"
        )
        return message

``````
------ src/riven_stuff/riven_rater.py ------
``````
"""Class used to calculate the best grades for each riven,
the punctuation system is based off pricings on different
platforms and trade chat.
It uses the weapon, its type, class, uses, stats...
"""
import src.settings as settings
import src.utils as utils


class Profile:
    """Class used to calculate the best grades for each riven,
    the punctuation system is based off pricings on different
    platforms and trade chat.
    It uses the weapon, its type, class, uses, stats...
    """

    def __init__(self, name, weapon_type, stat_weights, equations):
        self.name = name
        self.weapon_type = weapon_type
        self.stat_weights = stat_weights
        self.equations = equations

    def evaluate_stats(self, riven):
        """The method searches for the weight of the stats based on the
        weapon class, type and stats and its equation, then calculates
        a total rating for the riven"""

        punctuation = 0
        worst = 99
        for stat, grade in zip(riven.stats, riven.grades):
            weight = self.stat_weights[stat[1]][not stat[0]]
            equation = eval(self.equations[stat[1]])
            if equation < worst and stat[0]:
                worst = equation
            punctuation += equation
            if -10 < grade < 11:
                punctuation += (
                    weight * grade * 0.02 if stat[0] else weight * -grade * 0.02
                )
        if worst < 5:
            return punctuation - 20

        return punctuation


def generate_profiles():
    """Method used to load the profiles that'll be used"""

    for i, w_type in enumerate(settings.weights):
        for key, val in w_type.items():
            settings.profiles.append(Profile(key, i, val, settings.equations[i][key]))


def find_profiles(riven):
    """Method used to find the profiles that'll be used for the weapon, should rework it"""

    profiles = []
    for prof in settings.profiles:
        if prof.weapon_type == riven.riven_type:
            profiles.append(prof)
    return profiles


def profile_management(riven, prof, punct):
    """Method used to filter the profiles punctuation"""
    if prof.name == "Beam" and riven.weapon["attack_name"] not in ["Beam", "Held"]:
        punct = 0
    elif (
        prof.name == "Eidolon hunting - Vectis"
        and "vectis" not in riven.weapon_name.lower()
        or prof.name == "Eidolon hunting - Rubico"
        and "rubico" not in riven.weapon_name.lower()
    ):
        punct = 0
    elif riven.riven_type == 3:
        if prof.name == "Heavy" and (
            riven.weapon["class"]
            not in [
                "Gunblade",
                "Scythe",
                "Two-handed nikana",
                "Dual daggers",
                "Dagger",
                "Rapier",
                "Claws",
                "Hammer",
                "Machete",
            ]
            and riven.weapon_name not in ["paracesis", "korrudo"]
        ):
            punct /= 1.75
        if prof.name == "Glaive" and riven.weapon["class"] != "Glaive":
            punct = 0
        if prof.name != "Glaive" and riven.weapon["class"] == "Glaive":
            punct = 0
        if prof.name != "Gunblade" and "Gunblade" in riven.weapon["class"]:
            punct = 0
        if prof.name == "Gunblade" and "Gunblade" not in riven.weapon["class"]:
            punct = -20
        if riven.disposition[0] < 1.1 and "statstick" in prof.name:
            punct = punct * riven.disposition[0] / 2
    if (
        riven.riven_type == 2
        and prof.name == "Co-primer"
        and riven.weapon_name not in ["epitaph", "nukor", "cycron"]
    ):
        punct = -20
    if ("Rubico" in prof.name and "rubico" not in riven.weapon_name) or (
        "Vectis" in prof.name and "vectis" not in riven.weapon_name
    ):
        punct = -20
    elif (
        "rubico" in riven.weapon_name or "vectis" in riven.weapon_name
    ) and prof.name == "General use":
        punct /= 2

    return punct


def rate_riven(riven):
    """Searches for the weapon profiles, then executes them and chooses
    the best out of them, returning the best riven punctuation"""
    profiles = find_profiles(riven)
    initial_punct = -30
    if not riven.stats[-1][0]:
        initial_punct += 20
        a, b = 15, 0
        if len(riven.stats) == 3:
            a, b = b, a
        initial_punct += utils.scale_range(riven.disposition[0], 0.5, 1.5, a, b)

    punct_list = []
    for prof in profiles:
        punct_list.append(
            [
                prof.name,
                round(
                    utils.scale_range(
                        profile_management(
                            riven, prof, initial_punct + prof.evaluate_stats(riven)
                        ),
                        -30,
                        95,
                        0,
                        100,
                    ),
                    2,
                ),
            ]
        )

    if len(punct_list) == 0:
        print("xd")
    return sorted(punct_list, key=lambda punct: punct[1], reverse=True)

``````
------ src/riven_stuff/riven_relisting.py ------
``````
"""
File used to relist auctions based on the user's preference.
it logins with the user credentials, then retrieves all their auctions.
depending on the user's choice, it will relist all, or only the auctions that doesn't have bids.
"""
import json

import requests
from ratelimit import limits, sleep_and_retry
import time

import src.utils as utils

# practical information.
WFM_API = "https://api.warframe.market/v1"

# attributes that will get deleted from the auctions in order to relist them.
deleteattr = [
    "visible",
    "owner",
    "created",
    "updated",
    "winner",
    "closed",
    "note_raw",
    "top_bid",
    "platform",
    "id",
    "is_direct_sell",
    "is_marked_for",
    "marked_operation_at",
]


# deletes then list again the selected auctions.
@sleep_and_retry
@limits(calls=3, period=1.5)
def relist_auctions(bids):
    """File used to relist auctions based on the user's preference.
    it logins with the user credentials, then retrieves all their auctions.
    depending on the user's choice, it will relist all, or only the auctions
    that doesn't have bids.
    """

    for auction in utils.get_current_auctions():
        time.sleep(0.5)
        if bids and not auction["top_bid"] or not bids:
            auction_id = auction["id"]
            for attr in deleteattr:
                auction.pop(attr, None)
            # deletes the auctions.
            result = requests.put(
                WFM_API + "/auctions/entry/" + auction_id + "/close",
                stream=True,
                headers=utils.headers,
            )
            if result.status_code == 200:
                print(
                    auction["item"]["weapon_url_name"].capitalize().replace("_", " ")
                    + " "
                    + auction["item"]["name"].capitalize().replace("_", " ")
                    + " succesfully cancelled"
                )
            else:
                print(
                    auction["item"]["weapon_url_name"].capitalize().replace("_", " ")
                    + " "
                    + auction["item"]["name"].capitalize().replace("_", " ")
                    + " not cancelled"
                )
            rel = False
            adder = 0
            while not rel:
                # relists them.
                time.sleep(0.5 + adder)
                adder += 0.5
                result = requests.post(
                    WFM_API + "/auctions/create",
                    data=json.dumps(auction),
                    stream=True,
                    headers=utils.headers,
                )
                if result.status_code == 200:
                    print(
                        auction["item"]["weapon_url_name"]
                        .capitalize()
                        .replace("_", " ")
                        + " "
                        + auction["item"]["name"].capitalize().replace("_", " ")
                        + " succesfully posted"
                    )
                    rel = True
                else:
                    print(
                        auction["item"]["weapon_url_name"]
                        .capitalize()
                        .replace("_", " ")
                        + " "
                        + auction["item"]["name"].capitalize().replace("_", " ")
                        + " not posted"
                    )

``````
------ src/riven_stuff/riven_requests.py ------
``````
"""
    Modules used to do requests to the server.
    they are feeded with a list of urls created in url_creation.
    the api call rate used right now is a call each 0.5s.
    increasing it may causes the server to ban you.
"""

import src.settings as settings
import src.utils as utils


def wfm_riven_request(url):
    """Retrieves the list of auctions from the url if the request works"""
    result = utils.get_wfm_request(url)
    if result:
        return result["auctions"]


def rh_riven_request(url):
    """Retrieves the list of auctions from the url if the request works"""
    result = utils.get_rh_request(url)
    if result:
        return result


def mass_riven_request(riven_list):
    """Method used to call for requests for every url, then return the list of results"""
    if settings.search_type:
        results = [[], []]
        for riven in riven_list[0]:
            results[0].extend(wfm_riven_request(riven))
        for riven in riven_list[1]:
            results[1].extend(rh_riven_request(riven))
        return results
    else:
        results = []
        for riven in riven_list:
            rs = wfm_riven_request(riven)
            if rs:
                results.extend(rs)
        return results

``````
------ src/scanners/buy_scanner.py ------
``````
"""Class used to scan the market in search of prices near the ones the user's looking for"""

import src.utils as utils


def scan_buys(name):
    """Method used to retrieve the list of auctions from the user,
    then search for them and apply some conditions and print them
    """

    print("Looking for cheap buys")
    url = "profile/" + name + "/orders?include=profile"
    if (item_list := utils.get_wfm_request(url)) is not None and len(
        list(item_list["buy_orders"])
    ) != 0:
        item_list = list(item_list["buy_orders"])
        while True:
            for product in item_list:
                url = "items/" + product["item"]["url_name"] + "/orders?include=item"
                if (orders := utils.get_wfm_request(url)) is None:
                    continue
                orders = [
                    x
                    for x in orders["orders"]
                    if x["user"]["status"] == "ingame"
                    and x["order_type"] == "sell"
                    and (
                        "mod_rank" not in x
                        or ("mod_rank" in x and x["mod_rank"] == product["mod_rank"])
                    )
                ]
                if not orders:
                    continue
                order = sorted(orders, key=lambda x: x["platinum"])[0]
                if order["platinum"] <= (product["platinum"] * 1.1):
                    utils.wfm_string(
                        product["item"]["url_name"],
                        order["user"]["ingame_name"],
                        str(order["platinum"]),
                    )
    else:
        print("No buy orders found")

``````
------ src/scanners/deal_scanner.py ------
``````
"""Module used to scan the market in search of good deals."""

from datetime import datetime

from ratelimit import limits, sleep_and_retry

import src.utils as utils


@sleep_and_retry
@limits(calls=3, period=1.5)
def deal_finder():
    """Method used to scan the market in search of good prices.
    The conditions used are:
        -Be below 70% of the average price
        -Be below 70% of the other 3 best sells
    """

    print("Looking for deals")
    item_list = utils.get_wfm_request("items")
    if not item_list:
        return
    item_list = [x["url_name"] for x in item_list["items"]]
    while True:
        for item in item_list:
            item_stats = utils.get_wfm_request("items/" + item + "/statistics")
            if item_stats is None:
                continue
            item_stats = item_stats["statistics_closed"]["48hours"]
            if not item_stats:
                continue
            if "mod_rank" in item_stats[0]:
                stat_list = analyze_mod(item, item_stats)
            else:
                stat_list = [analyze_part(item, item_stats)]

            item_orders = utils.get_wfm_request(
                "items/" + item + "/orders?include=item"
            )
            if item_orders is None:
                continue

            item_orders = [
                x for x in item_orders["orders"] if x["user"]["status"] == "ingame"
            ]
            item_orders.sort(key=lambda x: x["platinum"])
            sell_orders = [x for x in item_orders if x["order_type"] == "sell"]

            if sell_orders == 0:
                continue

            for i in (0, 1):
                for rank_stats in stat_list:
                    sell_rank = (
                        [
                            x
                            for x in sell_orders
                            if "mod_rank" in x
                            and x["mod_rank"] == rank_stats["mod_rank"]
                        ]
                        if "mod_rank" in rank_stats
                        else sell_orders
                    )
                    if len(sell_rank) < [1, 4][i]:
                        continue

                    order = sell_rank[0]
                    if not i:
                        rank_stat = (
                            next(
                                x
                                for x in rank_stats
                                if order["mod_rank"] == x["mod_rank"]
                            )
                            if isinstance(type(rank_stats), list)
                            else rank_stats
                        )

                    if (
                        comparator(order, [rank_stat, sell_rank][i], i)
                        and order["platinum"] > 10
                        and (rank_stats["volume"] > 5 or order["platinum"] > 100)
                    ):
                        print_deal(
                            item,
                            order["user"]["ingame_name"],
                            order["platinum"],
                            order["quantity"],
                            ["first", "second"][i],
                            [
                                rank_stats["lowest_2d_average"],
                                sum([c["platinum"] for c in sell_rank[0:3]]) / 3,
                            ][i],
                            order.get("mod_rank"),
                        )


def analyze_mod(item, item_stats):
    """Method used to analyze a mod item.
    It gets the stats of every rank of the mod.
    """
    mod_stats = []
    timestamps_rank = {}
    for timestamp in item_stats:
        if timestamp["mod_rank"] not in timestamps_rank:
            timestamps_rank[timestamp["mod_rank"]] = []
        timestamps_rank[timestamp["mod_rank"]].append(
            [
                timestamp["avg_price"],
                timestamp["min_price"],
                timestamp["volume"],
            ]
        )
    for key, rank_stats in timestamps_rank.items():
        mod_stats.append(
            {
                "name": item,
                "mod_rank": key,
                "item_2d_average": sum([tmp[0] for tmp in rank_stats])
                / len(rank_stats),
                "lowest_2d_average": sum([tmp[1] for tmp in rank_stats])
                / len(rank_stats),
                "latest_average": rank_stats[0][0],
                "latest_lowest": rank_stats[0][1],
                "volume": sum([tmp[2] for tmp in rank_stats]),
            }
        )
    return mod_stats


def analyze_part(item, item_stats):
    """Method used to analyze a tradable item that isn't a mod."""

    return {
        "name": item,
        "item_2d_average": sum([timestamp["avg_price"] for timestamp in item_stats])
        / len(item_stats),
        "lowest_2d_average": sum([timestamp["min_price"] for timestamp in item_stats])
        / len(item_stats),
        "latest_average": item_stats[0]["avg_price"],
        "latest_lowest": item_stats[0]["min_price"],
        "volume": sum([timestamp["volume"] for timestamp in item_stats]),
    }


def comparator(order, item_stats, mode):
    """Checks if the item satisfies the conditions."""
    if mode:
        return order["platinum"] < (
            sum([c["platinum"] for c in item_stats[0:3]]) / 3 * 0.7
        )
    return order["platinum"] < item_stats["lowest_2d_average"] * 0.7


def print_deal(item_name, seller_name, price, quantity, approach, benefit, rank=None):
    """Simple print function."""
    if rank is not None:
        print(
            datetime.now().strftime("%H:%M:%S")
            + " item: "
            + item_name.replace("_", " ").title()
            + ", rank: "
            + str(rank)
            + ", price: "
            + str(price)
            + "p, seller: "
            + str(seller_name)
            + ", quantity: "
            + str(quantity)
            + " "
            + approach
            + " approach, normal value: "
            + str(benefit)
        )
    else:
        print(
            datetime.now().strftime("%H:%M:%S")
            + " item: "
            + item_name.replace("_", " ").title()
            + " price: "
            + str(price)
            + "p seller: "
            + str(seller_name)
            + " quantity: "
            + str(quantity)
            + " "
            + approach
            + " approach, normal value: "
            + str(benefit)
        )
    utils.wfm_string(
        item_name.replace("_", " ").title(),
        seller_name,
        price,
        rank,
    )

``````
------ src/scanners/endo_scanner.py ------
``````
"""Class used to scan the market in search of good endo/plat items"""

import json
import os
from datetime import datetime

import src.settings as settings
import src.utils as utils

# Endo value of each mod rarity
rarity_value = {
    "Common": 7678,
    "Uncommon": 15355,
    "Peculiar": 15355,
    "Rare": 23033,
    "Amalgam": 23033,
    "Galvanized": 23033,
    "Legendary": 30710,
}

# Sculpture list, with its associated value and rank
sculpture_list = [
    {"name": "Ayatan Anasa Sculpture", "value": 3450, "max_rank": 4},
    {"name": "Ayatan Kitha Sculpture", "value": 3000, "max_rank": 5},
    {"name": "Ayatan Orta Sculpture", "value": 2700, "max_rank": 4},
    {"name": "Ayatan Zambuka Sculpture", "value": 2600, "max_rank": 3},
    {"name": "Ayatan Hemakara Sculpture", "value": 2600, "max_rank": 3},
    {"name": "Ayatan Vaya Sculpture", "value": 1800, "max_rank": 3},
    {"name": "Ayatan Piv Sculpture", "value": 1725, "max_rank": 3},
    {"name": "Ayatan Velana Sculpture", "value": 1575, "max_rank": 3},
    {"name": "Ayatan Sah Sculpture", "value": 1500, "max_rank": 3},
    {"name": "Ayatan Ayr Sculpture", "value": 1425, "max_rank": 3},
]


def get_mods():
    """Method used to read the mod database"""

    with open("mods.json", encoding="utf8") as json_file:
        data = json.load(json_file)
        data = [
            x
            for x in data
            if "fusionLimit" in x and x["fusionLimit"] == 10 and x["rarity"] != "common"
        ]

        return [
            {"name": x["name"], "max_rank": x["fusionLimit"], "rarity": x["rarity"]}
            for x in data
        ]


def endo_scan(endo_limit=400):
    """Method used to scan the market using the mod database
    Calculates the endo/plat value of each item and writes it if
    it's above the user's endo_limit
    """
    os.chdir(settings.path + "\\config")
    print("The program will look for mods and ayatan sculptures to endo")
    print("Loading database")
    item_list = sculpture_list
    item_list.extend(get_mods())

    while True:
        for item in item_list:
            url = (
                "items/"
                + item["name"].replace(" ", "_").lower()
                + "/orders?include=item"
            )
            result = utils.get_wfm_request(url)
            if result:
                result = result["orders"]
                result = [
                    x
                    for x in result
                    if x["order_type"] == "sell"
                    and x["platform"] == "pc"
                    and x["mod_rank"] == item["max_rank"]
                    and x["user"]["status"] == "ingame"
                ]
                if not result:
                    continue
                order = sorted(result, key=lambda x: x["platinum"])[0]
                value = (
                    rarity_value[item["rarity"]] / order["platinum"]
                    if "rarity" in item
                    else item["value"] / order["platinum"]
                )
                if value > endo_limit:
                    print(
                        datetime.now().strftime("%H:%M:%S")
                        + " item: "
                        + item["name"]
                        + " endo/plat: "
                        + str(value)
                        + " price: "
                        + str(order["platinum"])
                        + " quantity: "
                        + str(order["quantity"])
                        + " seller: "
                        + order["user"]["ingame_name"]
                    )
                    utils.wfm_string(
                        item["name"],
                        order["user"]["ingame_name"],
                        order["platinum"],
                        order["mod_rank"],
                    )

``````
------ src/scanners/part_scanner.py ------
``````
import src.utils as utils
import json
import os
import random

""" This file is used to scan the market in order to find the best spare parts to buy
in order to sell them as sets for a profit."""


def part_scanner():
    """Main method"""
    item_dict = check_for_price_db()
    item_dict = {k: v for k, v in item_dict.items() if v["price"] != 9999}
    item_dict = get_diffs(item_dict)
    item_dict = dict(
        sorted(item_dict.items(), key=lambda item: item[1]["price_diff"], reverse=True)
    )
    print_items(item_dict)


def upvote_farmer(email, password):
    utils.login(email, password)
    item_dict = get_items()
    votes = vote_loop(item_dict)
    print("Upvoted " + str(votes) + " users")


def get_items():
    # if file exists, load it
    if os.path.isfile("config/items.json"):
        with open("config/items.json", "r") as file:
            return json.load(file)
    """Gets the items from wfm and returns a dict with the item name as key and a list of components as value"""
    item_list = utils.get_wfm_request("items")["items"]
    set_urls = [x["url_name"] for x in item_list if "set" in x["url_name"][-4:]]
    item_dict = {}

    print("Searching for " + str(len(set_urls)) + " sets, it will take some minutes.")

    for set_item in set_urls:
        req = utils.get_wfm_request("items/" + set_item)
        if req:
            item_dict[set_item] = {}
            item_dict[set_item]["components"] = req["item"]["items_in_set"]
    # store the item dict on a file called items.json
    with open("config/items.json", "w") as file:
        json.dump(item_dict, file)

    return item_dict


def check_for_price_db():
    """Checks if the price database exists, if it does, it loads it, otherwise it creates it"""
    if os.path.isfile("config/previous_prices.json"):
        with open("config/previous_prices.json", "r") as file:
            item_dict = json.load(file)
            item_dict = {k: v for k, v in item_dict.items() if v["price"] != 9999}
            # update only the most profitable items
            item_dict = get_diffs(item_dict)
            profit = dict(
                sorted(
                    item_dict.items(),
                    key=lambda item: item[1]["price_diff"],
                    reverse=True,
                )[:20]
            )

            profit = value_items(profit)
            for item, stats in profit.items():
                item_dict[item]["price_diff"] = stats["price_diff"]
                item_dict[item]["price"] = stats["price"]
    else:
        item_dict = value_items(get_items())
    with open("config/previous_prices.json", "w") as file:
        json.dump(item_dict, file)
    return item_dict


def value_items(item_dict):
    """Updates the value of each set in the dict"""
    for item, comps in item_dict.items():
        item_dict[item]["price"] = get_price(item)
        for i, compo in enumerate(comps["components"]):
            if "set_root" in compo and compo["set_root"] is True:
                item_dict[item]["components"][i]["price"] = 0
                continue
            quantity = compo["quantity_for_set"] if "quantity_for_set" in compo else 1
            item_dict[item]["components"][i]["price"] = (
                get_price(compo["url_name"]) * quantity
            )
            quantity = 0

    return item_dict


def get_names(item_dict):
    names = []
    for item_name, comps in item_dict.items():
        """Gets the names of sellers of buyers of orders of an item from wfm"""
        orders = utils.get_wfm_request("items/" + item_name + "/orders")
        if not orders or len(orders["orders"]) < 1:
            continue

        names.extend(
            [
                order["user"]["ingame_name"]
                for order in orders["orders"]
                if order["user"]["reputation"] > 1 and random.randint(0, 7) < 2
            ]
        )
    return names


def vote_loop(item_dict):
    i = 0
    e = 0
    for item_name, comps in item_dict.items():
        """Gets the names of sellers of buyers of orders of an item from wfm"""
        orders = utils.get_wfm_request("items/" + item_name + "/orders")
        if not orders or len(orders["orders"]) < 1:
            continue
        names = [
            order["user"]["ingame_name"]
            for order in orders["orders"]
            if order["user"]["reputation"] > 1 and random.randint(0, 7) < 2
        ]
        for name in names:
            if utils.upvote_wfm_user(name):
                i += 1
            else:
                e += 1

            if i > 15 or e > 3:
                break
    return i


def get_price(item_name):
    """Gets the price of an item from wfm"""
    orders = utils.get_wfm_request("items/" + item_name + "/orders")
    if not orders or len(orders["orders"]) < 1:
        return 9999
    orders = [
        order
        for order in orders["orders"]
        if order["order_type"] == "sell" and order["user"]["status"] == "ingame"
    ]
    if not orders:
        return 9999
    order = sorted(orders, key=lambda order: order["platinum"])[0]
    return order["platinum"]


def get_diffs(item_dict):
    """Gets the difference between the price of the set and the price of the parts"""
    for item, comps in item_dict.items():
        total = 0
        for component in comps["components"]:
            total += component["price"]
        item_dict[item]["price_diff"] = item_dict[item]["price"] - total
    return item_dict


def print_items(item_dict):
    """Prints the best sets to buy"""

    print("Best sets to buy parts of")
    i = 0
    for set_name, stats in item_dict.items():
        if i == 10:
            break
        i += 1
        print(
            "Item name: "
            + str(set_name.replace("_", " ").title())
            + ", Price Difference: "
            + str(stats["price_diff"])
            + ", Price: "
            + str(stats["price"])
        )

``````
------ src/scanners/relic_scanner.py ------
``````
"""Module used to scan the market in search of good relics to buy and sell."""

import os

import requests

import src.settings as settings
import src.utils as utils

item_dict = {}


def scan_relics():
    """Method used to retrieve the relics the market in search of good relics to buy and sell."""

    os.chdir(settings.path + "\\config")

    print("Updating relic database.")
    url = "https://drops.warframestat.us/data/relics.json"
    relic_list = requests.get(url, stream=True)
    if relic_list.status_code != 200:
        return
    relic_list = relic_list.json()["relics"]
    analyzed_relics = []

    with open("best_sell_relics.csv", "w+") as sell_relic:
        with open("best_buy_relics.csv", "w+") as buy_relic:
            # takes the intact and radiant relics and searches for its stats and orders
            relic_list = [
                relic for relic in relic_list if relic["state"] in ("Intact", "Radiant")
            ]
            print(
                "Analyzing "
                + str(len(relic_list))
                + " relics and "
                + str(len(relic_list) * 5)
                + " items."
            )
            for relic in relic_list:
                relic_stats = utils.get_wfm_request(
                    "items/"
                    + relic["tier"].lower()
                    + "_"
                    + relic["relicName"].replace(" ", "_").lower()
                    + "_relic/statistics"
                )
                relic_orders = utils.get_wfm_request(
                    "items/"
                    + relic["tier"].lower()
                    + "_"
                    + relic["relicName"].replace(" ", "_").lower()
                    + "_relic/orders"
                )

                if not relic_stats or (
                    relic_stats
                    and len(relic_stats["statistics_closed"]["90days"]) < 20
                    or not relic_orders
                ):
                    continue

                relic_orders = [
                    order
                    for order in relic_orders["orders"]
                    if order["subtype"] == relic["state"].lower()
                ]
                # gets the average value of each relic and updates the value of parts of the dict
                analyzed_relics.append(analyzeRelic(relic, relic_orders))
            # sorts the relics by real value removing empty ones
            analyzed_relics = [relic for relic in analyzed_relics if relic]
            analyzed_relics = sorted(
                analyzed_relics, key=lambda relic: (relic["value"] / relic["price"])
            )

            # writes the best relics to sell and buy
            print("Best relics to sell")
            for relic in analyzed_relics[:10]:
                print(
                    relic["name"]
                    + " Price: "
                    + str(relic["price"])
                    + " Value: "
                    + str(relic["value"])
                    + " Value/Price: "
                    + str(relic["value"] / relic["price"])
                )
                sell_relic.write(
                    relic["name"]
                    + " Price: "
                    + str(relic["price"])
                    + " Value: "
                    + str(relic["value"])
                    + " Value/Price: "
                    + str(relic["value"] / relic["price"])
                )
            print("\nBest relics to buy")
            for relic in analyzed_relics[-10:]:
                print(
                    relic["name"]
                    + " Price: "
                    + str(relic["price"])
                    + " Value: "
                    + str(relic["value"])
                    + " Value/Price: "
                    + str(relic["value"] / relic["price"])
                )
                buy_relic.write(
                    relic["name"]
                    + " Price: "
                    + str(relic["price"])
                    + " Value: "
                    + str(relic["value"])
                    + " Value/Price: "
                    + str(relic["value"] / relic["price"])
                )


def analyzeRelic(relic, relic_orders):
    """Method used to extract the real value of a relic and update its parts."""
    relic_orders = [
        order["platinum"]
        for order in relic_orders
        if order["subtype"] == relic["state"].lower()
        and order["order_type"] == "sell"
        and order["user"]["status"] == "ingame"
    ]
    if not relic_orders:
        return
    relic_orders.sort()
    analyzed_relic = {}
    analyzed_relic["name"] = (
        relic["tier"] + " " + relic["relicName"] + " " + relic["state"]
    )
    analyzed_relic["price"] = relic_orders[0]
    reward_values = []

    for reward in relic["rewards"]:
        reward["itemName"] = reward["itemName"].replace(" Blueprint", "")

        if "forma" in reward["itemName"]:
            reward_values.append(5 * reward["chance"] * 0.01)
            continue

        if reward["itemName"] not in item_dict:
            reward_data = utils.get_wfm_request(
                "items/" + reward["itemName"].replace(" ", "_").lower() + "/orders"
            )
            if reward_data:
                reward_orders = sorted(
                    [
                        order["platinum"]
                        for order in reward_data["orders"]
                        if order["order_type"] == "sell"
                        and order["user"]["status"] == "ingame"
                    ]
                )
                if reward_orders:
                    reward_values.append(reward_orders[0] * reward["chance"] * 0.01)
                    item_dict[reward["itemName"]] = reward_orders[0]
        else:
            reward_values.append(
                item_dict[reward["itemName"]] * reward["chance"] * 0.01
            )
    analyzed_relic["value"] = sum(reward_values)
    return analyzed_relic

``````
------ src/settings.py ------
``````
"""File used to prepare the data for the rest of the functions."""
import csv
import json
import os
import random
import shutil
from ast import literal_eval
from datetime import datetime

import src.riven_stuff.riven_rater as riven_rater

##################################################################################################
# settings
delete_previous_folder = True

# Determines the accuracy and speed of the search.
# 0 = fast, 1 = slow.
# fast search lasts for about a minute.
# it returns a low amount of rivens. used to search unrolleds for wanted weapons.

# slow search lasts for 30 minutes.
# it returns a great amount of rivens both for wanted weapons and unwanted ones.
# it mostly searches for godrolls and unrolleds of wanted weapons and unrolleds for unwanted ones.

search_type = False

# variables used to store specific search data from the user's input.
specific_search = False
specific_weapon = False
weapon_name = ""
weapon_stats = {}

################################################################################################
# global variables used around all the files

# creates a directory to place the results in.
path = os.getcwd()
folder_path = ""
wanted_path = ""
unwanted_path = ""

# lists used as database.
weapons = {}
stat_list = {}
combinations = {}
weights = {}
equations = {}
profiles = []

# User lists. can be manipulated to change search results.
# based on the weapons, stats and rolls that you want.
# you can make changes in the files in riven_search\\config
wanted_wp = []
wanted_unroll = []
wanted_rv = {}
positives = []


def init():
    """Calls for the initialization functions."""
    create_directory()
    load_data()


def create_directory():
    """Creates the multiple directories used to write data."""

    global path, folder_path, wanted_path, unwanted_path

    # gets the path to store the results in.
    folder_path = path + "\\Search results\\"
    if not os.path.exists(folder_path):
        os.mkdir(folder_path)
    os.chdir(folder_path)

    # creates a folder with the current date and time.
    date = datetime.today()
    folder = datetime(date.year, date.month, date.day).strftime("%d-%m-%y")
    folder_path = folder_path + folder

    if os.path.exists(folder_path):
        if delete_previous_folder:
            shutil.rmtree(folder_path)
        else:
            folder_path += "(" + str(random.randint(0, 9999)) + ")"
    os.mkdir(folder_path)

    # creates the wanted and unwanted folders along with the rating folders.
    wanted_path = folder_path + "\\Wanted weapons"
    unwanted_path = folder_path + "\\Unwanted weapons"
    for fpath in [wanted_path, unwanted_path]:
        os.mkdir(fpath)
        os.mkdir(fpath + "\\95-100. Godrolls")
        os.mkdir(fpath + "\\85-95. Good rivens")
        os.mkdir(fpath + "\\70-85. Decent rivens")
        os.mkdir(fpath + "\\60-70. Usable rivens")
        os.mkdir(fpath + "\\40-60. Bad rivens")
        os.mkdir(fpath + "\\Unrolleds")
        os.mkdir(fpath + "\\Trashcan")
        os.mkdir(fpath + "\\Wanted rolls")


def load_stats():
    """Loads the stats from the stats.csv file."""
    global stat_list
    with open("stat_values.csv", encoding="utf-8") as file:
        stat_list = list(csv.DictReader(file))
        for stat in stat_list:
            stat["rh_names"] = literal_eval(stat["rh_names"])
            for i in range(5):
                stat["value" + str(i)] = float(stat["value" + str(i)])
        file.close()


def load_roll_info():
    """Loads the combinations of decent rolls for each weapon type."""
    global combinations
    with open("wanted_rolls.json", encoding="utf-8") as file:
        combinations = json.load(file)
        file.close()


def load_specific_search():
    """Loads specific weapons and rolls the user wants to search for."""
    global wanted_rv, wanted_wp, wanted_unroll
    with open("specific_rolls.csv", encoding="utf-8") as file:
        wanted_rv = list(csv.DictReader(file))
        file.close()
    with open("wanted_weapons.json", encoding="utf-8") as file:
        wanted_wp = json.load(file)
        wanted_unroll = wanted_wp["wanted_unroll"]
        wanted_wp.pop("wanted_unroll")
        wanted_wp = list(wanted_wp.values())
        file.close()


def load_weapon_data():
    """Loads all the information related with weapons and its stats."""
    global weapons
    with open("weapons.json", encoding="utf-8") as file:
        weapons = json.loads(file.read())
        file.close()


def load_profile_data():
    """Loads the weighting profiles data for the scoring system."""
    global weights, equations, positives

    with open("rating_weights.json", encoding="utf-8") as file:
        weights = list(json.loads(file.read()).values())
        positives = [[] for i in range(0, 4)]
        positives.append(positives[0])

        for i, weapon_type in enumerate(weights):
            for use in weapon_type.values():
                for stat_name, stat_values in use.items():
                    if stat_values[0] < 10:
                        continue
                    if stat_name not in positives[i]:
                        positives[i].append(stat_name)
        file.close()

    with open("rating_equations.json", encoding="utf-8") as file:
        equations = list(json.loads(file.read()).values())
        file.close()


def load_data():
    """Calls for the loading functions."""

    os.chdir(path + "\\config")

    load_stats()
    load_roll_info()
    load_specific_search()
    load_weapon_data()
    load_profile_data()

    riven_rater.generate_profiles()

``````
------ src/url_creation.py ------
``````
"""File used to prepare the urls of the rivens, generating
    combinations of the possible stats and weapons"""
from itertools import combinations

import src.settings as settings
from src.utils import check_variant, get_weapon_type, translate_stat_wfm_rh


def data_creation():
    """Prepares the weapon list"""

    url_list = []

    # creates the url of generic rolls.
    url_list.extend(generic_urls())
    # creates the url of specific rolls
    url_list.extend(specific_urls())

    if settings.specific_weapon:
        # creates the url of a specific weapon search
        url_list.extend(weapon_urls(settings.weapon_name, settings.weapon_stats))

    if settings.search_type:
        # creates the url of generic rolls for wanted weapons.
        url_list.extend(wanted_urls())
        # creates the url of unrolleds
        url_list.extend(unrolled_urls())

    url_list = list(set(url_list))

    print(str(len(url_list)) + " possible riven combinations.")

    if settings.search_type:
        rm_list = [build_url_rh(extract_url_data(url)) for url in url_list]
        rm_list = list(filter(None, rm_list))
        return [url_list, rm_list]

    return url_list


def create_generic_url(stats=[], weapon_type=0):
    """Creates the url of a search with given stats."""
    if stats and "stat3" in stats:
        return [
            "auctions/search?type=riven&positive_stats="
            + stats["stat1"]
            + ","
            + stats["stat2"]
            + ","
            + stats["stat3"]
            + "&negative_stats=has&polarity=any&sort_by=price_asc"
        ]
    return add_positive(
        "auctions/search?type=riven&positive_stats="
        + stats["stat1"]
        + ","
        + stats["stat2"],
        weapon_type,
        stats,
    )


def create_specific_url(weapon, stats=[], unroll=False):
    """Used for specific rolls, given a weapon, it creates a url based on its stats,
    if no stats are given it creates a list of urls with generic godrolls.
    If the unroll parameter is True, it returns an unrolled url"""

    # specific case used to create the url of an unrolled fast.
    if unroll:
        return [
            "auctions/search?type=riven&weapon_url_name="
            + weapon
            + "&polarity=any&re_rolls_max=0&sort_by=price_asc"
        ]
    # if the wanted riven already has a stat list it either adds a
    # third positive or returns a list of urls for it.
    if stats:
        if "stat3" in stats and stats["stat3"] == stats["stat3"]:
            if "negative" in stats and len(stats["negative"]) > 0:
                return [
                    "auctions/search?type=riven&weapon_url_name="
                    + weapon
                    + "&positive_stats="
                    + stats["stat1"]
                    + ","
                    + stats["stat2"]
                    + ","
                    + stats["stat3"]
                    + "&negative_stats="
                    + stats["negative"]
                    + "&polarity=any&sort_by=price_asc"
                ]
            return [
                "auctions/search?type=riven&weapon_url_name="
                + weapon
                + "&positive_stats="
                + stats["stat1"]
                + ","
                + stats["stat2"]
                + ","
                + stats["stat3"]
                + "&negative_stats=has&polarity=any&sort_by=price_asc"
            ]
        if "negative" in stats and len(stats["negative"]) > 0:
            neg = (
                "&negative_stats=" + stats["negative"]
                if "negative" in stats
                else "&negative_stats=has"
            )
            return [
                url + neg
                for url in add_positive(
                    "auctions/search?type=riven&weapon_url_name="
                    + weapon
                    + "&positive_stats="
                    + stats["stat1"]
                    + ","
                    + stats["stat2"],
                    get_weapon_type(weapon),
                    stats,
                )
            ]
    return add_stats(weapon)


def extract_url_data(url):
    """Extracts the weapon and stats from a url."""

    weapon, stats, neg = None, None, None

    if "weapon_url_name" in url:
        weapon = url.split("weapon_url_name=")[1].split("&")[0]
    if "positive_stats" in url:
        stats = []
        sturl = url.split("positive_stats=")[1].split("&negative")[0]
        for stat in sturl.split(","):
            stats.append(stat)
    if "negative_stats" in url:
        neg = url.split("negative_stats=")[1].split("&")[0]
    return [stats, weapon, neg]


def build_url_rh(info):
    url = ""
    stats, weapon, neg = info
    if weapon:
        weapon_name = weapon.replace("_", " ").title()
        weapon_name = [w for w in settings.weapons if w["name"] == weapon_name][0][
            "internal_name"
        ]
        weapon_type = 1 if get_weapon_type(weapon_name) != 3 else 0
        url += weapon_name + "&interval=3 day&buffs="
        if stats:
            url += ",".join(
                [translate_stat_wfm_rh(stat, weapon_type) for stat in stats]
            )
        if neg:
            if neg != "has":
                url += "&curses=" + translate_stat_wfm_rh(neg, weapon_type)
            url += "&exact=1&needsCurse=1"
        else:
            url += "&exact=1&needsCurse=0"
        return url
    return None


def add_stats(weapon):
    """Used to create generic godrolls for a selected weapon."""

    weapon_type = get_weapon_type(weapon)
    res = set()
    for combination in combinations(settings.positives[weapon_type], 3):
        url = (
            "auctions/search?type=riven&weapon_url_name=" + weapon + "&positive_stats="
        )
        for comb in combination:
            url += comb + ","
        url = url[:-1] + "&negative_stats=has&polarity=any&sort_by=price_asc"
        res.add(url)
    return res


def add_positive(url, weapon_type, stats):
    """Adds specific 2 stat rivens to the url and also adds urls with a third positive."""

    result = [
        url + "," + stat + "&negative_stats=has&polarity=any&sort_by=price_asc"
        for stat in settings.positives[weapon_type]
        if stat not in stats.values()
    ]
    # good enough stats to have that aren't too detrimental to your riven.
    result = list(set(result))
    result.append(url)
    return result


def generic_urls():
    """It creates a list of urls with generic combos with no explicit weapon."""

    result = set()
    for i, weapon_type in enumerate(settings.combinations.values()):
        for weapon_comb in weapon_type:
            for url in create_generic_url(weapon_comb, i):
                result.add(url)
    return list(result)


def wanted_urls():
    """It creates a list of urls with generic godroll combos for wanted weapons.
    it provides better accuraccy."""

    result = set()
    for i, weapon_type in enumerate(settings.wanted_wp):
        for weapon in weapon_type:
            for url in create_specific_url(weapon):
                result.add(url)
            if check_variant(weapon):
                continue
            for weapon_t in settings.combinations.values():
                for weapon_comb in weapon_t:
                    for url in create_generic_url(weapon_comb, i):
                        result.add(url)
    return list(result)


def unrolled_urls():
    """It creates a list of urls for unrolleds of all the weapons."""

    result = set()
    for weapon in settings.wanted_unroll:
        for url in create_specific_url(weapon, [], True):
            result.add(url)
    return list(result)


def specific_urls():
    """It creates a list of urls for specific users inputted by the user in the files."""

    result = set()
    for weapon_comb in settings.wanted_rv:
        for url in create_specific_url(
            weapon_comb["weapon"],
            {
                "stat1": weapon_comb["stat1"],
                "stat2": weapon_comb["stat2"],
                "stat3": weapon_comb["stat3"],
                "negative": weapon_comb["negative"],
            },
            False,
        ):
            result.add(url)

    return list(result)


def weapon_urls(weapon, stats=[]):
    """Module used in the specific situation the user just want
    to search for combos for a single weapon.
    if a stat list is provided it searches just for it, otherwise
    it searches for generic godrolls."""

    result = set()
    if not stats:
        for url in add_stats(weapon):
            result.add(url)
    else:
        for url in create_specific_url(weapon, stats, False):
            result.add(url)
    return list(result)

``````
------ src/user_commands.py ------
``````
"""File used to redirect the user depending on which module he wants to use."""

from getpass import getpass


import src.settings as settings
import src.utils as utils


def choose_category():
    """Method used to ask the user for the mode to execute"""

    while True:
        print("Which mode do you want to use?:\n")
        print("1. Riven related actions\n")
        print("2. Market scan\n")
        print("3. Update the database\n")

        try:
            cat = int(input("Introduce a number: "))
        except ValueError:
            print("Write a number between 1 and 2. \n")
        if 0 < cat < 4:
            break
    if cat == 1:
        choose_riven_action()
    elif cat == 2:
        choose_scan_mode()
    elif cat == 3:
        import src.weapon_updater

        settings.load_data()
        src.weapon_updater.update_weapons()


def choose_riven_action():
    """Method used to ask the user for the mode to execute"""

    while True:
        print("Which mode do you want to use?:\n")
        print("1. Search for rivens\n")
        print("2. Relist your auctions\n")

        try:
            mode = int(input("Introduce a number: "))
        except ValueError:
            print("Write a number between 1 and 2. \n")
        if 0 < mode < 3:
            break
    if mode == 1:
        settings.init()
        define_search()
    elif mode == 2:
        relist_auctions()


def choose_scan_mode():
    """Method used to ask the user for the mode to execute"""

    while True:
        print("Which mode do you want to use?:\n")
        print("1. Find the best ducat/plat items and sellers\n")
        print("2. Scan the market for deals\n")
        print("3. Find the best spare parts to sell in sets\n")
        print("4. Find the best endo/plat mods\n")
        print("5. Scan the best relics to buy and sell\n")
        print("6. Search for your buy orders\n")
        print("7. Autofarm reputation.\n")

        try:
            mode = int(input("Introduce a number: "))
        except ValueError:
            print("Write a number between 1 and 2. \n")
        if 0 < mode < 9:
            break
    if mode == 1:
        from src.ducanator import use_ducanator

        use_ducanator(ask_for_ducat_scan())
    elif mode == 2:
        scan_for_deals()
    elif mode == 3:
        import src.scanners.part_scanner as part_scanner

        part_scanner.part_scanner()
    elif mode == 4:
        endo_scan()
    elif mode == 5:
        import src.scanners.relic_scanner as relic_scanner

        relic_scanner.scan_relics()
    elif mode == 6:
        scan_buys()
    elif mode == 7:
        import src.scanners.part_scanner as part_scanner

        email, password = ask_for_email(), ask_for_pass()
        part_scanner.upvote_farmer(email, password)


def ask_for_email():
    """Asks the user for its wfm email"""

    while True:
        try:
            email = str(
                input(
                    "Introduce the email associated to your warframe.market account: "
                )
            )
        except ValueError:
            print("An error happened when reading your email, please try again. \n")
        if email:
            return email


def ask_for_pass():
    """Asks the user for its wfm password"""

    while True:
        try:
            password = str(getpass("Introduce your password: "))
        except ValueError:
            print("An error happened when reading your password, please try again. \n")
        if password:
            return password


def ask_for_name():
    """Asks the user for its wfm username"""

    while True:
        try:
            name = str(input("Introduce your profile name: "))
        except ValueError:
            print(
                "An error happened when reading your profile name, please try again. \n"
            )
        if name:
            return name


def relist_auctions():
    """Asks the user about its options for relisting"""

    import src.riven_stuff.riven_relisting

    while True:
        print(
            "Do you want to relist all your auctions or only the ones without bids?:\n"
        )
        print("1. All the auctions\n")
        print("2. Only the ones without bid\n")
        try:
            bids = int(input("Introduce a number: "))
        except ValueError:
            print("Write a number between 1 and 2. \n")
        if 0 < bids < 3:
            break

    relist_type = bids == 2
    print("Introduce your warframe.market login credentials:\n")
    email = ask_for_email()
    password = ask_for_pass()
    utils.login(email, password)
    src.riven_stuff.riven_relisting.relist_auctions(relist_type)


# asks the user which kind of search he wants to do.
def define_search():
    """Defines the type of search the user wants to use"""

    while True:
        print("Which search do you want to use?:\n")
        print(
            "1. Fast search (low amount of rivens. specific rolls, unrolleds for wanted weapons, generic godrolls)\n"
        )
        print(
            "2. Slow search (high amount of rivens. will also search for godrolls for the wanted weapons)\n"
        )
        print(
            "3. Specific search (only searches for a weapon or your specific combinations)\n"
        )
        try:
            search_type = int(input("Introduce a number\n"))
        except ValueError:
            print("Write a number between 1 and 5. \n")
        if 0 < search_type < 6:
            break

    if search_type == 2:
        settings.search_type = True
    elif search_type == 3:
        specific_search()
    riv_search()


def specific_search():
    """Gets data needed to execute an specific search"""

    while True:
        print("Do you want to search for a specific weapon or your specific combos? \n")
        print("1. A specific weapon\n")
        print("2. My specific combos\n")
        try:
            specific = int(input())
        except ValueError:
            print("Write a number between 1 and 2\n")
        if specific == 1:
            settings.specific_weapon = True
            weapon_combo()
            break
        if specific == 2:
            settings.specific_search = True
            break


def weapon_combo():
    """Asks the user if he wants a broad search with generic godroll combinations
    or a specific one for the selected weapon."""

    while True:
        try:
            weapon_name = str(
                input("Which weapon do you want to search? write a non variant name\n")
            )
        except ValueError:
            print("Write the name of the weapon you want to search for \n")
        weapon_name = weapon_name.lower().replace(" ", "_")
        if (
            weapon_name in settings.weapons[utils.get_weapon_type(weapon_name)].keys()
            and utils.check_variant(weapon_name) is False
        ):
            settings.weapon_name = weapon_name
            break
        print("Name not valid")

    while True:
        print("Do you want the riven to have specific stats or a generic combo?\n")
        print("1. Specific stats\n")
        print("2. Generic combo\n")
        try:
            stats_type = int(input())
        except ValueError:
            print("Introduce a number between 1 and 2 \n")
        if stats_type == 1:
            ask_for_stats()
            break
        if stats_type == 2:
            break


def ask_for_stats():
    """Asks for the stats to search for the selected weapon.
    A restart option should be added in case the user makes a mistake"""

    print("The list of stats: \n")
    stat_list = [value["name"] for value in settings.stat_list.values()]
    order = ["first positive", "second positive", "third positive", "negative"]
    stat_name = ["stat1", "stat2", "stat3", "negative"]
    thirdpos, neg = 0, 0

    for i, stat in enumerate(stat_list):
        print(str(i) + ". " + stat)
    stat_list = list(settings.stat_list.keys())
    for i in range(4):
        if i == 2:
            while thirdpos not in (1, 2):
                print("Do you want a third positive? \n")
                print("1. Yes")
                print("2. No")
                try:
                    thirdpos = int(input())
                except ValueError:
                    print("Introduce a number between 1 and 2")
                if thirdpos in (1, 2):
                    break
            if thirdpos == 2:
                continue
        if i == 3:
            while neg not in (1, 2):
                print("Do you want a negative? \n")
                print("1. Yes")
                print("2. No")
                try:
                    neg = int(input())
                except ValueError:
                    print("Introduce a number between 1 and 2")
            if neg == 2:
                break
        try:
            statnum = int(input("Write the number of the " + order[i] + "\n"))
        except ValueError:
            print("Introduce a number between 1 and " + str(len(stat_list)))
        if statnum < len(stat_list):
            settings.weapon_stats[stat_name[i]] = (
                stat_list[statnum].lower().replace(" ", "_")
            )


def ask_for_ducat_scan():
    """Asks the user about his ducat limit and the scanning"""

    print(
        "The program will scan the market for items with a ducat value higher than your limit.\n"
    )
    print(
        "At the end of the scan, it'll provide you with a list of the best sellers to buy off the market.\n"
    )
    while True:
        print("Write the ducats/plat ratio you want to search for.\n")
        try:
            ducat_ratio = float(input())
        except ValueError:
            print("Introduce a number between 1 and 2")
        if ducat_ratio >= 0:
            return ducat_ratio


def riv_search():
    """Process used to search for rivens, step by step"""
    import src.request_processor
    import src.riven_stuff.riven_requests
    import src.url_creation

    # gets the urls for the searches we have to do.
    res = src.url_creation.data_creation()
    # requests said searches to the server and returns them.
    res = src.riven_stuff.riven_requests.mass_riven_request(res)
    # processes the requests and converts them into riven objects.
    res = src.request_processor.process_data(res).values()
    # sorts them by price.
    res = sorted(res, key=lambda riven: (riven.buyout_price, riven.real_value))
    # exports them into txt files.
    src.request_processor.export_txt(res)

    print("Rivens found: " + str(len(res)))


def scan_for_deals():
    """Searches for deals in the market"""
    import src.scanners.deal_scanner as deal_scanner

    deal_scanner.deal_finder()


def scan_buys():
    """Searches for good prices the user wants"""
    import src.scanners.buy_scanner as buy_scanner

    buy_scanner.scan_buys(ask_for_name())


def endo_scan():
    """Searches for good endo/plat deals in the market"""
    import src.scanners.endo_scanner as endo_scanner

    while True:
        print(
            "If the program finds an item with a good endo/plat ratio it will save it and show a message."
        )
        print("Type the endo/plat to look for. (default 400)")
        try:
            endo = int(input())
        except ValueError:
            print("Introduce a number between 1 and 2")
        if endo > 0:
            endo_scanner.endo_scan(endo)

``````
------ src/utils.py ------
``````
"""Common methods used by all modules"""

import json
import re

import random

import requests
from ratelimit import limits, sleep_and_retry

import src.settings as settings

jwt_token = None

headerss = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
    "Accept": "application/json",
    "Accept-Language": "es-ES,es;q=0.8,en-US;q=0.5,en;q=0.3",
    "Accept-Encoding": "gzip, deflate, br",
    "Referer": "https://warframe.market/",
    "content-type": "application/json",
    "language": "en",
    "platform": "pc",
    "Origin": "https://warframe.market",
    "DNT": "1",
    "Connection": "keep-alive",
    "Sec-Fetch-Dest": "empty",
    "Sec-Fetch-Mode": "cors",
    "Sec-Fetch-Site": "same-site",
    "TE": "trailers",
}

cookies = {
    "language": "en",
    "sessionid": "yoursessionid",
    "csrftoken": "yourtoken",
}
headers = {
    "x-csrftoken": "yourtoken",
    "Connection": "keep-alive",
}
params = {
    "interval": "7 day",
}

ingame_name = None
WFM_API = "https://api.warframe.market/v1/"
RH_API = "https://rivenhunter.com/api/v1/rivensearch/?itemtype="
session = requests.Session()


@sleep_and_retry
# @limits(calls=3, period=1.2)
def get_wfm_request(url):
    """Executes a wfm query"""
    request = session.get(WFM_API + url, stream=True, headers=headerss)
    if request.status_code == 200:
        return request.json()["payload"]
    return None


@sleep_and_retry
def get_rh_request(url):
    """Executes a rh query"""
    request = session.get(
        RH_API + url, params=params, cookies=cookies, headers=headers
    ).json()
    if len(request) != 0 and len(request["data"]) != 0:
        return request["data"]
    return None


def get_weapon(weapon_name):
    """Returns the weapon data given a name"""
    weapon_name = weapon_name.replace("_", " ").title()
    weapon = [x for x in settings.weapons if x["name"] == weapon_name]
    if len(weapon) == 0:
        return None
    return weapon[0]


def get_weapon_type(weapon_name):
    """Returns the weapon type given the name."""
    weapon = get_weapon(weapon_name)
    if weapon is None:
        return 3
    return weapon["weapon_type"]


def check_variant(weapon_name):
    """Returns if the weapon is a variant given the name."""
    weapon_name = weapon_name.replace("_", " ").title()
    family = [w for w in settings.weapons if w["name"] == weapon_name][0]["family"]
    if family == weapon_name:
        return False
    return True


def scale_range(actual, actual_min, actual_max, wanted_min, wanted_max):
    """Scales a value from within a range to another range"""

    return (wanted_max - wanted_min) * (
        (actual - actual_min) / (actual_max - actual_min)
    ) + wanted_min


def compare_stats(little_list, big_list):
    """Returns if the first list is a subset of the second one."""

    return set(little_list).issubset(big_list)


def login(
    user_email: str, user_password: str, platform: str = "pc", language: str = "en"
):
    """Login function, copied from the wfm discord."""

    global jwt_token, ingame_name, headers
    headers = {
        "content-type": "application/json; utf-8",
        "accept": "application/json",
        "authorization": "jwt",
        "platform": platform,
        "language": language,
    }
    content = {"email": user_email, "password": user_password, "auth_type": "header"}
    response = requests.post(
        f"{WFM_API}/auth/signin", data=json.dumps(content), headers=headers
    )
    if response.status_code != 200:
        print("couldn't login with those credentials")
        return None
    print("logged succesfully")
    ingame_name = response.json()["payload"]["user"]["ingame_name"]
    jwt_token = response.headers["authorization"]
    headers["authorization"] = jwt_token
    headers["auth_type"] = "header"
    return [headers, ingame_name, jwt_token]


def get_current_auctions():
    """Returns the current auctions of the user."""
    if ingame_name:
        url = "profile/" + ingame_name + "/auctions"
        result = get_wfm_request(url)
        if result:
            return result["auctions"]
    return None


@sleep_and_retry
@limits(calls=3, period=1.2)
def upvote_wfm_user(user_name):
    # We'll do a positive review of the user, the URL is like this https://api.warframe.market/v1/profile/{user_name}/review
    # But we have to add the fields review_type = 1 and text = "Good seller"
    url = "profile/" + user_name + "/review"
    texts = [
        "Nice trader!",
        "Fast trader",
        "Good prices",
        "Generous trader",
        "Good trader",
        "Nice trader",
        "+1",
    ]
    data = {"review_type": 1, "text": random.choice(texts)}
    result = requests.post(WFM_API + url, data=json.dumps(data), headers=headers)
    if result.status_code == 200:
        print("upvoted " + user_name)
        return True
    return False


def wfm_string(item_name, seller_name, price, rank=None):
    """Writes a message that can be copied directly as if the user came from wfm"""
    if rank is not None:
        print(
            "/w "
            + str(seller_name)
            + " hi! i want to buy: "
            + item_name.title().replace("_", " ")
            + " (rank "
            + str(rank)
            + ") for "
            + str(price)
            + " platinum. (warframe.market)\n"
        )
    else:
        print(
            "/w "
            + str(seller_name)
            + " hi! i want to buy: "
            + item_name.title().replace("_", " ")
            + " for "
            + str(price)
            + " platinum. (warframe.market)\n"
        )


def translate_stat_rh_wfm(stat):
    return [x["wfm_name"] for x in settings.stat_list if stat in x["rh_names"]][0]


def translate_stat_wfm_rh(stat, type):
    return [x["rh_names"][type] for x in settings.stat_list if stat == x["wfm_name"]][0]


def translate_stats_wiki_wfm(stat):
    res = [stat2 for stat2 in settings.stat_list if stat2["wiki_name"] == stat]
    if len(res) != 0:
        return str(res[0]["wfm_name"])
    return str(re.sub("([a-z0-9])([A-Z])", r"\1_\2", stat).lower())

``````
------ src/weapon_updater.py ------
``````
"""File used to update the weapon list used on the rest of modules.
   Must be greatly improved in the future, by not depending on WFCD's data."""

from io import StringIO

import pandas as pd
import requests
from numpy import NaN

import src.utils as utils


def call_api(slot):
    """Return a table of weapons of a specific slot using MediaWikiAPI."""
    url = "https://warframe.fandom.com/api.php"
    question = (
        "local CSV = require('Module:Weapons/csv') print(CSV._csvGunComparisonTable('{}'))".format(
            slot
        )
        if slot != "Melee"
        else "local CSV = require('Module:Weapons/csv') print(CSV._csvMeleeComparisonTable())"
    )

    params = {
        "action": "scribunto-console",
        "format": "json",
        "title": "Module:Weapons/csv",
        "content": "",
        "question": question,
        "clear": 1,
    }

    response = requests.get(url, params=params).json()["print"]
    response = (
        response.replace("/\\n/gm", "\n")
        .replace("/\\t/gm", "\t")
        .replace('/\\"/gm', '"')
        .split("calculating FireRate from Burst stats for Gorgon Wraith")[-1]
        .split("\n", 1)[1]
        # .split("Name,")[1]
        # .split("+1\n", 1)[1]
    )

    return response


def correct_kitguns(weapons):
    """Corrects the kitgun stats."""
    with open("kitguns.csv", encoding="utf-8") as file:
        exceptions = pd.read_csv(file)
        for name, type in zip(exceptions["name"], exceptions["slot"]):
            for weapon in weapons["name"]:
                if name.lower() in weapon.lower() and type.lower() in weapon.lower():
                    for column in exceptions.columns.tolist():
                        if column not in ["name", "slot", "Unnamed: 0"]:
                            weapons.loc[
                                weapons["name"] == weapon, column
                            ] = exceptions.loc[
                                exceptions["name"] == name, column
                            ].values[
                                0
                            ]
                    break
        for name, type in zip(exceptions["name"], exceptions["slot"]):
            for weapon in weapons["name"]:
                if name.lower() in weapon.lower() and type.lower() in weapon.lower():
                    weapons.loc[weapons["name"] == weapon, "name"] = name.title()
    file.close()
    return weapons


def update_weapons():
    """Updates the weapons.json file."""
    slots = [
        "Primary",
        "Secondary",
        "Melee",
        "Robotic",
        "Arch-Gun",
        "Arch-Gun (Atmosphere)",
        "Amp",
    ]
    print("Updating weapon database.")
    weapons = pd.concat(
        [pd.read_csv(StringIO(call_api(slot))) for slot in slots]
    ).reset_index(drop=True)
    weapons = weapons.loc[weapons.groupby("Name")["TotalDamage"].idxmax()]
    weapons["TotalDamage"] = weapons["TotalDamage"] + 0.1
    slots.insert(1, "Shotgun")
    alternatives = slots[5:]
    slots = slots[:5]
    column_list = [utils.translate_stats_wiki_wfm(x) for x in weapons.columns.tolist()]
    weapons.rename(
        columns=dict(zip(weapons.columns.tolist(), column_list)), inplace=True
    )

    # remove weapons with no disposition
    weapons = weapons[weapons["disposition"] != NaN]
    weapons = correct_kitguns(weapons)
    weapons["weapon_type"] = weapons.apply(
        lambda x: 1
        if (x["class"] == "Shotgun" and x["slot"] == "Primary")
        else (
            slots.index(x["slot"])
            if x["slot"] in slots
            else (5 if x["slot"] in alternatives else (3 if x["class"] != 4 else 4))
        ),
        axis=1,
    )

    weapons = weapons.loc[:, ~weapons.columns.duplicated(keep="last")]
    weapons.to_json("weapons.json", orient="records")
    print("Weapon database updated.")

``````
------ deal_finder.py ------
``````

import src.user_commands as user_commands

user_commands.choose_category()