Beispiel #1
0
class Bot(object):
    def __init__(self, access_token, since_at, instance_url, msg_template='', debug_mode=True, account_id=1):
        self.instance_url = instance_url
        self.msg_template = Template(msg_template)
        self.client = Mastodon(
            access_token=access_token,
            api_base_url=self.instance_url
        )
        self.since_at = since_at
        self.debug_mode = debug_mode

        self.log = logging.getLogger()
        if self.debug_mode:
            self.log.setLevel(logging.DEBUG)
        else:
            self.log.setLevel(logging.INFO)

        ch = logging.StreamHandler(sys.stdout)
        self.log.addHandler(ch)

        self.account_id = account_id

        self.until = pytz.utc.localize(datetime.now() - timedelta(days=1))
        self.until = self.until.replace(hour=23, minute=59, second=59)

    def _should_get_msg(self, account):
#         return account.url.startswith(self.instance_url) and account.created_at >= self.since_at and account.created_at <= self.until and not account.locked
        self.log.debug("self.until")
        return account.url.startswith(self.instance_url) and account.created_at >= self.since_at and not account.locked

    def get_users(self):
        users_to_msg = []

        def get_prev(users):
            followers = self.client.fetch_previous(users)
            filtered = [follower for follower in followers if self._should_get_msg(follower)]
            users_to_msg.extend(filtered)

            # this assumes that followers are returned in descending order by follow date!
            if len(filtered) == 0:
                return users_to_msg

            return get_prev(followers)

        followers = self.client.account_followers(self.account_id, limit=80)
        filtered = [follower for follower in followers if self._should_get_msg(follower)]
        users_to_msg.extend(filtered)

        if len(filtered) == 0:
            return users_to_msg

        return get_prev(followers)

    def send_msg(self, account):
        msg = self.msg_template.render(account)
        msg = msg.replace('\\n', '\n')
        self.log.debug(msg)
        if self.debug_mode:
            return

        self.client.status_post(msg, visibility='direct')

    def go(self):
        users_to_msg = self.get_users()
        if self.debug_mode:
            self.log.debug('in debug mode so not actually sending toots, but if I was, I\'d be sending to ' \
                  '{} accounts'.format(len(users_to_msg)))
            self.log.debug('here\'s the toots that I would be sending:')

        for u in users_to_msg:
            self.send_msg(u)

        self.log.info('{} toots sent!'. format(len(users_to_msg)))
Beispiel #2
0
    if j % 100 == 0:
        consolidate('accounts')
        consolidate('followers')
        consolidate('following')

    try:
        account = mastodon.account(j)
        accounts.append(account)
        with open(pathTo + 'accounts/accounts' + str(j) + '.json',
                  'w') as accountsFile:
            json.dump(account, accountsFile, default=datetimeconvert)

        if account['url'][:len(url)] == url:
            if len(account) > 0:
                if account["followers_count"] > 0:
                    followersTemp = getLists(mastodon.account_followers(j))
                    if account["followers_count"] != len(followersTemp[1]):
                        error.append(['followers', j])
                    followers.append(followersTemp)

                if account["following_count"] > 0:
                    followingTemp = getLists(mastodon.account_following(j))
                    if account["following_count"] != len(followingTemp[1]):
                        error.append(['followiong', j])
                    following.append(followingTemp)
                with open(pathTo + 'followers/followers' + str(j) + '.json',
                          'w') as followersFile:
                    json.dump(followers,
                              followersFile,
                              default=datetimeconvert)
                with open(pathTo + 'following/following' + str(j) + '.json',
Beispiel #3
0
class FediGroupBot:
    def __init__(self, base_url, access_token, accept_dms, accept_retoots,
                 save_file):
        self.base_url = base_url
        self.accept_dms = accept_dms
        self.accept_retoots = accept_retoots
        self.save_file = save_file

        self.masto = Mastodon(debug_requests=False,
                              access_token=access_token,
                              api_base_url=base_url)

        self.id = self.masto.account_verify_credentials().id
        self.username = self.masto.account_verify_credentials().username
        self.group_members = []
        self.group_admins = []

    def run(self):
        last_seen_id = 0
        if os.path.exists(self.save_file):
            with open(self.save_file, 'r') as fd:
                try:
                    last_seen_id = int(fd.readline())
                except ValueError:
                    pass

        while True:
            notifications = self.masto.notifications(since_id=last_seen_id)

            if notifications:
                self.__update_group_members()

            for notification in sorted(notifications, key=lambda x: x.id):
                if notification.id > last_seen_id:
                    last_seen_id = notification.id
                    self.__do_action(notification)
                    with open(self.save_file, 'w') as fd:
                        fd.write(str(last_seen_id))

            time.sleep(2)

    def __update_group_members(self):
        self.group_members, max_id = [], None
        followers = self.masto.account_followers(self.id,
                                                 limit=sys.maxsize,
                                                 max_id=max_id)  # NOQA: E501
        while len(followers) > 1:
            self.group_members += [member.acct for member in followers]
            max_id = followers[-1].id
            followers = self.masto.account_followers(
                self.id, limit=sys.maxsize, max_id=max_id)  # NOQA: E501

        self.group_admins, max_id = [], None
        following = self.masto.account_following(self.id,
                                                 limit=sys.maxsize,
                                                 max_id=max_id)  # NOQA: E501
        while len(following) > 1:
            self.group_admins += [member.acct for member in following]
            max_id = followers[-1].id
            following = self.masto.account_following(
                self.id, limit=sys.maxsize, max_id=max_id)  # NOQA: E501

    def __do_action(self, notification):
        if notification.type != "mention":
            return

        if notification.status.in_reply_to_id is not None:
            return

        if self.accept_retoots and notification.status.visibility == "public" \
           and notification.status.account.acct in self.group_members:
            self.masto.status_reblog(notification.status.id)

        if self.accept_dms and notification.status.visibility == "direct" \
           and notification.status.account.acct in self.group_admins:
            new_status = re.sub("<br />", "\n", notification.status.content)
            new_status = re.sub("</p><p>", "\n\n", new_status)
            new_status = re.sub("<.*?>", "", new_status)

            if new_status.startswith("@" + self.username):
                new_status = re.sub("@" + self.username, "", new_status)
                new_status = html.unescape(new_status)

                media_ids = []
                for media in notification.status.media_attachments:
                    response = requests.get(media.url)
                    mime_type = response.headers['Content-Type']
                    media_data = response.content
                    media_ids.append(
                        self.masto.media_post(media_data,
                                              mime_type,
                                              description=media.description))

                self.masto.status_post(
                    new_status,
                    media_ids=media_ids,
                    sensitive=notification.status.sensitive,
                    visibility="public",
                    spoiler_text=notification.status.spoiler_text)
Beispiel #4
0
import os
from mastodon import Mastodon

fedibird = Mastodon(
    access_token=os.getenv('MASTODON_TOKEN'),
    api_base_url='https://fedibird.com',
)
mentions = ' '.join(
    [f'@{follower.acct}' for follower in fedibird.account_followers(77150)])
poll = fedibird.make_poll(options=('21時から参加可能', '22時から参加可能', '23時から参加可能',
                                   '行けたら行く'),
                          expires_in=10000)
fedibird.status_post(
    status=f'{mentions}\n #今日のプラベ 如何ですか?',
    poll=poll,
    visibility='private',
)
Beispiel #5
0
if last_id_file.is_file():
    with open(last_id_file) as f:
        last_id = int(f.read())

print('Botvenon is starting...')
print('Welcoming any users after id %d' % last_id)

mastodon = Mastodon(
    client_id=CLIENT_ID,
    client_secret=CLIENT_SECRET,
    access_token=ACCESS_TOKEN,
    api_base_url=INSTANCE_BASE)

user = mastodon.account_verify_credentials()  # Get the current user

followers = mastodon.account_followers(user)  # Get the latest followers


# print(
#     f'''Followed by: {account.username}
# (Acct value: {account.acct}, id: {account.id})''')

while True:
    for account in mastodon.account_followers(user):
        if is_local_account(account) and account.id > last_id and not account.bot:
            last_id = account.id
            with open(last_id_file, 'w+') as f:
                f.write(str(last_id))
            print('Welcoming %s...' % account.username)
            send_welcome_message(mastodon, account)
    time.sleep(WAIT_TIME)
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)))