#!/usr/bin/env python3
#
# usage: ./missclear.py
#
# WARNING: This tool doesn't prompt for approval. Once you approve the
# app in Misskey, it'll start deleting right away!
# 

from Misskey import Misskey, Util, Exceptions # pip install Misskey.py
import webbrowser
import time
import datetime
import os
import sys
import argparse

SLEEP_TIME=2                # how many seconds to wait between each notes/delete call
BAD_SLEEP_TIME=15           # how many seconds to wait when things go bad

def get_cache_dir():
    cache_path = os.getenv('XDG_CACHE_DIR')
    if cache_path is None or len(cache_path) == 0:
        home_dir = os.getenv('HOME')
        if home_dir is None or len(home_dir) == 0:
            print("ERR: both $XDG_CACHE_DIR and $HOME are unset, not caching tokens...", file=sys.stderr)
            return None

        cache_path = os.path.join(home_dir, '.cache')

    return os.path.join(cache_path, 'missclear')


class Missclear(object):
    """ Main Missclear module """
    instance = None
    quiet = False
    cache_token=True
    retry_count=None
    interactive=True

    def __init__(self, args):
        self.instance = args.instance[0]
        self.quiet = args.quiet
        self.cache_token = args.cache
        self.retry_count = args.retries
        self.interactive = args.interactive

        self.print("instance=" + self.instance + ", quiet=" + str(self.quiet) +
                   ", caching=" + str(self.cache_token) + ", retries=" + str(self.retry_count) +
                   ", interactive=" + str(self.interactive))

        self.api = Misskey(self.instance)


    def use_cached_token(self):
        """ imports the cached token, if available and requested """
        if self.cache_token is False:
            return False

        token_file = os.path.join(get_cache_dir(), self.instance)
        if os.path.exists(token_file):
            with open(token_file) as f:
                token_data = f.read()

            try:
                self.api.apiToken = token_data
            except Exceptions.MisskeyAPITokenException as err:
                self.print("Cached token is invalid!")
                return False
            return True

        return False


    def set_token(self, token):
        self.api.apiToken = token

        if self.cache_token:
            cache_dir = get_cache_dir()
            token_file = os.path.join(cache_dir, self.instance)
            if cache_dir is None:
                return False

            os.makedirs(cache_dir, mode=0o700, exist_ok=True)

            with open(token_file, mode='w') as f:
                f.write(token)


        
    def interactive_get_token(self):
        """Gets the user's token using MiAuth"""
        if self.interactive is False:
            return False

        print("A new browser instance will be opened to request permissions for this app to function.")
        print("Please click 'Accept' to continue. If you don't want to continue, press Ctrl+C.")
        auth = Util.MiAuth(self.instance, permission=('read:account', 'write:notes'), name="missclear")
        webbrowser.open_new_tab(auth.getUrl())

        authed = False
        while not authed:
            try:
                authdata = auth.check()
                self.print("Authed as " + authdata['user']['username'])
                authed = True
                self.set_token(authdata['token'])
                return True
            except Exceptions.MisskeyMiAuthCheckException:
                time.sleep(5)

        return False

    def print(self, fmt, file=None):
        """Extension of print() to provide quiet support"""
        if not self.quiet:
            print(fmt)


    def auth(self):
        """ Wrapper for use_cached_token/interactive_get_token """
        if not self.use_cached_token():
            if not self.interactive:
                print("ERR: No cached token and interactive auth is disabled!", file=sys.stderr)
                return False
            else:
                if not self.interactive_get_token():
                    print("ERR: Couldn't auth", file=sys.stderr)
                    return False

        return True


    def clear_notes(self, since_days=14):
        until_stamp = int((datetime.datetime.now() - datetime.timedelta(days=since_days)).timestamp() * 1000)
        my_id = self.api.i()["id"]

        while True:
            old_notes = self.api.users_notes(my_id, untilDate=until_stamp)
            if len(old_notes) == 0:
                break

            for to_del in old_notes:
                deleted = False
                try_times = 0
                while not deleted:
                    try:
                        if self.api.notes_delete(to_del["id"]):
                            self.print("Successfully deleted " + to_del["id"] + " from " + to_del["createdAt"] + ".")
                            time.sleep(SLEEP_TIME)
                        else:
                            print("Was unable to delete " + to_del["id"] + " for some unknown reason.", file=sys.stderr)
                        # technically not necessarily deleted, but if we get False then there's nothing we can do
                        deleted = True
                    except Exception as err:
                        self.print("!!! An exception occurred of type {0}: {1}".format(type(err), err), file=sys.stderr)
                        time.sleep(BAD_SLEEP_TIME)
                        try_times += 1
                        if self.retry_count > 0 and try_times > self.retry_count:
                            print("Giving up on " + to_del["id"] + "...", file=sys.stderr)
                        else:
                            self.print("... trying again ...")


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description='Deletes old Misskey posts.')
    parser.add_argument('instance', nargs=1,
                        help='Misskey instance domain, in format domain.tld')
    parser.add_argument('-d', '--days', type=int,
                        help='How many days back to start deleting from',
                        default=14)
    parser.add_argument('-q', '--quiet', action='store_true',
                        help='Suppress less urgent messages')
    parser.add_argument('-r', '--retries', type=int, nargs=1,
                        help='Amount of times to retry a failed delete',
                        default=0)
    authgrp = parser.add_mutually_exclusive_group()
    authgrp.add_argument('-A', '--no-auth', action='store_false',
                        help='Disable interactive authentication',
                        dest='interactive')
    authgrp.add_argument('-N', '--no-cache', action='store_false',
                        help='Disable reading/saving cached tokens',
                        dest='cache')

    args = parser.parse_args()

    mc = Missclear(args)
    if not mc.auth():
        sys.exit(1)
    if not mc.clear_notes(args.days):
        sys.exit(1)