Esempio n. 1
0
def founder_endings(
    requester: nsapi.NSRequester,
    since: t.Optional[int] = None,
    before: t.Optional[int] = None,
    referenceDate: datetime.date = None,
) -> t.Iterable[Ending]:
    """Returns the founder endings in the given timestamp frame.

    `before` defaults to now,
    and `since` defaults to 24 hours ago.

    Uses the given referenceDate to retrive a region dump to cross-reference
    with endings with region founders (defaults to most recent dump).
    """

    endings = retrieve_endings(requester, since, before)
    # convert endings to nation: region map
    endingRegions = {
        nsapi.clean_format(ending.nation): ending.region
        for ending in endings
    }

    # cross reference with region dump
    founderEndings = (
        Ending(
            nation=nsapi.clean_format(region.founder),
            region=endingRegions[nsapi.clean_format(region.founder)],
        ) for region in requester.dumpManager().regions(date=referenceDate)
        if nsapi.clean_format(region.founder) in endingRegions.keys())

    return founderEndings
Esempio n. 2
0
def check_roster(
        requester: nsapi.NSRequester,
        nations: t.Mapping[str, t.Collection[str]]) -> t.Mapping[str, Result]:
    """Checks for the WA of each nation.

    Checks the main nation, and the list of puppets.
    If none are in the WA, prompts for a new nation.
    """
    output = {}
    for nation, puppets in nations.items():
        # Find current WA nation(s): SHOULD NOT BE PLURAL, WARN IF SO
        WAs = []
        if is_wa(requester, nation):
            WAs.append(nation)
        WAs.extend(puppet for puppet in puppets if is_wa(requester, puppet))
        # Warn if len > 1
        if len(WAs) > 1:
            print(f"WARNING: {nation} has multiple WA nations: {WAs}")
        # Construct Result object
        if WAs:
            result = Result(nsapi.clean_format(WAs[0]))
        else:
            result = Result(None)
        output[nsapi.clean_format(nation)] = result
    return output
Esempio n. 3
0
def count_change(
    requester: nsapi.NSRequester,
    nations: t.Collection[str],
    start: datetime.date,
    end: datetime.date,
) -> t.Tuple[t.Mapping[str, int], t.Iterable[str]]:
    """For each nation provided, retrieves the number of issues
    they have answered between the two dates.

    The returned mapping contains .clean_format version of nation names.
    """

    starting = {}
    ending = {}

    invalid = []

    # Get the starting number of issues
    for nation in requester.dumpManager().nations(date=start):
        # Check if either normal or clean name was provided
        if nation.name in nations or nsapi.clean_format(
                nation.name) in nations:
            starting[nsapi.clean_format(nation.name)] = nation.issuesAnswered

    # Get the ending number of issues
    for nation in requester.dumpManager().nations(date=end):
        # Check if either normal or clean name was provided
        if nation.name in nations or nsapi.clean_format(
                nation.name) in nations:
            ending[nsapi.clean_format(nation.name)] = nation.issuesAnswered

    # Check/make sure both starting/ending have all the nations
    # (e.g. differences may occur when a nation ceased to exist, was founded, etc)

    # If a nation didnt exist on the start date, it started with 0 answered.
    # If a nation didnt exist on the end date, it must have CTE
    # there is not really an easy way of finding the ending number of issues answered,
    # so we cant really calculate.
    for nationName in nations:
        if nationName not in starting:
            starting[nationName] = 0
        if nationName not in ending:
            starting.pop(nationName)
            # track the invalid nations
            invalid.append(nationName)

    # Calculate the difference between end and start for each nation in
    # starting, which should have identical keys to ending so it doesnt matter
    # which one we iterate through
    # Standardize nation name formatting
    delta = {
        nsapi.clean_format(nationName):
        ending[nationName] - starting[nationName]
        for nationName in starting
    }

    return delta, invalid
Esempio n. 4
0
def read_plain(lines: t.Iterable[str]) -> t.Mapping[str, str]:
    """Converts each line into a nation: wa pair"""
    output = {}
    for line in lines:
        # Try to find the two matches
        waMatch = re.search(
            r'\[a href="https://www.nationstates.net/nation=(.*?)"\]', line
        )
        nationMatch = re.search(r"\](.*?)\[", line)
        # If both succeed add the pair
        if waMatch and nationMatch:
            output[nsapi.clean_format(nationMatch[1])] = nsapi.clean_format(waMatch[1])
    return output
Esempio n. 5
0
def filter_regions(
    requester: nsapi.NSRequester, exclude: t.Iterable[str], *tags: str
) -> t.Collection[str]:
    """Returns a collection of regions retrieved from the world region tag api,
    after filtering is applied.
    """

    excludeSet = set(nsapi.clean_format(exclusion) for exclusion in exclude)

    return [
        region
        for region in requester.world().regions_by_tag(*tags)
        if nsapi.clean_format(region) not in excludeSet
    ]
Esempio n. 6
0
def report_status(
        requester: nsapi.NSRequester,
        table: t.Iterable[t.Sequence[str]]) -> t.Iterable[t.Sequence[str]]:
    """Parses a table (such as sourced from csv), and returns columns reporting nation status.

    The returned iterable represents a iterable of rows in the same order as input.
    """

    iterator = iter(table)
    header = next(iterator)

    # Expected headers:
    # username, forum, nation
    # use username as primary key
    # usernameIndex = header.index("username")
    # forumIndex = header.index("forum")
    nationIndex = header.index("nation")

    # define output headers
    output = [("link", "cte", "region", "wa", "label")]

    link = "https://www.nationstates.net/nation={}"

    for row in iterator:
        nation = row[nationIndex]
        status = retrieve_status(requester, nation)
        output.append((
            link.format(nsapi.clean_format(nation)),
            str(status.cte),
            str(status.region),
            str(status.wa),
            xki_label(status),
        ))

    return output
Esempio n. 7
0
def _get_forum_names() -> t.Mapping[str, str]:
    """Returns a mapping of main nation names to forum usernames.

    If the main nation does not have a forum account, it will not be in the map.

    This method only provides nations part of the XKI Card Coop program.

    Guarenteed to return cleaned format of nation names.
    """
    source = (
        "https://docs.google.com/spreadsheets/d/e/2PACX-1vSem15AVLXgdjxWBZOnWRFnF6NwkY0gVKPYI8"
        "aWuHJzlbyILBL3o1F5GK1hSK3iiBlXLIZBI5jdpkVr/pub?gid=1835258219&single=true&output=tsv"
    )

    logging.info("Retrieving nation-forum mappings")

    text = requests.get(source).text
    # 2D table: each first-level element is a row,
    # each row contains 1st element main nation and
    # optionally 2nd forum username.
    # If there is no username, the second element should be "", which is falsy
    table = [line.split("\t") for line in text.split("\r\n")]

    usernames = {nsapi.clean_format(row[0]): row[1] for row in table if row[1]}

    logging.info("Retrieved nation-forum mappings")

    return usernames
Esempio n. 8
0
def _get_forum_names() -> t.Mapping[str, str]:
    """Returns a mapping of main nation names to forum usernames.

    If the main nation does not have a forum account, it will not be in the map.

    This method only provides nations part of the XKI Card Coop program.

    Guarenteed to return cleaned format of nation names.
    """
    source = FORUM_SPREADSHEET

    logger.info("Retrieving nation-forum mappings")

    text = requests.get(source).text
    # 2D table: each first-level element is a row,
    # each row contains 1st element main nation and
    # optionally 2nd forum username.
    # If there is no username, the second element should be "", which is falsy
    table = [line.split("\t") for line in text.split("\r\n")]

    usernames = {nsapi.clean_format(row[0]): row[1] for row in table if row[1]}

    logger.info("Retrieved nation-forum mappings")

    return usernames
Esempio n. 9
0
def unendorsed_nations(requester: nsapi.NSRequester,
                       endorser: str) -> Tuple[str, Iterable[str]]:
    """Finds all WA members of the nation's region who have not been endorsed
    Returns a tuple containing the region, and a iterable of unendorsed nations
    """

    # Collect region
    region = requester.nation(endorser).shard("region")

    # Load downloaded nation file
    # Pack into conversion generator to simplify transformations
    nationDump = requester.dumpManager().nations()

    # Pull all nations in the region that are WA members
    # Use generator because we dont need to generate a list that is never used
    logger.info("Collecting %s WA Members", region)
    waMembers = (
        nation for nation in nationDump
        if nation.region == region and nation.WAStatus.startswith("WA"))

    # Pull nations who are not endorsed
    logger.info("Collecting WA members who have not been endorsed")
    nonendorsed = (
        # Save name string, converting to lowercase, underscore format
        nsapi.clean_format(nation.name) for nation in waMembers
        # Check if unendorsed by checking endorsements
        if endorser not in nation.endorsements)

    return (region, nonendorsed)
Esempio n. 10
0
def get_nations_by_region(
    requester: nsapi.NSRequester, regions: t.Iterable[str]
) -> t.Mapping[str, t.Iterable[str]]:
    """Returns an mapping from region to all residents of that region.
    """

    regionSet = set(nsapi.clean_format(string) for string in regions)

    output: t.Dict[str, t.List[str]] = {}

    for region in requester.dumpManager().regions():
        if nsapi.clean_format(region.name) in regionSet:
            output[nsapi.clean_format(region.name)] = [
                nsapi.clean_format(name) for name in region.nations
            ]

    return output
Esempio n. 11
0
def retrieve_status(requester: nsapi.NSRequester, nation: str) -> Status:
    """Retrieves info on the nation provided."""

    # Check region, wa, and cte
    try:
        shard_info = requester.nation(nation).shards("region", "wa")
    except nsapi.ResourceError:
        return Status(
            nsapi.clean_format(nation),
            True,
            None,
            None,
        )
    else:
        return Status(
            nsapi.clean_format(nation),
            False,
            shard_info["region"],
            shard_info["unstatus"],
        )
Esempio n. 12
0
def deployed(
    requester: nsapi.NSRequester,
    lead: str,
    roster: t.Mapping[str, t.Iterable[str]],
) -> t.Collection[str]:
    """Determine who are deployed and endorsing the lead."""
    # Obtain endorsement list of lead
    endos = map(nsapi.clean_format, requester.nation(lead).endorsements())
    # Invert roster into puppet -> main form
    owners = {
        nsapi.clean_format(switcher): main
        for main, switchers in roster.items() for switcher in switchers
    }
    # Also include main nations
    owners.update({main: main for main in roster.keys()})
    # Return owners who have a nation endoing lead
    return [owners[nation] for nation in endos if nation in owners]
Esempio n. 13
0
def main() -> None:
    """Main function"""

    # Create requester
    requester = nsapi.NSRequester(config.userAgent)

    print("Provide a data file to work through.")
    print(
        "Ideally, the file should be a csv-type text format, with each row in this format:"
    )
    print("<card_link>,<sender>,<receiver>,<sender_password>")
    print(
        "e.g.: 'https://www.nationstates.net/page=deck/card=926511/season=1,hn67,hn67_ii,aPassword"
    )
    print(
        "Technically, only the 'card=num/season=num' part of the link is required."
    )
    print(
        "Also, the password is only required for the first occurence of a nation."
    )
    print(
        "If your password contains a comma (','), it should be enclosed with double quotes (\")."
    )
    print(
        "The path to the file is relative to this script. If the file is in the same directory,"
    )
    print("just enter the name (e.g. 'gifts.csv')")
    dataPath = input("Data file: ")

    print("\nInstead of saving your password in the file,"
          " you can alternatively provide an API autologin,")
    print("which is a encrypted by NationStates version.")
    print("Enter 'yes'/'y' to interpret the passwords as autologins,"
          " or 'no' (or anything else) to not.")
    autologinInput = input("Interpret password fields as autologin? ").lower()
    if autologinInput in ("yes", "y"):
        autologin = True
        print("Interpreting password field as autologins.")
    else:
        autologin = False

    # We save Nation objects so that they can all use the same Auth, i.e. pin
    nations: MutableMapping[str, nsapi.Nation] = {}

    with open(nsapi.absolute_path(dataPath), "r", newline="") as file:
        csvReader = csv.reader(file)
        for row in csvReader:
            try:
                # We need to prepare or find the nation object first (of the sender)
                nation = nsapi.clean_format(row[1])
                if nation not in nations:
                    nations[nation] = requester.nation(nation)
                    # Update the nation auth using given password (or autologin)
                    # Autologin is True if the passwords are actually autologins
                    if autologin:
                        nations[nation].login(row[3])
                    else:
                        nations[nation].auth = nsapi.Auth(password=row[3])
                # Now we can delegate to the function
                send_card(link=row[0], sender=nations[nation], receiver=row[2])
                print(f"Sent {row[0]} from {nation} to {row[2]}")
            # Broad error cause we want to catch anything, so the whole
            # program doesnt crash. logs the message
            except Exception as exception:  # pylint: disable=broad-except
                print("Failed to send a card. Error:", exception)
Esempio n. 14
0
def main() -> None:
    """Main method"""

    # TODO add type checking into the arguments?
    # would possibly provide better error messages

    parser = argparse.ArgumentParser(
        description="Count the issues answered by a set of nations.")

    subparsers = parser.add_subparsers(
        help="The possible modes, rerun with [mode] -h for more info.",
        dest="sub",
    )

    datesParser = subparsers.add_parser("dates")

    datesParser.add_argument("start",
                             help="The start date, in YYYY-MM-DD format.")

    datesParser.add_argument("end", help="The end date, in YYYY-MM-DD format.")

    datesParser.add_argument(
        "-m",
        "--month",
        help="The month to use for a report. Defaults to month of end date.",
        default=None,
    )

    monthParser = subparsers.add_parser("month")

    monthParser.add_argument(
        "month",
        help=("The month to count across, in YYYY-MM format. "
              "Compares issue counts from YYYY-MM-01 to (MM+1)-01."),
    )

    # monthParser.add_argument(
    parser.add_argument(
        "-r",
        "--report",
        action="store_true",
        help=("Create output in report mode. Produces a formatted file in "
              "Issue Payout Reports folder for the given month. "
              "The report is labelled using the month of the starting date, "
              "unless specified by month parameter."),
    )

    # XKI Puppet List
    default_source = (
        "https://docs.google.com/spreadsheets/d/e/2PACX-1vSem15AVLXgdjxWBZOnWRFnF6NwkY0gVKPYI8"
        "aWuHJzlbyILBL3o1F5GK1hSK3iiBlXLIZBI5jdpkVr/pub?gid=1588413756&single=true&output=tsv"
    )

    parser.add_argument(
        "-s",
        "--source",
        help=("""URL or file name to retrieve nation list from. Expects either
            one or two nations seperated by a tab per line, where the second
            is interpreted as the puppetmaster. Defaults to the XKI puppets URL."""
              ),
        default=default_source,
    )

    parser.add_argument(
        "-f",
        "--file",
        action="store_true",
        help="Makes the script source from a file instead of URL.",
    )

    parser.add_argument(
        "-o",
        "--output",
        help=
        "File name to write (raw) output to. Outputs to stdout if not provided.",
        default=None,
    )

    if len(sys.argv) > 1:
        args = parser.parse_args()
    else:
        inputString = input("Arguments (One line, -h for help): ")
        args = parser.parse_args(shlex.split(inputString))

    requester = nsapi.NSRequester(config.userAgent)

    # Get input data
    if args.file:
        with open(nsapi.absolute_path(args.source)) as file:
            nations = [line.split("\t") for line in file.readlines()]
    else:
        text = requests.get(args.source).text
        nations = [line.split("\t") for line in text.split("\r\n")]

    # Convert to puppetmaster dicts
    # nation[1] is "" if no master, which is falsy
    # Clean the format of all the names
    puppets = {
        nsapi.clean_format(nation[0]): nsapi.clean_format(nation[1])
        if nation[1] else nsapi.clean_format(nation[0])
        for nation in nations
    }

    # Convert dates to start and end date objects
    if args.sub == "dates":
        start = datetime.date.fromisoformat(args.start)
        end = datetime.date.fromisoformat(args.end)
        # Usually makes sense to have the 'month' (for reports) be the ending date if not provided
        if args.month:
            month = datetime.date.fromisoformat(args.month + "-01")
        else:
            month = end.replace(day=1)
    elif args.sub == "month":
        month = datetime.date.fromisoformat(args.month + "-01")
        # Last day of previous month, i.e. 08 -> 07-31
        start = month - datetime.timedelta(days=1)
        # Last day of this month, i.e. 08 -> 08-31
        end = month.replace(
            day=calendar.monthrange(month.year, month.month)[1])

    logging.info("month: %s start: %s end: %s", month, start, end)

    counts = count_change(requester, puppets.keys(), start, end)
    changes = counts[0]

    # Collect puppetmaster counts
    collected_count = {puppetmaster: 0 for puppetmaster in puppets.values()}
    for puppet, change in changes.items():
        collected_count[puppets[puppet]] += change

    def write_output(file: t.TextIO) -> None:
        for puppetmaster, count in collected_count.items():
            print(f"{puppetmaster},{count}", file=file)

    # Write output
    if args.output is not None:
        with open(args.output, "w") as file:
            write_output(file)
    else:
        write_output(sys.stdout)

    # Generate report if chosen.
    # Check the subcommand first, because if month
    # wasnt chosen, the .report attribute wont exist
    # this avoids any exception due to lazy eval
    # if args.sub == "month" and args.report:
    if args.report:
        report = generate_report(month, collected_count)
        # Check for output directory
        if not os.path.isdir(nsapi.absolute_path("IssuePayoutReports")):
            os.mkdir(nsapi.absolute_path("IssuePayoutReports"))
        # Write the report
        with open(
                nsapi.absolute_path(
                    # Format the month to always be 2-digit
                    f"IssuePayoutReports/issuePayoutReport_{month.year}-{month.month:0>2d}.txt"
                ),
                "w",
        ) as f:
            f.write(report)
Esempio n. 15
0
logging.basicConfig(level=level)
# Name logger
logger = logging.getLogger()
# Change nsapi logging level
nsapi.logger.setLevel(level=level)

requester = nsapi.NSRequester(config.userAgent)

# load legendary cards
rarityfinder.verify_rarity_data(requester, "legendary")
cards = rarityfinder.load_rarity_data("legendary")

# load member nations
response = requests.get(PUPPET_SPREADSHEET)
members = {
    nsapi.clean_format(line.split("\t")[0])
    for line in response.text.split("\r\n")
}

print("Enter the timestamp to check trading since,")
print("or press enter to default to the first day of the previous month.")
boundInput = input("Timestamp: ")

if boundInput:
    bound = int(boundInput)
else:
    # first day, first second (12:00 AM) (UTC) of last month
    lastMonth: int = int(
        (
            # Gets a datetime in the previous month
            # by subtracting 1 day from the first day of this month
Esempio n. 16
0
def rule(nation: str) -> str:
    """Produce a ContainerRise rule for a nation."""

    clean = nsapi.clean_format(nation)
    return fr"@^.*\.nationstates\.net/(.*/)?nation={clean}(/.*)?$ , {nation}"