コード例 #1
0
def main():
    colorama_init()

    parser = ArgumentParser(description=__doc__,
                            formatter_class=RawDescriptionHelpFormatter)
    parser.add_argument('--min-activity',
                        dest='min_activity',
                        type=parse_time_ago,
                        default="1y",
                        help=("Remove followings inactive for a given period"
                              " (m for months, y for years, d for days) "
                              "(default: %(default)s)"))
    parser.add_argument('--target-count',
                        dest='target_count',
                        type=int,
                        help=("Target some following count (will try to stop"
                              " when you have that many followings left)"))
    parser.add_argument('--unfollow',
                        action='store_true',
                        help="Actually unfollow")
    parser.add_argument('--followers',
                        action='store_true',
                        help="Instead of removing people you follow, remove "
                        "people who follow YOU")
    parser.add_argument('--unmutuals',
                        action='store_true',
                        help="Remove people who follow you but that you "
                        "don't follow")
    parser.add_argument('-v',
                        '--verbose',
                        action='store_true',
                        help="Display more things")

    args = parser.parse_args()

    session = requests.Session()

    mastodon = Mastodon(access_token=settings.ACCESS_TOKEN,
                        api_base_url=settings.API_BASE,
                        ratelimit_method='pace')

    current_user = mastodon.account_verify_credentials()
    uid = current_user['id']

    if args.unmutuals:
        args.followers = True

    if args.followers:
        followings_count = current_user['followers_count']
    else:
        followings_count = current_user['following_count']
    local_count = followings_count

    goal_msg = ""
    if args.target_count:
        goal_msg = "(goal: n>={})".format(args.target_count)

    now = datetime.now(tz=timezone.utc)

    def clog(c, s):
        tqdm.write(c + s + Fore.WHITE + Style.NORMAL)

    cprint(Fore.GREEN,
           "Current user: @{} (#{})".format(current_user['username'], uid))
    cprint(
        Fore.GREEN,
        "{}: {} {}".format("Followers" if args.followers else "Followings",
                           followings_count, goal_msg))

    if args.unfollow:
        cprint(Fore.RED, "Action: unfollow")
    else:
        cprint(Fore.YELLOW, "Action: none")

    followings = None
    if args.followers:
        followings = mastodon.account_followers(uid)
    else:
        followings = mastodon.account_following(uid)
    followings = mastodon.fetch_remaining(followings)

    bar = tqdm(list(followings))
    for f in bar:
        fid = f.get('id')
        acct = f.get('acct')
        fullhandle = "@{}".format(acct)

        if '@' in acct:
            inst = acct.split('@', 1)[1].lower()
        else:
            inst = None

        if args.target_count is not None and local_count <= args.target_count:
            clog(Fore.RED + Style.BRIGHT,
                 "{} followings left; stopping".format(local_count))
            break

        title_printed = False

        def title():
            nonlocal title_printed
            if title_printed:
                return
            title_printed = True
            clog(Fore.WHITE + Style.BRIGHT,
                 "Account: {} (#{})".format(f.get('acct'), fid))

        if args.verbose:
            title()
        try:
            bar.set_description(fullhandle.ljust(30, ' '))

            act = False

            if args.unmutuals and inst not in settings.SKIP_INSTANCES:
                try:
                    relations = mastodon.account_relationships(fid)
                    is_mutual = relations[0]["following"] or relations[0][
                        "requested"]

                    if not is_mutual:
                        clog(Fore.YELLOW, "- Unmutual ({})".format(relations))
                        act = True
                except (UserGone, Error, requests.RequestException) as e:
                    act = False
                    clog(Fore.YELLOW, "- Exception ({})".format(e))

            elif args.min_activity and inst not in settings.SKIP_INSTANCES:
                try:
                    last_toot = get_last_toot(mastodon, fid)
                    if last_toot < now - args.min_activity:
                        # force a cache miss to be Sure
                        last_toot = get_last_toot(mastodon, fid, force=True)

                    if last_toot < now - args.min_activity:
                        act = True
                        msg = "(!)"
                        title()
                        clog(Fore.WHITE,
                             "- Last toot: {} {}".format(last_toot, msg))
                    else:
                        msg = "(pass)"
                        if args.verbose:
                            clog(Fore.WHITE,
                                 "- Last toot: {} {}".format(last_toot, msg))
                except UserGone as e:
                    moved = f.get('moved')
                    if moved:
                        # TODO: follow new account and unfollow old
                        act = False
                        title()
                        clog(
                            Fore.YELLOW,
                            "- User moved ({}) [NOT IMPLEMENTED]".format(
                                moved))
                    else:
                        act = True
                        title()
                        clog(Fore.YELLOW, "- User gone ({})".format(e))
                except (Error, requests.RequestException) as e:
                    if inst and inst in settings.ASSUME_DEAD_INSTANCES:
                        act = True
                        title()
                        clog(Fore.YELLOW, "- Instance gone ({})".format(e))
                    else:
                        raise

            if act:
                local_count -= 1

                if args.unfollow:
                    if args.followers:
                        clog(Fore.GREEN + Style.BRIGHT,
                             "- Removing follower {}".format(fullhandle))
                        mastodon.account_block(fid)
                        mastodon.account_unblock(fid)
                    else:
                        clog(Fore.GREEN + Style.BRIGHT,
                             "- Unfollowing {}".format(fullhandle))
                        mastodon.account_unfollow(fid)
                else:
                    clog(Fore.GREEN + Style.BRIGHT,
                         "- (not) unfollowing {}".format(fullhandle))

                clog(Fore.WHITE, ("- {}/{} followings left".format(
                    local_count, followings_count)))
        except Exception as e:
            title()
            clog(Fore.RED, "- Error: {}".format(str(e)))
コード例 #2
0
ファイル: backup.py プロジェクト: sivy/mastodon-backup
def backup(args):
    """
    Backup toots, followers, etc from your Mastodon account
    """

    (username, domain) = args.user.split("@")

    url = 'https://' + domain
    client_secret = domain + '.client.secret'
    user_secret = domain + '.user.' + username + '.secret'
    status_file = domain + '.user.' + username + '.json'
    data = None

    if os.path.isfile(status_file):
        print("Loading existing backup")
        with open(status_file, mode = 'r', encoding = 'utf-8') as fp:
            data = json.load(fp)

    if not os.path.isfile(client_secret):

        print("Registering app")
        Mastodon.create_app(
            'mastodon-backup',
            api_base_url = url,
            to_file = client_secret)

    if not os.path.isfile(user_secret):

        print("Log in")
        mastodon = Mastodon(
            client_id = client_secret,
            api_base_url = url)

        url = mastodon.auth_request_url(
            client_id = client_secret,
            scopes=['read'])

        print("Visit the following URL and authorize the app:")
        print(url)

        print("Then paste the access token here:")
        token = sys.stdin.readline().rstrip()

        mastodon.log_in(
            username = username,
            code = token,
            to_file = user_secret,
            scopes=['read'])

    else:

        mastodon = Mastodon(
            client_id = client_secret,
            access_token = user_secret,
            api_base_url = url)

    print("Get user info")
    user = mastodon.account_verify_credentials()

    def find_id(list, id):
        """Return the list item whose id attribute matches."""
        return next((item for item in list if item["id"] == id), None)

    def fetch_up_to(page, id):
        statuses = []
        # use a generator expression to find our last status
        found = find_id(page, id)
        # get the remaining pages
        while len(page) > 0 and found is None:
            statuses.extend(page)
            sys.stdout.flush()
            page = mastodon.fetch_next(page)
            if page is None:
                break
            found = find_id(page, id)
        page = page[0:page.index(found)]
        statuses.extend(page)
        print("Fetched a total of %d new toots" % len(statuses))
        return statuses

    if data is None or not "statuses" in data:
        print("Get statuses (this may take a while)")
        statuses = mastodon.account_statuses(user["id"])
        statuses = mastodon.fetch_remaining(
            first_page = statuses)
    else:
        id = data["statuses"][0]["id"]
        print("Get new statuses")
        statuses = fetch_up_to(mastodon.account_statuses(user["id"]), id)
        statuses.extend(data["statuses"])

    if data is None or not "favourites" in data:
        print("Get favourites (this may take a while)")
        favourites = mastodon.favourites()
        favourites = mastodon.fetch_remaining(
            first_page = favourites)
    else:
        id = data["favourites"][0]["id"]
        print("Get new favourites")
        favourites = fetch_up_to(mastodon.favourites(), id)
        favourites.extend(data["favourites"])

    data = { 'account': user,
            'statuses': statuses,
            'favourites': favourites }

    print("Saving %d statuses and %d favourites" % (
        len(statuses),
        len(favourites)))

    date_handler = lambda obj: (
        obj.isoformat()
        if isinstance(obj, (datetime.datetime, datetime.date))
        else None)

    with open(status_file, mode = 'w', encoding = 'utf-8') as fp:
        data = json.dump(data, fp, indent = 2, default = date_handler)