def main()

in new-contributors.py [0:0]


def main() -> None:
    # parse command line arguments
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "version",
        type=int,
        help="Firefox version",
    )
    parser.add_argument(
        "--api-key",
        "--apikey",
        required=True,
        help="Bugzilla API-Key",
    )
    args = parser.parse_args()
    if args.version < 0:
        raise Error(f"Invalid version: {args.version}")

    # load cache
    # store a list of users against each version that were determined to have patches
    # landed _prior_ to the specified version
    cache_file = Path(__file__).parent / "new-contributors.cache"
    try:
        with cache_file.open() as f:
            cache = json.load(f)
    except (FileNotFoundError, ValueError):
        cache = []
    current_cache = None
    for cache_item in cache:
        if cache_item["version"] == args.version:
            current_cache = cache_item
            break
    if not current_cache:
        current_cache = {"version": args.version, "skip": []}
        cache.append(current_cache)

    # find bugs fixed in specified version
    print(f"looking for bugs fixed in Firefox {args.version}", file=sys.stderr)
    bugs = bmo_request(
        "bug",
        {
            "target_milestone": f"{args.version} Branch",
            "status": "RESOLVED",
            "product": PRODUCTS,
            "include_fields": "id,assigned_to,cf_last_resolved",
            "order": "cf_last_resolved",
        },
        api_key=args.api_key,
    )["bugs"]
    print(f"found {plural(len(bugs), 'bug')}", file=sys.stderr)
    if not bugs:
        return

    # find new assignees
    new = {}
    for bug in bugs:
        assignee = bug["assigned_to"]

        # skip users that are clearly employees or contractors
        if assignee.endswith(
            (
                "@getpocket.com",
                "@mozilla.com",
                "@mozilla.org",
                "@mozillafoundation.org",
                "@softvision.com",
                "@softvision.ro",
                "@softvisioninc.eu",
            )
        ):
            continue
        # skip users we already know are not new
        should_skip = False
        for cache_item in cache:
            if cache_item["version"] <= args.version and assignee in cache_item["skip"]:
                should_skip = True
                break
        if should_skip:
            continue
        # handle users that we know are new and fixed more than one bug
        if assignee in new:
            new[assignee]["bugs"].append(bug["id"])
            continue

        print(f"checking {assignee}", file=sys.stderr, end="")

        # always exclude employees; this is quicker than a bug search, and not
        # all bugs have correct metadata
        users = bmo_request(
            "user",
            {"names": assignee},
            api_key=args.api_key,
        )["users"]
        is_employee = False
        if users:
            for group in users[0]["groups"]:
                if group["name"] == "mozilla-employee-confidential":
                    is_employee = True
                    break
        if is_employee:
            print(" employee", file=sys.stderr)
            current_cache["skip"].append(assignee)
            continue
        print(" contributor", file=sys.stderr, end="")

        # query for bugs fixed by this user before this one
        prior_bugs = bmo_request(
            "bug",
            {
                # resolved bugs in our products
                "product": PRODUCTS,
                "status": "RESOLVED",
                # assigned to our user
                "emailassigned_to1": "1",
                "emailtype1": "exact",
                "email1": assignee,
                # where the last resolved is older than this bug's
                "f1": "cf_last_resolved",
                "o1": "lessthan",
                "v1": bug["cf_last_resolved"].replace("T", " ").replace("Z", ""),
                # and a target_milestone is set (filter our duplicates, etc)
                "f2": "target_milestone",
                "o2": "notequals",
                "v2": "---",
                # don't need the full list or count, just need to know if there's any
                "limit": 1,
            },
            api_key=args.api_key,
        )["bugs"]
        if prior_bugs:
            print(" existing", file=sys.stderr)
            current_cache["skip"].append(assignee)
            continue

        # collate in `new` dict
        print(" new", file=sys.stderr)
        new.setdefault(
            assignee,
            {
                "name": bug["assigned_to_detail"]["real_name"]
                or bug["assigned_to_detail"]["nick"],
                "bugs": [],
            },
        )
        new[assignee]["bugs"].append(bug["id"])
    print(f"found {plural(len(new), 'new contributor')}", file=sys.stderr)

    # update cache
    with cache_file.open("w") as f:
        json.dump(cache, f, indent=2, sort_keys=True)

    # generate nucleus output
    print(
        f"With the release of Firefox {args.version}, we are pleased to welcome "
        "the developers who contributed their first code change to Firefox in "
        f"this release, {len(new)} of whom were brand new volunteers! Please "
        "join us in thanking each of these diligent and enthusiastic "
        "individuals, and take a look at their contributions:\n"
    )
    for user in sorted(new.values(), key=lambda u: u["name"].lower()):
        bug_links = ", ".join(
            f'<a href="https://bugzilla.mozilla.org/{b}">{b}</a>'
            for b in sorted(user["bugs"])
        )
        print(f"* {user['name']}: {bug_links}")