diff --git a/.gitignore b/.gitignore
index f85c6b1..c8f9ffc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-config.py
\ No newline at end of file
+config.py
+config.yaml
\ No newline at end of file
diff --git a/README.md b/README.md
index 6c1455f..92ab249 100644
--- a/README.md
+++ b/README.md
@@ -1,90 +1,160 @@
-A script for deleting old toots.
+A tool for deleting old toots, written in Python 3.
-Based partially on [tweet-deleting script](https://gist.github.com/flesueur/bcb2d9185b64c5191915d860ad19f23f) by [@flesueur](https://github.com/flesueur)
+# Prior work
+The initial `ephemetoot` script was based on [this tweet-deleting script](https://gist.github.com/flesueur/bcb2d9185b64c5191915d860ad19f23f) by [@flesueur](https://github.com/flesueur)
+
+`ephemetoot` relies heavily on the Mastodon.py package by [@halcy](https://github.com/halcy)
# Usage
-You can use this script to delete [Mastodon](https://github.com/tootsuite/mastodon) toots that are older than a certain number of days. By default it will keep any pinned toots, but if you want them to be deleted as well you can change `keep_pinned` to `False` in `config.py`. You can also make a list toots that you want to keep, by adding the ID numbers to the `toots_to_keep` list in `config.py` (see point 9 below). The ID of a toot is the last part of its individual URL. e.g. for [https://ausglam.space/@hugh/101294246770105799](https://ausglam.space/@hugh/101294246770105799) the id is `101294246770105799`
-
-This script requires Python3, the `mastodon.py` package and an API access token.
+You can use `ephemetoot` to delete [Mastodon](https://github.com/tootsuite/mastodon) toots that are older than a certain number of days. Toots can optionally be saved from deletion if:
+* they are pinned;
+* they include certain hashtags;
+* they have certain visibility; or
+* they are individually listed to be kept
# Setup
-1. Install Python3 if you don't already have it (recommended approach is to [use Homebrew](https://docs.brew.sh/Homebrew-and-Python) if you're on MacOS)
-2. Install the mastodon package: `pip3 install mastodon.py`
-3. Copy _example.config.py_ to a new file called _config.py_ (e.g. `cp example.config.py config.py`)
-4. Log in to your Mastodon account using a web browser
- 1. Click the settings cog
- 2. Click on Development
- 3. Click 'NEW APPLICATION'
- 4. Enter an application name, and give the app 'read' and 'write' Scopes
- 5. Click 'SUBMIT'
- 6. Click on the name of the new app
- 7. Copy the 'access token' string
-5. Replace `YOUR_ACCESS_TOKEN_HERE` in config.py with the access token string
-6. Set the `base_url` to match your mastodon server
-7. Set the `days_to_keep` to the number of days you want to keep toots before deleting them
-8. If you do **not** wish to keep all pinned toots regardless of age, change `keep_pinned` to `False`
-9. If there are any other toots you want to keep, put the ID numbers (without quotes) in the `toots_to_keep` list, separated by commas. For example:
-```python
-toots_to_keep = [100029521330725397, 100013562864734780, 100044187305250752]
+## Install Python 3
+
+You need to [install Python 3](https://wiki.python.org/moin/BeginnersGuide/Download) to use `ephemetoot`. Python 2 is now end-of-life, however it continued to be installed as the default Python on MacOS until very recently, and may also be installed on your server.
+
+## Install ephemetoot
+### get code with git
+If you already have `git` installed on the machine where you're running ephemetoot, you can download the latest release with:
+```shell
+git clone https://github.com/hughrun/ephemetoot.git
```
-10. If you want to keep toots with a particular hashtag, list each hashtag in the `hashtags_to_keep` set (omitting the `#`):
-```python
-hashtags_to_keep = {'introduction', 'announcement'}
+### get code by downloading zip file
+If you don't have `git` or don't want to use it, you can download the zip file by clicking the green `Clone or download` button above and selecting `Download ZIP`. You will then need to unzip the file into a new directory where you want to run it.
+
+### install using pip
+From a command line, move into the main `ephemetoot` directory (i.e. where the README file is) and run:
+```shell
+pip install .
```
-11. You can keep toots with particular visibility (e.g. direct messages) by including that visibility in `visibility_to_keep`. For example the following would only delete public toots:
-```python
-visibility_to_keep = ['unlisted', 'private', 'direct']
+With some Python 3 installations (e.g on MacOS with Homebrew) you may need to use:
+```shell
+pip3 install .
```
+## Obtain an access token
+
+Now you've installed `ephemetoot`, in order to actually use it you will need an application "access token" from each user. Log in to your Mastodon account using a web browser:
+
+1. Click the `settings` cog
+2. Click on `Development`
+3. Click `NEW APPLICATION`
+4. Enter an application name (e.g. 'ephemetoot'), and give the app both 'read' and 'write' Scopes
+5. Click `SUBMIT`
+6. Click on the name of the new app, which should be a link
+7. Copy the `Your access token` string
+
+## Configuration file
+
+As of version 2, you can use a single `ephemetoot` installation to delete toots from multiple accounts. Configuration for each user is set up in the `config.yaml` file. This uses [yaml syntax](https://yaml.org/spec/1.2/spec.html) and can be updated at any time without having to reload `ephemetoot`.
+
+Copy `example-config.yaml` to a new file called `config.yml`:
+```shell
+cp example-config.yam config.yaml
+```
+You can now enter the configuration details for each user:
+
+| setting | description |
+| ---: | :--- |
+| access_token | The alphanumeric access token string from the app you created in Mastodon |
+| username | Your username without the '@' or server domain. e.g. `hugh`|
+| base_url | The base url of your Mastodon server, without the 'https://'. e.g. `ausglam.space`|
+| days_to_keep | Number of days to keep toots e.g. `30`|
+| keep_pinned | Either `True` or `False` - if `True`, any pinned toots will be kept regardless of age |
+| toots_to_keep | A list of toot ids indicating toots to be kept regardless of other settings. The ID of a toot is the last part of its individual URL. e.g. for [https://ausglam.space/@hugh/101294246770105799](https://ausglam.space/@hugh/101294246770105799) the id is `101294246770105799` |
+| hashtags_to_keep | a Set of hashtags, where any toots with any of these hashtags will be kept regardless of age. Do not include the '#' symbol, and remember the [rules for hashtags](https://docs.joinmastodon.org/user/posting/#hashtags) |
+| visibility_to_keep | Any toots with visibility settings in this list will be kept regardless of age. Options are: `public`, `unlisted`, `private`, `direct`. For example the following would only delete public toots:
+```yaml
+- unlisted
+- private
+- direct
+```
+|
+
+If you want to use `ephemetoot` for multiple accounts, separate the config for each user with a single dash (`-`), as shown in the example file.
+
# Running the script
-## Test mode
+It is **strongly recommended** that you do a [test run](#running-in-test-mode) before using `ephemetoot` live.
+
+To call the script you can simply enter:
+```shell
+ephemetoot
+```
+
+Depending on how many toots you have and how long you want to keep them, it may take a minute or two before you see any results.
+
+## Specifying the config location
+
+By default ephemetoot expects there to be a config file called `config.yaml` in the directory from where you run the `ephemetoot` command. If you want to call it from elsewhere (e.g. from `cron`), you need to specify where your config file is:
+
+```shell
+ephemetoot --config 'directory/config.yaml'
+```
+
+## Running in test mode
To do a test-run without actually deleting anything, run the script with the `--test` flag:
```shell
-python3 ephemetoot.py --test
+ephemetoot --test
```
-Depending on how many toots you have and how long you want to keep them, it may take a minute or two before you see any results.
-## Live mode
+## Other flag options
-Run the script with no flags:
+You can use both flags together:
```shell
-python3 ephemetoot.py
+ephemetoot --config 'directory/config.yaml' --test
+```
+Use them in any order:
+```shell
+ephemetoot --test --config 'directory/config.yaml'
+```
+Instead of coming back to this page when you forget the flags, you can just use the help option:
+```shell
+ephemetoot --help
```
-Depending on how many toots you have and how long you want to keep them, it may take a minute or two before you see any results.
+## Rate limits
+
+As of v2.7.2 the Mastodon API has a rate limit of 30 deletions per 30 minutes. `mastodon.py` automatically handles this. If you are running `ephemetoot` for the first time and/or have a lot of toots to delete, it may take a while as the script will pause when it hits a rate limit, until the required time has expired. Note that the rate limit is per access token, so using ephemetoot for multiple accounts on the same server shouldn't be a big problem, however one new user may delay action on subsequent accounts in the config file.
## Scheduling
Deleting old toots daily is the best approach to keeping your timeline clean and avoiding problems wiht the API rate limit.
-To run automatically every day you could try using crontab:
+To run automatically every day on a n*x server you could try using crontab:
1. `crontab -e`
- 2. `@daily python3 ~/ephemetoot/ephemetoot.py`
+ 2. `@daily ephemetoot`
-Alternatively on MacOS you could use [launchd](https://www.launchd.info/) or Automator.
+Alternatively on MacOS you could use [launchd](https://www.launchd.info/). Some further work on an example setup for launchd is coming soonish.
-## Rate limits
-
-As of v2.7.2 the Mastodon API has a rate limit of 30 deletions per 30 minutes. `mastodon.py` automatically handles this. If you are running `ephemetoot` for the first time and/or have a lot of toots to delete, it may take a while as the script will pause when it hits a rate limit, until the required time has expired.
-
-## ASCII / utf-8 errors
+# ASCII / utf-8 errors
Prior to Python 3.7, running a Python script on some BSD and Linux systems may throw an error. This can be resolved by:
* setting a _locale_ that encodes utf-8, by using the environment setting `PYTHONIOENCODING=utf-8` when running the script, or
* upgrading your Python version to 3.7 or higher. See [Issue 11](https://github.com/hughrun/ephemetoot/issues/11) for more information.
+# Uninstalling
+
+Uninstall using pip;
+```
+pip uninstall ephemetoot
+```
+
# Bugs and suggestions
Please check existing [issues](https://github.com/hughrun/ephemetoot/issues) and if your issue is not already listed, create a new one with as much detail as possible (but don't include your access token!).
# Contributing
-Contributions are very welcome, but if you want to suggest any changes or improvements, please log an issue or have a chat to [me on Mastodon](https://ausglam.space/@hugh) _before_ lodging a pull request.
+Contributions are very welcome, but if you want to suggest any changes or improvements, please log an issue or have a chat to [me on Mastodon](https://ausglam.space/@hugh) _before_ making a pull request.
# License
diff --git a/bin/ephemetoot b/bin/ephemetoot
new file mode 100644
index 0000000..448ecbf
--- /dev/null
+++ b/bin/ephemetoot
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+
+# #####################################################################
+# Ephemetoot - A script to delete your old toots
+# Copyright (C) 2018 Hugh Rundle, 2019-2020 Hugh Rundle & Mark Eaton
+# Initial work based on tweet-deleting script by @flesueur
+# (https://gist.github.com/flesueur/bcb2d9185b64c5191915d860ad19f23f)
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+
+# You can contact Hugh on Mastodon @hugh@ausglam.space
+# or email hugh [at] hughrundle [dot] net
+# #####################################################################
+
+# import
+import yaml
+
+# from standard library
+from argparse import ArgumentParser
+import os
+
+# local files
+from lib import ephemetoot
+
+parser = ArgumentParser()
+parser.add_argument(
+ "--config", action="store", metavar="'filepath'", default="config.yaml", help="filepath of your config file, relative to the current directory. If no --config path is provided, ephemetoot will use 'config.yaml'."
+)
+parser.add_argument(
+ "--test", action="store_true", help="do a test run without deleting any toots"
+)
+
+options = parser.parse_args()
+if options.config[0] == '~':
+ config_file = os.path.expanduser(options.config)
+elif options.config[0] == '/':
+ config_file = options.config
+else:
+ config_file = os.path.join( os.getcwd(), options.config )
+
+if __name__ == "__main__":
+ with open(config_file) as config:
+ for accounts in yaml.safe_load_all(config):
+ for user in accounts:
+ ephemetoot.checkToots(user, options)
diff --git a/example-config.yaml b/example-config.yaml
new file mode 100644
index 0000000..9e651d6
--- /dev/null
+++ b/example-config.yaml
@@ -0,0 +1,42 @@
+# access_token : the access token from the app you created in Mastodon at Settings - Development
+# username : your username without the '@' or server domain.
+# base_url : the base url of your Mastodon server, without the 'https://'
+# days_to_keep : number of days to keep toots.
+# keep_pinned : either True or False - if True, any pinned toots will be kept
+# toots_to_keep : a List of toot ids indicating toots to be kept regardless of other settings
+# hashtags_to_keep : a Set of hashtags, where any toots with any of these hashtags will be kept. Do not include the '#' symbol
+# visibility_to_keep : any toots with visibility settings in this list will be kept. Options are: 'public', 'unlisted', 'private', 'direct'
+
+# you can list only one user, or multiple users
+# each user account should be preceded by a single dash, and indented, as per below
+-
+# ausglam.space account
+ access_token : ZA-Yj3aBD8U8Cm7lKUp-lm9O9BmDgdhHzDeqsY8tlL0
+ username : alice
+ base_url : ausglam.space
+ days_to_keep : 14
+ keep_pinned : True
+ toots_to_keep :
+ - 103996285277439262
+ - 103976473612749097
+ - 103877521458738491
+ hashtags_to_keep : !!set { python, glamblogclub }
+ visibility_to_keep :
+ - direct
+ - private
+-
+# aus.social account
+ access_token : AZ-Yj3aBD8U8Cm7lKUp-lm9O9BmDgdhHzDeqsY8tlL9
+ username : bob
+ base_url : aus.social
+ days_to_keep : 30
+ keep_pinned : False
+ # toots_to_keep can be empty
+ toots_to_keep :
+ -
+ # hashtags_to_keep can be empty
+ hashtags_to_keep : !!set { }
+ # visibility_to_keep can be empty
+ visibility_to_keep :
+ -
+
\ No newline at end of file
diff --git a/example.config.py b/example.config.py
deleted file mode 100644
index 5eaff47..0000000
--- a/example.config.py
+++ /dev/null
@@ -1,7 +0,0 @@
-access_token = 'YOUR_ACCESS_TOKEN_HERE'
-base_url = 'https://ausglam.space'
-days_to_keep = 30
-keep_pinned = True
-toots_to_keep = []
-hashtags_to_keep = {'introduction', 'announcement'} # comma separated Set as strings, e.g. 'introduction'
-visibility_to_keep = ['private', 'unlisted'] # options are: 'public', 'unlisted', 'private', 'direct'
diff --git a/lib/__init__.py b/lib/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/ephemetoot.py b/lib/ephemetoot.py
similarity index 63%
rename from ephemetoot.py
rename to lib/ephemetoot.py
index 4b92035..d9dde13 100644
--- a/ephemetoot.py
+++ b/lib/ephemetoot.py
@@ -1,68 +1,38 @@
-# #####################################################################
-# Ephemetoot - A script to delete your old toots
-# Copyright (C) 2018, 2020 Hugh Rundle, 2019 Hugh Rundle & Mark Eaton
-# Based partially on tweet-deleting script by @flesueur
-# (https://gist.github.com/flesueur/bcb2d9185b64c5191915d860ad19f23f)
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see .
-
-# You can contact Hugh on Mastodon @hugh@ausglam.space
-# or email hugh [at] hughrundle [dot] net
-# #####################################################################
-
-from argparse import ArgumentParser
-import config
import json
from mastodon import Mastodon, MastodonError
from datetime import datetime, timedelta, timezone
import time
-parser = ArgumentParser()
-parser.add_argument(
- "--test", action="store_true", help="do a test run without deleting any toots"
-)
-options = parser.parse_args()
-if options.test:
- print("This is a test run...")
+def checkToots(config, options, deleted_count=0):
+ if options.test:
+ print("This is a test run...")
+ print("Fetching account details for @" + config['username'] + "@" + config['base_url'] + "...")
+ mastodon = Mastodon(
+ access_token=config['access_token'],
+ api_base_url="https://" + config['base_url'],
+ ratelimit_method="wait",
+ )
-print("Fetching account details...")
+ cutoff_date = datetime.now(timezone.utc) - timedelta(days=config['days_to_keep'])
+ user_id = mastodon.account_verify_credentials().id
+ account = mastodon.account(user_id)
+ timeline = mastodon.account_statuses(user_id, limit=40)
-mastodon = Mastodon(
- access_token=config.access_token,
- api_base_url=config.base_url,
- ratelimit_method="wait",
-)
+ print("Checking " + str(account.statuses_count) + " toots...")
-cutoff_date = datetime.now(timezone.utc) - timedelta(days=config.days_to_keep)
-user_id = mastodon.account_verify_credentials().id
-timeline = mastodon.account_statuses(user_id, limit=40)
-
-
-def checkToots(timeline, deleted_count=0):
for toot in timeline:
toot_tags = set()
for tag in toot.tags:
toot_tags.add(tag.name)
try:
- if config.keep_pinned and hasattr(toot, "pinned") and toot.pinned:
+ if config['keep_pinned'] and hasattr(toot, "pinned") and toot.pinned:
print("📌 skipping pinned toot - " + str(toot.id))
- elif toot.id in config.toots_to_keep:
+ elif toot.id in config['toots_to_keep']:
print("💾 skipping saved toot - " + str(toot.id))
- elif toot.visibility in config.visibility_to_keep:
+ elif toot.visibility in config['visibility_to_keep']:
print("👀 skipping " + toot.visibility + " toot - " + str(toot.id))
- elif len(config.hashtags_to_keep.intersection(toot_tags)) > 0:
+ elif len(config['hashtags_to_keep'].intersection(toot_tags)) > 0:
print("#️⃣ skipping toot with hashtag - " + str(toot.id))
elif cutoff_date > toot.created_at:
if hasattr(toot, "reblog") and toot.reblog:
@@ -137,11 +107,4 @@ def checkToots(timeline, deleted_count=0):
else:
print("Removed " + str(deleted_count) + " toots.")
except IndexError:
- print("No toots found!")
-
-
-# trigger from here
-if __name__ == "__main__":
- account = mastodon.account(user_id)
- print("Checking " + str(account.statuses_count) + " toots...")
- checkToots(timeline)
+ print("No toots found!")
\ No newline at end of file
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..4a17b17
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,19 @@
+from setuptools import setup, find_packages
+
+setup(name='ephemetoot',
+ version='2.0.0',
+ url='https://github.com/hughrun/ephemetoot',
+ license='GPL-3.0-or-later',
+ packages=find_packages(),
+ scripts=['bin/ephemetoot'],
+ install_requires=['Mastodon.py', 'pyyaml'],
+ zip_safe=False,
+ author='Hugh Rundle',
+ author_email='hugh@hughrundle.net',
+ description='A command line tool for selectively deleting old toots.',
+ keywords='mastodon, mastodon api',
+ project_urls={
+ 'Source Code': 'https://github.com/hughrun/ephemetoot',
+ 'Documentation': 'https://github.com/hughrun/ephemetoot'
+ }
+)
\ No newline at end of file