Exemple #1
0
def autologin(requester: nsapi.NSRequester, nations: Mapping[str, str],
              isAutologin: bool) -> Mapping[str, Optional[Result]]:
    """Autologins a list of nations, from the nations dict of {nation: password/autologin}.
    If isAutologin is true, the passwords are interpreted as autologins.
    Returns a dict mapping from nations to autologins.
    If the autologin value of the returned mapping is None, that means the login failed.
    """
    output: Dict[str, Optional[Result]] = {}
    for nation, password in nations.items():
        # Check how to interpret password
        if isAutologin:
            nationAPI = requester.nation(nation,
                                         nsapi.Auth(autologin=password))
        else:
            nationAPI = requester.nation(nation, nsapi.Auth(password=password))
        # Try making the shard request
        try:
            shards = nationAPI.shards("region", "ping", "wa")
        except nsapi.APIError:
            output[nation] = None
        else:
            output[nation] = Result(
                autologin=nationAPI.get_autologin(),
                region=shards["region"],
                wa=shards["unstatus"].startswith("WA"),
            )
    return output
Exemple #2
0
def unendorsed_nations_v2(
    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
    """

    # Retrieve endorser region and endorsement list
    logging.info("Collecting WA Members")
    info = requester.nation(endorser).shards("region", "endorsements")
    region = info["region"]
    endorsements = set(info["endorsements"].split(","))

    # Retrieve regional citizens by cross referencing residents and wa members
    citizens = set(requester.wa().shard("members").split(",")) & set(
        requester.region(region).shard("nations").split(":")
    )

    # Optionally only check nations who havent endorsed the endorser
    nations = citizens - endorsements

    # Check each nation's endorsments
    logging.info("Checking WA members for endorsement")
    nonendorsed = [
        nation
        for nation in nations
        if endorser not in requester.nation(nation).shard("endorsements")
    ]

    return (region, nonendorsed)
Exemple #3
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
    logging.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
    logging.info("Collecting WA members who have not been endorsed")
    nonendorsed = [
        # Save name string, converting to lowercase, underscore format
        clean_format(nation.name)
        for nation in waMembers
        # Check if unendorsed by checking endorsements
        if endorser not in nation.endorsements
    ]

    return (region, nonendorsed)
Exemple #4
0
def check_region(requester: nsapi.NSRequester,
                 region: str,
                 previous: Container[str] = None) -> Sequence[str]:
    """Checks all nations in the specified region for trading card activity.
    Makes at most 1 + region population requests (1 for each nation).
    A nation does not qualify if it is in the `previous` container.
    """
    # If a previous list is not provided, use an empty set
    if not previous:
        previous = set()

    participants = []

    # Grabs all residents of the region
    residents = requester.region(region).shard("nations").split(":")

    # Make a request for each resident
    for nation in residents:
        if nation not in previous:
            info = requester.nation(nation).deck_info()
            # Save the nation if it meets any of the requirments
            if (info.numCards > 0 or info.bank > 0 or info.lastValued
                    or info.lastPackOpened):
                participants.append(nation)

    return participants
Exemple #5
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
Exemple #6
0
def low_endorsements(requester: nsapi.NSRequester,
                     region: str,
                     count: int = 20) -> Collection[str]:
    """
    Finds nations with low endorsements.

    Searches the nation dump for WA nations in the specified region
    with less endorsements than the given count.
    """

    filtered = []

    # Squash casing on region
    lower_region = region.lower()

    # Search for matching nations
    for nation in requester.dumpManager().nations():
        if (
                # Compare regions, case insensitive
                nation.region.lower() == lower_region
                # Check that in WA
                and nation.WAStatus.startswith("WA")
                # Check that endorsements are under the specified level
                and len(nation.endorsements) <= count):
            # Save to return at end of search
            filtered.append(nation.name)

    return filtered
Exemple #7
0
def login(
    requester: nsapi.NSRequester,
    nation: str,
    autologin: t.Optional[str],
    password: t.Optional[str],
) -> t.Optional[Result]:
    """Attempts to log in a nation via NS API.

    Returns None on failure.

    At least one of password or autologin must be provided;
    autologin is used if both are provided.
    """
    # Create API object
    nationAPI = requester.nation(
        nation, auth=nsapi.Auth(autologin=autologin, password=password)
    )
    # Try making the shard request
    try:
        shards = nationAPI.shards("region", "ping", "wa")
    except nsapi.APIError:
        # None indicates any failure
        return None
    else:
        return Result(
            autologin=nationAPI.get_autologin(),
            region=shards["region"],
            wa=shards["unstatus"].startswith("WA"),
        )
Exemple #8
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
Exemple #9
0
def answer_all(
    requester: nsapi.NSRequester,
    name: str,
    autologin: str,
    optionNum: Optional[int] = None,
) -> Iterable[Tuple[int, int, str]]:
    """Answers all available issues on the nation.
    If optionNum is provided, attempts to answer all issues with that option
    (starting at 0, so 0 is the first option).
    If an issue does not have that many options, answers with the last.
    Counts by available options, not all options.
    Returns tuples encoding (id, option, description).
    """
    # Create and add authentication to the nation API object
    nation = requester.nation(name)
    nation.login(autologin)
    # Prepare output list
    output: List[Tuple[int, int, str]] = []
    # Iterate through all available issues
    for issue in nation.issues():
        # Use the option requested
        if optionNum:
            option = list(issue.options.keys())[optionNum]
        # If none, answer randomly
        else:
            option = random.choice(list(issue.options.keys()))
        info = nation.answer_issue(issue=issue.id, option=option)
        # info[1] should be the DESC node
        output.append((issue.id, option, info[1].text if info[1].text else ""))
    return output
Exemple #10
0
def uncrossed(requester: nsapi.NSRequester,
              nation: str,
              lead: str,
              duration: int = 3600) -> Iterable[str]:
    """Returns a iterable of non-recently endorsed nations sourced from lead endorsements
    `duration` is the number of seconds to check back in the nation's endo happenings
    """

    # Source endorsement list from lead
    logging.info("Retrieving lead endorser list")
    nations: Set[str] = set(
        requester.nation(nation).shard("endorsements").split(","))

    # Retrieve recent endo happenings in text form
    timestamp = int(time.time()) - duration
    logging.info("Retrieving endo happenings of nation since %s", timestamp)
    endos: List[str] = [
        happening.text for happening in requester.world().happenings(
            view=f"nation.{nation}",
            filter="endo",
            sincetime=str(timestamp),
        )
    ]

    # Parse endo happening texts
    # A text can be one of the following two 'distinct' forms:
    # based on 'hn67' == nation
    # '@@hn67@@ endorsed @@hn67_ii@@.' (outgoing)
    # '@@hn67_ii@@ endorsed @@hn67@@.' (incoming)
    # we only care about outgoing endorsements
    # the endorsed nation is retrieved using the following process:
    # .split() transforms the above outgoing example to ['', 'hn67', 'endorsed', 'hn67_ii', '']
    # index [3] retrieves the endorsed nation, index [1] retrieves the endorser
    # [1] is used to verify outgoing ( by checking == nation), and [3] retrieves the endorsee
    # Note: Splitting twice, could be optimized with pre-comprehension, for loop, or walrus operator
    logging.info("Filtering for outgoing endorsements")
    outgoing: Set[str] = set(
        text.split("@@")[3] for text in endos if text.split("@@")[1] == nation)

    # Unendorsed is obtained by subtracting the outgoing endorsements from the lead endorsement list
    logging.info("Obtaining uncross endorsed nations")
    unendorsed = nations - outgoing
    return unendorsed
Exemple #11
0
def collect(requester: nsapi.NSRequester,
            region: str) -> t.Mapping[str, t.Iterable[str]]:
    """Compiles the endorsees of every WA nation in the region."""

    # Cross-reference region nations and WA members
    region_nations = set(requester.region(region).nations())
    wa_nations = set(requester.wa().shard("members").split(","))
    members = region_nations & wa_nations

    # A dict mapping each WA member of this region to the nations they endorse
    endorsees: t.Dict[str, t.List[str]] = {member: [] for member in members}

    # Parse nation dump
    for nation in requester.dumpManager().nations():
        # Search for nations that are WA in this region
        if nation in members:
            for endorser in nation.endorsements:
                endorsees[endorser].append(nation.name)

    return endorsees
Exemple #12
0
def factbook_searcher(
        requester: nsapi.NSRequester,
        *keywords: str,
        populationMinimum: int = 0) -> Sequence[nsapi.RegionStandard]:
    """Returns Region's which have at least one of the given keywords in their WFE (.factbook)"""
    return [
        region
        # Maps from XML to RegionStandard, should be single pass since its a generator wrapping
        for region in requester.dumpManager().regions()
        # Probably not the most efficient (probably O(n^2))
        if any(
            keyword in region.factbook
            for keyword in keywords) and region.numnations > populationMinimum
    ]
Exemple #13
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
    ]
Exemple #14
0
def find_rarities(requester: nsapi.NSRequester, rarity: str) -> None:
    """Searches both card dumps for cards of the specified rarity
    and saves the id/season to a file of the same name"""

    # Goes through each dump, grabbing any CardStandard that has the right rarity
    # store season with it, since the CardStandard does not have season
    cards = []
    for card in requester.dumpManager().cards(season="1"):
        if card.rarity == rarity:
            cards.append((card, "1"))
    for card in requester.dumpManager().cards(season="2"):
        if card.rarity == rarity:
            cards.append((card, "2"))

    # Dump everything as strings so its easier to use
    with open(resolve_path(rarity), "w") as file:
        json.dump(
            [{
                "cardid": str(card.id),
                "season": season,
                "name": card.name
            } for card, season in cards],
            file,
        )
Exemple #15
0
def named_cards(
    requester: nsapi.NSRequester,
    cards: Iterable[nsapi.CardIdentifier],
) -> Mapping[int, str]:
    """Maps each CardIdentifier to the nation name,
    using the card dumps. Returns a mapping from id to name
    """
    cardMap = {card.id: "" for card in cards}
    for standard in itertools.chain(
            requester.dumpManager().cards(season="1"),
            requester.dumpManager().cards(season="2"),
    ):
        if standard.id in cardMap:
            cardMap[standard.id] = standard.name
    return cardMap
Exemple #16
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
Exemple #17
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]
Exemple #18
0
def retrieve_endings(
    requester: nsapi.NSRequester,
    since: t.Optional[int] = None,
    before: t.Optional[int] = None,
) -> t.Iterable[Ending]:
    """Returns the endings in the given timestamp frame.

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

    # Define default window in seconds
    DEFAULT_WINDOW = 24 * 3600

    # Check for before default
    if before:
        beforetime = before
    else:
        beforetime = int(time.time())

    # Check for since default
    if since:
        sincetime = since
    else:
        sincetime = beforetime - DEFAULT_WINDOW

    # Retrieve endings happening data
    happenings = requester.world().happenings(safe=False,
                                              filter="cte",
                                              sincetime=str(sincetime),
                                              beforetime=str(beforetime))

    output = []
    for happ in happenings:
        # Try to match the two patterns
        nationMatch = re.search(r"@@(.*)@@", happ.text)
        regionMatch = re.search(r"%%(.*)%%", happ.text)
        if nationMatch and regionMatch:
            # Pull the first group of each match, i.e. omit the @@/%% delimiters
            output.append(Ending(nation=nationMatch[1], region=regionMatch[1]))
        else:
            logging.warning(
                "Found CTE happening with no nation or no region: %s",
                happ.text)

    return output
Exemple #19
0
def sorted_cards(
    requester: nsapi.NSRequester, nations: Iterable[str]
) -> Mapping[str, Mapping[str, Mapping[nsapi.CardIdentifier, int]]]:
    """Searchs the trading card decks of the given nations (by name), and sorts by rarity
    Returns a mapping with {rarity: {nation: {card: quantity}}} structure.
    Always includes exactly `common`, `uncommon`, `rare`, `ultra-rare`, `epic`, and `legendary`
    """
    # Prepare output structure with rarities
    # empty string key should catch any Card objects that dont have a category for some reason
    out: MutableMapping[str,
                        MutableMapping[str,
                                       MutableMapping[nsapi.CardIdentifier,
                                                      int]]] = {
                                                          "common": {},
                                                          "uncommon": {},
                                                          "rare": {},
                                                          "ultra-rare": {},
                                                          "epic": {},
                                                          "legendary": {},
                                                          "": {},
                                                      }
    # Sort the deck of each nation
    for nation in nations:
        # Retrieve the deck of the nation (1 request)
        deck = requester.nation(nation).deck()
        # Parse each card in the deck, sorting into appropriate rarity bin
        for card in deck:
            # We dont wrap the out[] indexing in a try, since there should never be a error:
            # if for some reason the card doesnt have a category, it should have "" and
            # get placed in that bin
            rarityDict = out[card.rarity]
            # ensure this nation exists in this rarity bin
            try:
                nationDict = rarityDict[nation]
            except KeyError:
                rarityDict[nation] = {}
                nationDict = rarityDict[nation]
            # creates the card: num key-value pair if it doesnt exist
            # otherwise, increment the count
            try:
                nationDict[card] += 1
            except KeyError:
                nationDict[card] = 1

    return out
Exemple #20
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"],
        )
Exemple #21
0
def delegacy(
    requester: nsapi.NSRequester,
    startTime: int = None,
    endTime: int = None,
    duration: int = None,
) -> Iterable[nsapi.Happening]:
    """Obtain all delegacy changes in the requested time period.

    Up to two of startTime, endTime, and duration should be provided.

    End time defaults to now, unless overridden by a combination of start and duration.
    Start time defaults to duration before end time, or 12 hours before end time otherwise.
    If all three arguments are given, duration is ignored.
    """
    # Determine start and end
    if endTime is None:
        if startTime is not None and duration is not None:
            endTime = startTime + duration
        else:
            # Time.time produces a float to represent milli, we just want seconds
            endTime = int(time.time())
    if startTime is None:
        if duration is not None:
            startTime = endTime - duration
        else:
            # 12 hours
            startTime = endTime - 12 * 3600

    # Filter happenings
    return [
        happening for happening in requester.world().happenings(
            safe=False,
            sincetime=str(startTime),
            beforetime=str(endTime),
            filter="member",
        ) if "WA Delegate" in happening.text
    ]
Exemple #22
0
def delegacy(
    requester: nsapi.NSRequester, startTime: int = None, endTime: int = None
) -> Iterable[nsapi.Happening]:
    """Returns all delegacy changes between start and end times (as epoch timestamps),
    endTime defaults to now, and startTime defaults to 12 hours before endTime
    """
    # Set start/end defaults
    if not endTime:
        # Time.time produces a float to represent milli, we just want seconds
        endTime = int(time.time())
    if not startTime:
        # 12 hours, 3600 seconds an hour, 12 hours previous
        startTime = endTime - 12 * 3600
    # Filter happenings
    return [
        happening
        for happening in requester.world().happenings(
            safe=False,
            sincetime=str(startTime),
            beforetime=str(endTime),
            filter="member",
        )
        if "WA Delegate" in happening.text
    ]
Exemple #23
0
def residents(requester: nsapi.NSRequester, region: str) -> Collection[str]:
    """Retrieves the WA residents of the given region."""

    # Retrieve residents
    return set(requester.region(region).nations()) & set(
        requester.wa().shard("members").split(","))
Exemple #24
0
def is_wa(requester: nsapi.NSRequester, nation: str) -> bool:
    """Checks if the nation is in the WA"""
    try:
        return requester.nation(nation).wa().startswith("WA")
    except nsapi.ResourceError:
        return False