Browse Source

Update project to use YAML for configs; code cleanup/overhaul

bot_curator 20170708.1
KemoNine 4 years ago
parent
commit
f6a3fc7fab
  1. 17
      README.md
  2. 40
      configs/example_generic_config.yaml
  3. 23
      configs/example_reminder.ini
  4. 30
      configs/example_reminder.yaml
  5. 40
      configs/example_reminder_art_with_open_source.ini
  6. 37
      configs/example_reminder_art_with_open_source.yaml
  7. 26
      configs/example_reminder_local_timeline.ini
  8. 23
      configs/example_reminder_local_timeline.yaml
  9. 21
      configs/example_reminder_metrics_stats_follow_counts.ini
  10. 18
      configs/example_reminder_metrics_stats_follow_counts.yaml
  11. 19
      configs/example_reminder_mobile_clients.ini
  12. 18
      configs/example_reminder_mobile_clients.yaml
  13. 19
      configs/example_reminder_not_here_to_judge.ini
  14. 20
      configs/example_reminder_not_here_to_judge.yaml
  15. 29
      configs/example_reminder_tips_tricks.ini
  16. 47
      configs/example_reminder_tips_tricks.yaml
  17. 32
      configs/example_reminder_trust_opsec.ini
  18. 47
      configs/example_reminder_trust_opsec.yaml
  19. 21
      configs/example_reminder_what_is_this_place.ini
  20. 19
      configs/example_reminder_what_is_this_place.yaml
  21. 52
      configs/example_reminder_whats_your_art.ini
  22. 56
      configs/example_reminder_whats_your_art.yaml
  23. 26
      configs/example_rss.ini
  24. 27
      configs/example_rss.yaml
  25. 30
      configs/example_rss_custom_logic.ini
  26. 27
      configs/example_rss_custom_logic.yaml
  27. 1
      requirements.txt
  28. 427
      toot_bot.py

17
README.md

@ -15,28 +15,29 @@ This was designed with the admin in mind and functionality will grow over time.
- Python 3.x
- Mastodon.py Python module
- feedparser Python Module
- pyyaml Python module
- argparse Python module (should be built in)
- configparser Python module (should be built in)
- sqlite3 Python module (should be built in)
# Installation
1. Install the dependencies: ```pip3 install -r requirements.txt```
1. Run the bot: ```python .\toot_bot.py --config .\configs\config.ini [init|login|toot|rss]```
1. Run the bot: ```python .\toot_bot.py --config .\configs\config.yaml [init|login|toot|rss]```
# Tooting
## Reminder Toots
To send reminder toots take a look at 'configs/example_reminder.ini' and do the following
1. Run python ```.\toot_bot.py --config .\configs\example_reminder.ini init```
1. Run python ```.\toot_bot.py --config .\configs\example_reminder.ini toot```
To send reminder toots take a look at 'configs/example_reminder.yaml' and do the following
1. Run python ```.\toot_bot.py --config .\configs\example_reminder.yaml init```
1. Run python ```.\toot_bot.py --config .\configs\example_reminder.yaml toot```
The above will initialize the configuration (including login, you'll be prompted for user/password) and then toot what has been setup in your config file.
You'll probably want to run the 2nd command on a schedule via cron for auto-tooting on a schedule
## RSS Toots
To cross post RSS articles take a look at 'configs/example_rss.ini' and do the following
1. Run python ```.\toot_bod.py --config .\configs\example_rss.ini init```
1. Run python ```.\toot_bot.py --config .\configs\example_rss.ini rss```
To cross post RSS articles take a look at 'configs/example_rss.yaml' and do the following
1. Run python ```.\toot_bod.py --config .\configs\example_rss.yaml init```
1. Run python ```.\toot_bot.py --config .\configs\example_rss.yaml rss```
The above will initialize the configuration (including login, you'll be prompted for user/password) then toot the various articles from the RSS feed. *BE CAREFUL*, the cache will be empty to start and you might spam your instance if there are a large number of articles in the feed.
@ -44,7 +45,7 @@ Note: the RSS cache will be initialized the first time you run the rss command
You'll probably want to run the 2nd command on a schedule via cron for cross posting on a schedule
You can also implement custom logic for filtering RSS feeds, take a look at ```configs/example_rss_custom_logic.ini``` and ```custom_logic/example_custom_rss_include.py``` for ideas.
You can also implement custom logic for filtering RSS feeds, take a look at ```configs/example_rss_custom_logic.yaml``` and ```custom_logic/example_custom_rss_include.py``` for ideas.
## TOTP
If you're using a bot with an account that has 2FA / TOTP enabled you can pass the ```--totp``` option to ```init``` and ```login``` to make the init and login functions work correctly. There will be additional steps and you'll be prompted to complete the necessary steps.

40
configs/example_generic_config.yaml

@ -0,0 +1,40 @@
# Setup general config for the bot (registered name, instance url, credential cache)
# This section is REQUIRED
config:
app_name: name of app in web ui
api_base_url: https://instance.com
client_cred_file: /home/mastodon/bot/client_cred.secret
user_cred_file: /home/mastodon/bot/user_cred.secret
# Setup the RSS feed to watch
# This section is OPTIONAL
rss:
feed: https://instance.com/users/admin.atom
cache_file: /home/mastodon/bot/rss_cache.db # Path to sqlite database that caches seen articles
custom_logic_include_file: /home/mastodon/bot/custom_rss_logic.py # This is optional, see custom_logic folder for samples
# Setup the toots that will be sent
# These static toots will ALWAYS be sent (even in RSS mode)
# This section is REQUIRED
toot:
visibility: public # One of: public, unlisted, private, or direct
cw_text: this is the CW text of the toot (optional)
toot_text: |
this is the body of the toot (required)
If RSS directive is specified the following special variables can be used as placeholders for information about the RSS article
- [[ title ]]
- [[ url ]]
- [[ published ]]
subtoots: # This section is OPTIONAL
- visibility: unlisted
toot_text: |
Toots can be one line too without CW
- visibility: unlisted
cw_text: Yet another sub toot
toot_text: |
Toots and subtoots can span multiple lines
#WithHashTags
And 😊 (unicode characters) #FunStuffRightHere

23
configs/example_reminder.ini

@ -1,23 +0,0 @@
[config]
# Define a static toot that's send via the bot
# Most useful for a single toot that's send on a regular basis as a reminder
# Think generic follow friday, weekly reminders, etc
# The name of the app that's registered on the instance
app_name = reminder_bot
# The base URL for the instance
api_base_url = https://instance.com
# Where to store the client secret for the bot (THIS NEEDS TO BE SECURE!)
client_cred_file = /root/reminder_bot/reminder_bot.secret
# Where to store the user credentials for the bot (THIS NEEDS TO BE SECURE!)
user_cred_file = /root/reminder_bot/reminder_bot_user_cred.secret
[toots]
#Values can span multiple lines as long as they are indented more than the key that holds them
# Sub toot's are optional elements, specify more than one (following naming convention) for multiple sub-toots to the main toot
cw_text = This is a friendly reminder! awoooooooooo
main_toot = Reminder!
There is a blog with announcements at https://blog.instance.com
subtoot_1 = subtoot 1
subtoot_2 = subtoot 2
with a second line!!!

30
configs/example_reminder.yaml

@ -0,0 +1,30 @@
# Setup general config for the bot (registered name, instance url, credential cache)
# This section is REQUIRED
config:
app_name: example reminder bot
api_base_url: https://instance.com
client_cred_file: /home/mastodon/bot/client_cred.secret
user_cred_file: /home/mastodon/bot/user_cred.secret
toot:
visibility: public # One of: public, unlisted, private, or direct
cw_text: this is the CW text of the toot (optional)
toot_text: |
this is the body of the toot (required)
If RSS directive is specified the following special variables can be used as placeholders for information about the RSS article
- [[ title ]]
- [[ url ]]
- [[ published ]]
subtoots: # This section is OPTIONAL
- visibility: unlisted
toot_text: |
Toots can be one line too without CW
- visibility: unlisted
cw_text: Yet another sub toot
toot_text: |
Toots and subtoots can span multiple lines
#WithHashTags
And 😊 (unicode characters) #FunStuffRightHere

40
configs/example_reminder_art_with_open_source.ini

@ -1,40 +0,0 @@
[config]
# Define a static toot that's send via the bot
# Most useful for a single toot that's send on a regular basis as a reminder
# Think generic follow friday, weekly reminders, etc
# The name of the app that's registered on the instance
app_name = reminder_bot
# The base URL for the instance
api_base_url = https://instance.com
# Where to store the client secret for the bot (THIS NEEDS TO BE SECURE!)
client_cred_file = /root/reminder_bot/reminder_bot.secret
# Where to store the user credentials for the bot (THIS NEEDS TO BE SECURE!)
user_cred_file = /root/reminder_bot/reminder_bot_user_cred.secret
[toots]
#Values can span multiple lines as long as they are indented more than the key that holds them
# Sub toot's are optional elements, specify more than one (following naming convention) for multiple sub-toots to the main toot
cw_text = Art With Open Source
main_toot = Are you a #Creative #Artist in the most general sense of the word?
🎨 ✏️ 🎼 📽️ 🎮 📸❣️
Do you use
#Krita
#Gimp
#Ardour
#Blender
#Plume
#Darktable
#LMMS
#Godot
#Twine
#KdenLive
or other #FLOSS / #OpenSource software
as part of your workflow?
Do you use #Linux as your OS when creating/performing?
Consider tagging:
🐧 #ArtWithOpenSource #LinuxArtists #LinuxArtist

37
configs/example_reminder_art_with_open_source.yaml

@ -0,0 +1,37 @@
# Setup general config for the bot (registered name, instance url, credential cache)
# This section is REQUIRED
config:
app_name: art with open source reminder bot
api_base_url: https://instance.com
client_cred_file: /home/mastodon/bot/client_cred.secret
user_cred_file: /home/mastodon/bot/user_cred.secret
# Setup the toots that will be sent
# These static toots will ALWAYS be sent (even in RSS mode)
# This section is REQUIRED
toot:
visibility: public # One of: public, unlisted, private, or direct
cw_text: Art With Open Source
toot_text: |
Are you a #Creative #Artist in the most general sense of the word?
🎨 ✏️ 🎼 📽️ 🎮 📸❣️
Do you use
#Krita
#Gimp
#Ardour
#Blender
#Plume
#Darktable
#LMMS
#Godot
#Twine
#KdenLive
or other #FLOSS / #OpenSource software
as part of your workflow?
Do you use #Linux as your OS when creating/performing?
Consider tagging:
🐧 #ArtWithOpenSource #LinuxArtists #LinuxArtist

26
configs/example_reminder_local_timeline.ini

@ -1,26 +0,0 @@
[config]
# Define a static toot that's send via the bot
# Most useful for a single toot that's send on a regular basis as a reminder
# Think generic follow friday, weekly reminders, etc
# The name of the app that's registered on the instance
app_name = reminder_bot
# The base URL for the instance
api_base_url = https://instance.com
# Where to store the client secret for the bot (THIS NEEDS TO BE SECURE!)
client_cred_file = /root/reminder_bot/reminder_bot.secret
# Where to store the user credentials for the bot (THIS NEEDS TO BE SECURE!)
user_cred_file = /root/reminder_bot/reminder_bot_user_cred.secret
[toots]
#Values can span multiple lines as long as they are indented more than the key that holds them
# Sub toot's are optional elements, specify more than one (following naming convention) for multiple sub-toots to the main toot
cw_text = 🎨🐘 Post Public & Not Unlisted to 🦈Feed Local Timeline
main_toot = 🦈 Feed the Local Timeline y'all by checking your default post privacy settings!
Post that toot Public & your content feeds 🦈 & inspires the Local & Federated timelines to be DISCOVERED by new fans & friends to Boost/Discuss/Star.
🤐 Unlisted toots will only be seen by your followers.
@ replies also won't be seen on Local & only seen by others 'if' your followers have "show @'s" turned on within the Home Feed.
#MastoTip #Mastodon

23
configs/example_reminder_local_timeline.yaml

@ -0,0 +1,23 @@
# Setup general config for the bot (registered name, instance url, credential cache)
# This section is REQUIRED
config:
app_name: public timeline reminder bot
api_base_url: https://instance.com
client_cred_file: /home/mastodon/bot/client_cred.secret
user_cred_file: /home/mastodon/bot/user_cred.secret
# Setup the toots that will be sent
# These static toots will ALWAYS be sent (even in RSS mode)
# This section is REQUIRED
toot:
visibility: public # One of: public, unlisted, private, or direct
cw_text: 🎨🐘 Post Public & Not Unlisted to 🦈Feed Local Timeline
toot_text: |
🦈 Feed the Local Timeline y'all by checking your default post privacy settings!
Post that toot Public & your content feeds 🦈 & inspires the Local & Federated timelines to be DISCOVERED by new fans & friends to Boost/Discuss/Star.
🤐 Unlisted toots will only be seen by your followers.
@ replies also won't be seen on Local & only seen by others 'if' your followers have "show @'s" turned on within the Home Feed.
#MastoTip #Mastodon

21
configs/example_reminder_metrics_stats_follow_counts.ini

@ -1,21 +0,0 @@
[config]
# Define a static toot that's send via the bot
# Most useful for a single toot that's send on a regular basis as a reminder
# Think generic follow friday, weekly reminders, etc
# The name of the app that's registered on the instance
app_name = reminder_bot
# The base URL for the instance
api_base_url = https://instance.com
# Where to store the client secret for the bot (THIS NEEDS TO BE SECURE!)
client_cred_file = /root/reminder_bot/reminder_bot.secret
# Where to store the user credentials for the bot (THIS NEEDS TO BE SECURE!)
user_cred_file = /root/reminder_bot/reminder_bot_user_cred.secret
[toots]
#Values can span multiple lines as long as they are indented more than the key that holds them
# Sub toot's are optional elements, specify more than one (following naming convention) for multiple sub-toots to the main toot
cw_text = Metrics/Stats/Follow Counts
main_toot = Mastodon is an OSS network that's bucked the trend of statistics and metrics. Follow counts are available but purposefully buried. Metrics and Stats might show up in the odd place but they are intentionally 'not there'. It's a network of not caring about metrics/stats and enjoying the people present as-is. Penis measuring contests aren't a thing here.
Sit back, follow some people, enjoy the Timelines and random engagement.

18
configs/example_reminder_metrics_stats_follow_counts.yaml

@ -0,0 +1,18 @@
# Setup general config for the bot (registered name, instance url, credential cache)
# This section is REQUIRED
config:
app_name: metrics stats follow counts reminder bot
api_base_url: https://instance.com
client_cred_file: /home/mastodon/bot/client_cred.secret
user_cred_file: /home/mastodon/bot/user_cred.secret
# Setup the toots that will be sent
# These static toots will ALWAYS be sent (even in RSS mode)
# This section is REQUIRED
toot:
visibility: public # One of: public, unlisted, private, or direct
cw_text: Metrics/Stats/Follow Counts
toot_text: |
Mastodon is an OSS network that's bucked the trend of statistics and metrics. Follow counts are available but purposefully buried. Metrics and Stats might show up in the odd place but they are intentionally 'not there'. It's a network of not caring about metrics/stats and enjoying the people present as-is. Penis measuring contests aren't a thing here.
Sit back, follow some people, enjoy the Timelines and random engagement.

19
configs/example_reminder_mobile_clients.ini

@ -1,19 +0,0 @@
[config]
# Define a static toot that's send via the bot
# Most useful for a single toot that's send on a regular basis as a reminder
# Think generic follow friday, weekly reminders, etc
# The name of the app that's registered on the instance
app_name = reminder_bot
# The base URL for the instance
api_base_url = https://instance.com
# Where to store the client secret for the bot (THIS NEEDS TO BE SECURE!)
client_cred_file = /root/reminder_bot/reminder_bot.secret
# Where to store the user credentials for the bot (THIS NEEDS TO BE SECURE!)
user_cred_file = /root/reminder_bot/reminder_bot_user_cred.secret
[toots]
#Values can span multiple lines as long as they are indented more than the key that holds them
# Sub toot's are optional elements, specify more than one (following naming convention) for multiple sub-toots to the main toot
cw_text = Mobile Clients -- IMPORTANT
main_toot = Some mobile clients default posts to 'Unlisted'. This means your toots will NOT be seen by anyone other than your followers. If you're new to Mastodon you should consider posting as 'Public' for awhile. This will put your toots on the Local Timeline of your instance and will help drive engagement. There are a lot of people here watching the Local Timeline but if your toots aren't public we won't see them.

18
configs/example_reminder_mobile_clients.yaml

@ -0,0 +1,18 @@
# Setup general config for the bot (registered name, instance url, credential cache)
# This section is REQUIRED
config:
app_name: mobile client reminder bot
api_base_url: https://instance.com
client_cred_file: /home/mastodon/bot/client_cred.secret
user_cred_file: /home/mastodon/bot/user_cred.secret
# Setup the toots that will be sent
# These static toots will ALWAYS be sent (even in RSS mode)
# This section is REQUIRED
toot:
visibility: public # One of: public, unlisted, private, or direct
cw_text: Mobile Clients -- IMPORTANT
toot_text: |
Some mobile clients default posts to 'Unlisted'. This means your toots will NOT be seen by anyone other than your followers. If you're new to Mastodon you should consider posting as 'Public' for awhile. This will put your toots on the Local Timeline of your instance and will help drive engagement. There are a lot of people here watching the Local Timeline but if your toots aren't public we won't see them.
#MastoTip #Mastodon

19
configs/example_reminder_not_here_to_judge.ini

@ -1,19 +0,0 @@
[config]
# Define a static toot that's send via the bot
# Most useful for a single toot that's send on a regular basis as a reminder
# Think generic follow friday, weekly reminders, etc
# The name of the app that's registered on the instance
app_name = reminder_bot
# The base URL for the instance
api_base_url = https://instance.com
# Where to store the client secret for the bot (THIS NEEDS TO BE SECURE!)
client_cred_file = /root/reminder_bot/reminder_bot.secret
# Where to store the user credentials for the bot (THIS NEEDS TO BE SECURE!)
user_cred_file = /root/reminder_bot/reminder_bot_user_cred.secret
[toots]
#Values can span multiple lines as long as they are indented more than the key that holds them
# Sub toot's are optional elements, specify more than one (following naming convention) for multiple sub-toots to the main toot
cw_text = I'm not here to judge. I'm here to :clap:, :dancer: and :drool: over everyone's creations and supplies.
main_toot = I'm not here to judge. I'm here to :clap:, :dancer: and :drool: over everyone's awesome, wonderful, insightful and amazing toots.

20
configs/example_reminder_not_here_to_judge.yaml

@ -0,0 +1,20 @@
# Setup general config for the bot (registered name, instance url, credential cache)
# This section is REQUIRED
config:
app_name: not here judge reminder bot
api_base_url: https://instance.com
client_cred_file: /home/mastodon/bot/client_cred.secret
user_cred_file: /home/mastodon/bot/user_cred.secret
# Setup the toots that will be sent
# These static toots will ALWAYS be sent (even in RSS mode)
# This section is REQUIRED
toot:
visibility: public # One of: public, unlisted, private, or direct
cw_text: I'm not here to judge.
toot_text: |
I'm here to :clap:, :dancer: and :drool:
over everyone's creations and supplies.
over everyone's awesome, wonderful, insightful and amazing toots.

29
configs/example_reminder_tips_tricks.ini

@ -1,29 +0,0 @@
[config]
# Define a static toot that's send via the bot
# Most useful for a single toot that's send on a regular basis as a reminder
# Think generic follow friday, weekly reminders, etc
# The name of the app that's registered on the instance
app_name = reminder_bot
# The base URL for the instance
api_base_url = https://instance.com
# Where to store the client secret for the bot (THIS NEEDS TO BE SECURE!)
client_cred_file = /root/reminder_bot/reminder_bot.secret
# Where to store the user credentials for the bot (THIS NEEDS TO BE SECURE!)
user_cred_file = /root/reminder_bot/reminder_bot_user_cred.secret
[toots]
#Values can span multiple lines as long as they are indented more than the key that holds them
# Sub toot's are optional elements, specify more than one (following naming convention) for multiple sub-toots to the main toot
cw_text = Some #TipsAndTricks
main_toot = Some #TipsAndTricks for y'all. Expand this status to view 'em.
subtoot_1 = Some mobile clients default posts to 'Unlisted'. This means your toots will NOT be seen by anyone other than your followers. If you're new to Mastodon you should consider posting as 'Public' for awhile. This will put your toots on the Local Timeline of your instance and will help drive engagement. There are a lot of people here watching the Local Timeline but if your toots aren't public we won't see them.
subtoot_2 = You can lower character counts via copy/paste of an emoji instead of using the emoji selector. http://emojipedia.org/ has very common/standard emoji's to use for copy/paste
subtoot_3 = You can see ALL #Art & #MastoArt a user has posted by clicking the :hamburger: menu ≡ on their online profiles.
subtoot_4 = YOU CAN DELETE YOUR ACCOUNT!
(if your instance runs 1.4.3 or higher)
1) Go to *Settings*.
2) Click *Credentials*.
3) Look under the *Delete account* section, and click the "proceed here" link.
4) Confirm account deletion when asked.

47
configs/example_reminder_tips_tricks.yaml

@ -0,0 +1,47 @@
# Setup general config for the bot (registered name, instance url, credential cache)
# This section is REQUIRED
config:
app_name: tips tricks reminder bot
api_base_url: https://instance.com
client_cred_file: /home/mastodon/bot/client_cred.secret
user_cred_file: /home/mastodon/bot/user_cred.secret
# Setup the toots that will be sent
# These static toots will ALWAYS be sent (even in RSS mode)
# This section is REQUIRED
toot:
visibility: public # One of: public, unlisted, private, or direct
cw_text: "Some #TipsAndTricks"
toot_text: |
Some #TipsAndTricks for y'all. Expand this status to view 'em.
subtoots: # This section is OPTIONAL
- visibility: unlisted
cw_text: Mobile Clients - Default Privacy Settings
toot_text: |
Some mobile clients default posts to 'Unlisted'. This means your toots will NOT be seen by anyone other than your followers. If you're new to Mastodon you should consider posting as 'Public' for awhile. This will put your toots on the Local Timeline of your instance and will help drive engagement. There are a lot of people here watching the Local Timeline but if your toots aren't public we won't see them.
#MastoTip #Mastodon
- visibility: unlisted
cw_text: Character Counts
toot_text: |
You can lower charater counts via copy/paste of an emoji instead of using the emoji selector. http://emojipedia.org/ has very common/standard emoji's to use for copy/paste
#MastoTip #Mastodon
- visibility: unlisted
cw_text: Media Browsing
toot_text: |
You can see ALL #Art & #MastoArt a user has posted by clicking the :hamburger: menu ≡ on their online profiles.
#MastoTip #Mastodon
- visibility: unlisted
cw_text: Account Deletion
toot_text: |
YOU CAN DELETE YOUR ACCOUNT!
(if your instance runs 1.4.3 or higher)
1) Go to *Settings*.
2) Click *Credentials*.
3) Look under the *Delete account* section, and click the "proceed here" link.
4) Confirm account deletion when asked.
#MastoTip #Mastodon

32
configs/example_reminder_trust_opsec.ini

@ -1,32 +0,0 @@
[config]
# Define a static toot that's send via the bot
# Most useful for a single toot that's send on a regular basis as a reminder
# Think generic follow friday, weekly reminders, etc
# The name of the app that's registered on the instance
app_name = reminder_bot
# The base URL for the instance
api_base_url = https://instance.com
# Where to store the client secret for the bot (THIS NEEDS TO BE SECURE!)
client_cred_file = /root/reminder_bot/reminder_bot.secret
# Where to store the user credentials for the bot (THIS NEEDS TO BE SECURE!)
user_cred_file = /root/reminder_bot/reminder_bot_user_cred.secret
[toots]
#Values can span multiple lines as long as they are indented more than the key that holds them
# Sub toot's are optional elements, specify more than one (following naming convention) for multiple sub-toots to the main toot
cw_text = Trust & OpSec - Think About This
main_toot = For those concerned about privacy, harassment and dumpster fires: expand this status for some REALLY IMPORTANT CONSIDERATIONS when using Mastodon.
#OpSec #InfoSec #Trust #TheMoreYouKnow #KnowledgeIsPower
subtoot_1 = The admins/mods of your home instance CAN view your private toots (DM's). They also have FULL insight into registration e-mail, IP address(es), reports (whenever you report a post/user), follow/block/mute lists and ALL messages. They can even reset your account password and other fun stuff via direct database access.
subtoot_2 = If you send a toot to another instance the admin/mods of the remote instance CAN see the message (even if DM) and other metadata (IP/destination user/etc). The good news is remote instance insights will be limited to the toot received by their instance and basic account info (what we call can see publicly). Regardless, be VERY MINDFUL of toots that you send outside of your home instance.
subtoot_3 = Two factor auth (#2FA) can be enabled via your account settings. It's TOTP based and you can use apps like Google Authenticator and others to generate the tokens. If you're concerned about someone hacking your account this is a great way to add another layer of safety.
subtoot_4 = YOU CAN DELETE YOUR ACCOUNT!
(if your instance runs 1.4.3 or higher)
1) Go to *Settings*.
2) Click *Credentials*.
3) Look under the *Delete account* section, and click the "proceed here" link.
4) Confirm account deletion when asked.
subtoot_5 = TRUST is IMPORTANT. Be safe out there in the federverse

47
configs/example_reminder_trust_opsec.yaml

@ -0,0 +1,47 @@
# Setup general config for the bot (registered name, instance url, credential cache)
# This section is REQUIRED
config:
app_name: trust opsec reminder bot
api_base_url: https://instance.com
client_cred_file: /home/mastodon/bot/client_cred.secret
user_cred_file: /home/mastodon/bot/user_cred.secret
# Setup the toots that will be sent
# These static toots will ALWAYS be sent (even in RSS mode)
# This section is REQUIRED
toot:
visibility: public # One of: public, unlisted, private, or direct
cw_text: Trust & OpSec - Please Think About This
toot_text: |
For those concerned about privacy, harassment and dumpster fires...
Expand this status for some REALLY IMPORTANT CONSIDERATIONS when using Mastodon.
#OpSec #InfoSec #Trust #TheMoreYouKnow #KnowledgeIsPower
subtoots: # This section is OPTIONAL
- visibility: unlisted
cw_text: Admins/Mods and Trust
toot_text: |
The admins/mods of your home instance CAN view your private toots (DM's). They also have FULL insight into registration e-mail, IP address(es), reports (whenever you report a post/user), follow/block/mute lists and ALL messages. They can even reset your account password and other fun stuff via direct database access.
- visibility: unlisted
cw_text: Trusting Federated Instances
toot_text: |
If you send a toot to another instance the admin/mods of the remote instance CAN see the message (even if DM) and other metadata (IP/destination user/etc). The good news is remote instance insights will be limited to the toot received by their instance and basic account info (what we call can see publicly). Regardless, be VERY MINDFUL of toots that you send outside of your home instance.
- visibility: unlisted
cw_text: Two Factor Auth (2FA/TOTP) Considerations
toot_text: |
Two factor auth (#2FA) can be enabled via your account settings. It's TOTP based and you can use apps like Google Authenticator and others to generate the tokens. If you're concerned about someone hacking your account this is a great way to add another layer of safety.
- visibility: unlisted
cw_text: Account Deletions
toot_text: |
YOU CAN DELETE YOUR ACCOUNT!
(if your instance runs 1.4.3 or higher)
1) Go to *Settings*.
2) Click *Credentials*.
3) Look under the *Delete account* section, and click the "proceed here" link.
4) Confirm account deletion when asked.
- visibility: unlisted
cw_text: TRUST is IMPORTANT!
toot_text: |
Be safe out there in the federverse

21
configs/example_reminder_what_is_this_place.ini

@ -1,21 +0,0 @@
[config]
# Define a static toot that's send via the bot
# Most useful for a single toot that's send on a regular basis as a reminder
# Think generic follow friday, weekly reminders, etc
# The name of the app that's registered on the instance
app_name = reminder_bot
# The base URL for the instance
api_base_url = https://instance.com
# Where to store the client secret for the bot (THIS NEEDS TO BE SECURE!)
client_cred_file = /root/reminder_bot/reminder_bot.secret
# Where to store the user credentials for the bot (THIS NEEDS TO BE SECURE!)
user_cred_file = /root/reminder_bot/reminder_bot_user_cred.secret
[toots]
#Values can span multiple lines as long as they are indented more than the key that holds them
# Sub toot's are optional elements, specify more than one (following naming convention) for multiple sub-toots to the main toot
cw_text = What is this place, I'm confused...
main_toot = If you're having trouble finding your way about Mastodon, @ginnymcqueen@kitty.town has put together some great info. Check it out: https://medium.com/@GinnyMcQueen/toot-how-to-intro-to-mastodon-e5655bfa87d2
There is also a wiki over at https://en.mstdn.wiki/Main_Page with some very good information too.

19
configs/example_reminder_what_is_this_place.yaml

@ -0,0 +1,19 @@
# Setup general config for the bot (registered name, instance url, credential cache)
# This section is REQUIRED
config:
app_name: what is this place reminder bot
api_base_url: https://instance.com
client_cred_file: /home/mastodon/bot/client_cred.secret
user_cred_file: /home/mastodon/bot/user_cred.secret
# Setup the toots that will be sent
# These static toots will ALWAYS be sent (even in RSS mode)
# This section is REQUIRED
toot:
visibility: public # One of: public, unlisted, private, or direct
cw_text: What is this place, I'm confused...
toot_text: |
If you're having trouble finding your way about Mastodon, @ginnymcqueen@kitty.town has put together some great info.
Check it out: https://medium.com/@GinnyMcQueen/toot-how-to-intro-to-mastodon-e5655bfa87d2
There is also a wiki over at https://en.mstdn.wiki/Main_Page with some very good information

52
configs/example_reminder_whats_your_art.ini

@ -1,52 +0,0 @@
[config]
# Define a static toot that's send via the bot
# Most useful for a single toot that's send on a regular basis as a reminder
# Think generic follow friday, weekly reminders, etc
# The name of the app that's registered on the instance
app_name = reminder_bot
# The base URL for the instance
api_base_url = https://instance.com
# Where to store the client secret for the bot (THIS NEEDS TO BE SECURE!)
client_cred_file = /root/reminder_bot/reminder_bot.secret
# Where to store the user credentials for the bot (THIS NEEDS TO BE SECURE!)
user_cred_file = /root/reminder_bot/reminder_bot_user_cred.secret
[toots]
#Values can span multiple lines as long as they are indented more than the key that holds them
# Sub toot's are optional elements, specify more than one (following naming convention) for multiple sub-toots to the main toot
cw_text = What's your ART‽ #️⃣ Cheatsheet v3.1
main_toot = The only way others can find your work is with a proper hashtag. Expand this status for some awesome hash tags to explore and use. 😀
#CreativeToots #MastoTags
subtoot_1 = 🎨#Art #MastoArt #MixedMedia #ConceptualArt #ConceptArt #RelationArt #MyArt
🐧#ArtWithOpenSource #LinuxArtist #LinuxArtists
‍🌈#TransCreator
📸#Photography #Photo #Photog #Photographie
✏️#Writing #Sketch #Poetry #Drawing #AmWriting
🖌️#Illustration #Typography
🗜️#Maker
🌏#ArtWithOpenSource
🎥#Cinematography
👾#PixelArt #GameDev #GlitchArt
🎶#Music #MastoMusic #Singing
🔪#Carving #Sculpture
🖥️ #UX #IxD #3D #Digital
🖼️#Painting
🎂🌮 #Cooking
💡#WIP
💬#CritiqueRequested #Critique #Feedback
subtoot_2 = 📚#Tootfic #Mfic #Mastofic
🔖#Collecting
❄️#Mandala
🍍🕹️#Gaming
🌀#Fractal
🎭#Performance #Theater #Improv #Circus #SpokenWord
💍#Jewelry
📍#Knitting
💃#Dancing #Dance #Ballet
🌱#Gardening
🌲#LandArt #LandscapeArt
✈️#SkyWriting
🏂#Sport
❣️Others‽

56
configs/example_reminder_whats_your_art.yaml

@ -0,0 +1,56 @@
# Setup general config for the bot (registered name, instance url, credential cache)
# This section is REQUIRED
config:
app_name: whats your art reminder bot
api_base_url: https://instance.com
client_cred_file: /home/mastodon/bot/client_cred.secret
user_cred_file: /home/mastodon/bot/user_cred.secret
# Setup the toots that will be sent
# These static toots will ALWAYS be sent (even in RSS mode)
# This section is REQUIRED
toot:
visibility: public # One of: public, unlisted, private, or direct
cw_text: "What's your ART‽ #️⃣ Cheatsheet v3.1"
toot_text: |
The only way others can find your work is with a proper hashtag. Expand this status for some awesome hash tags to explore and use. 😀
#CreativeToots #MastoTags
subtoots: # This section is OPTIONAL
- visibility: unlisted
cw_text: Creative MastoTags - Part 1
toot_text: |
🎨#Art #MastoArt #MixedMedia #ConceptualArt #ConceptArt #RelationArt #MyArt
🐧#ArtWithOpenSource #LinuxArtist #LinuxArtists
‍🌈#TransCreator
📸#Photography #Photo #Photog #Photographie
✏️#Writing #Sketch #Poetry #Drawing #AmWriting
🖌️#Illustration #Typography
🗜️#Maker
🌏#ArtWithOpenSource
🎥#Cinematography
👾#PixelArt #GameDev #GlitchArt
🎶#Music #MastoMusic #Singing
🔪#Carving #Sculpture
🖥️ #UX #IxD #3D #Digital
🖼️#Painting
💡#WIP
💬#CritiqueRequested #Critique #Feedback
- visibility: unlisted
cw_text: Creative MastoTags - Part 2
toot_text: |
📚#Tootfic #Mfic #Mastofic
🔖#Collecting
❄️#Mandala
🍍🕹️#Gaming
🌀#Fractal
🎭#Performance #Theater #Improv #Circus #SpokenWord
💍#Jewelry
📍#Knitting
🎂🌮 #Cooking
💃#Dancing #Dance #Ballet
🌱#Gardening
🌲#LandArt #LandscapeArt
✈️#SkyWriting
🏂#Sport
❣️Others‽

26
configs/example_rss.ini

@ -1,26 +0,0 @@
[config]
# Define a static toot that's send via the bot
# Most useful for a single toot that's send on a regular basis as a reminder
# Think generic follow friday, weekly reminders, etc
# The name of the app that's registered on the instance
app_name = rss_bot
# The base URL for the instance
api_base_url = https://instance.com
# Where to store the client secret for the bot (THIS NEEDS TO BE SECURE!)
client_cred_file = /root/rss_bot/rss_bot.secret
# Where to store the user credentials for the bot (THIS NEEDS TO BE SECURE!)
user_cred_file = /root/rss_bot/rss_bot_user_cred.secret
# This section is optional in RSS mode
[toots]
#Values can span multiple lines as long as they are indented more than the key that holds them
cw_text = A new article is on the blog, check it out!
[rss]
# The main RSS feed
feed = http://blog.instance.com/rss
# The number of articles to fetch, must be between 1 and 150 (150 is a standard instance rate limit)
article_limit = 25
# File that holds the cache of RSS articles previously seen (sqlite3)
cache_file = /root/rss_bot/rss_cache.db

27
configs/example_rss.yaml

@ -0,0 +1,27 @@
# Setup general config for the bot (registered name, instance url, credential cache)
# This section is REQUIRED
config:
app_name: rss bot
api_base_url: https://instance.com
client_cred_file: /home/mastodon/bot/client_cred.secret
user_cred_file: /home/mastodon/bot/user_cred.secret
# Setup the RSS feed to watch
# This section is OPTIONAL
rss:
feed: https://instance.com/users/admin.atom
cache_file: /home/mastodon/bot/rss_cache.db # Path to sqlite database that caches seen articles
# Setup the toots that will be sent
# These static toots will ALWAYS be sent (even in RSS mode)
# This section is REQUIRED
toot:
visibility: public # One of: public, unlisted, private, or direct
cw_text: this is the CW text of the toot (optional)
toot_text: |
this is the body of the toot (required)
If RSS directive is specified the following special variables can be used as placeholders for information about the RSS article
- [[ title ]]
- [[ url ]]
- [[ published ]]

30
configs/example_rss_custom_logic.ini

@ -1,30 +0,0 @@
[config]
# Define a static toot that's send via the bot
# Most useful for a single toot that's send on a regular basis as a reminder
# Think generic follow friday, weekly reminders, etc
# The name of the app that's registered on the instance
app_name = rss_bot_include_logic
# The base URL for the instance
api_base_url = https://instance.com
# Where to store the client secret for the bot (THIS NEEDS TO BE SECURE!)
client_cred_file = /root/rss_bot/rss_bot.secret
# Where to store the user credentials for the bot (THIS NEEDS TO BE SECURE!)
user_cred_file = /root/rss_bot/rss_bot_user_cred.secret
# This section is optional in RSS mode
[toots]
#Values can span multiple lines as long as they are indented more than the key that holds them
cw_text = A new article has been posted!
[rss]
# The main RSS feed
feed = https://website.com/feed/
# Custom article include implementation file
# add an include_article(article) function to this file that returns True or False if you want custom logic for cross-posting articles from rss feeds
# Think cross posting challenges from a photography website
custom_article_include_file = /root/rss_bot/custom_rss_include.py
# The number of articles to fetch, must be between 1 and 150 (150 is a standard instance rate limit)
article_limit = 1
# File that holds the cache of RSS articles previously seen (sqlite3)
cache_file = /root/rss_bot/rss_cache.db

27
configs/example_rss_custom_logic.yaml

@ -0,0 +1,27 @@
# Setup general config for the bot (registered name, instance url, credential cache)
# This section is REQUIRED
config:
app_name: rss bot custom logic
api_base_url: https://instance.com
client_cred_file: /home/mastodon/bot/client_cred.secret
user_cred_file: /home/mastodon/bot/user_cred.secret
# Setup the RSS feed to watch
# This section is OPTIONAL
rss:
feed: https://instance.com/users/admin.atom
cache_file: /home/mastodon/bot/rss_cache.db # Path to sqlite database that caches seen articles
custom_logic_include_file: /home/mastodon/bot/custom_rss_logic.py # This is optional, see custom_logic folder for samples
# Setup the toots that will be sent
# These static toots will ALWAYS be sent (even in RSS mode)
# This section is REQUIRED
toot:
visibility: public # One of: public, unlisted, private, or direct
cw_text: "'[[ title ]]' has been posted to the blog!"
toot_text: |
[[ title ]] has been posted to the blog
Check it out here: [[ url ]]
(posted at [[ published ]])

1
requirements.txt

@ -1,2 +1,3 @@
Mastodon.py
feedparser
ruamel.yaml

427
toot_bot.py

@ -1,222 +1,249 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import argparse, sys, os, configparser, getpass, sqlite3, codecs
# Adjust PyLint settings
# pylint: disable=C0301
''' Basic bot that toots '''
import argparse
import sys
import os
import getpass
import codecs
import sqlite3
from mastodon import Mastodon
import feedparser
from ruamel.yaml import YAML
def init(config):
''' Initialize the Mastdon API app and cache the API key
Auto login if app creation succeeds '''
# Prompt user to find out if they want to continue if the client_cred_file exists already
# Shouldn't happen twice per docs
if os.path.exists(config['config']['client_cred_file']):
sys.exit('init should only ever be called once, try login instead, if that fails. delete ' + config['config']['client_cred_file'] + ' and re-run init')
# Create app for API
Mastodon.create_app(
config['config']['app_name'],
api_base_url=config['config']['api_base_url'],
scopes=['read', 'write', 'follow'],
to_file=config['config']['client_cred_file']
)
# Login to seed login credentials
login(config)
def login(config):
''' Login to API and cache API access token(s) '''
# Prompt for user/password (NEVER store them on disk!)
email = input('what is the e-mail that was used to setup the bot account on ' + config['config']['api_base_url'] + '? ')
password = getpass.getpass('what is the password for the account? ')
# Setup Mastodon API
mastodon = Mastodon(
client_id=config['config']['client_cred_file'],
api_base_url=config['config']['api_base_url']
)
totp_code = None
if config['config']['totp']:
print('TOTP enabled, open the link (below) in a browser to authorize access')
print(mastodon.auth_request_url())
print()
totp_code = input('enter the code you received from the url: ')
# Login and cache credential
if totp_code is None:
mastodon.log_in(
email,
password,
scopes=['read', 'write', 'follow'],
to_file=config['config']['user_cred_file']
)
else:
mastodon.log_in(
code=totp_code,
scopes=['read', 'write', 'follow'],
to_file=config['config']['user_cred_file']
)
def toot(config, article=None):
''' Send a toot '''
# Setup Mastodon API
mastodon = Mastodon(
client_id=config['config']['client_cred_file'],
access_token=config['config']['user_cred_file'],
api_base_url=config['config']['api_base_url']
)
# Process main toot
cw_text = None
if 'cw_text' in config['toot']:
cw_text = config['toot']['cw_text']
if article is not None:
cw_text = cw_text.replace('[[ title ]]', article.title)
cw_text = cw_text.replace('[[ url ]]', article.link)
cw_text = cw_text.replace('[[ published ]]', article.published)
toot_text = config['toot']['toot_text']
if article is not None:
toot_text = toot_text.replace('[[ title ]]', article.title)
toot_text = toot_text.replace('[[ url ]]', article.link)
toot_text = toot_text.replace('[[ published ]]', article.published)
parent_toot = mastodon.status_post(toot_text, visibility=config['toot']['visibility'], spoiler_text=cw_text)
parent_toot_id = parent_toot['id']
# Handle any sub-toots
if 'subtoots' in config['toot']:
for subtoot in config['toot']['subtoots']:
cw_text = None
if 'cw_text' in subtoot:
cw_text = subtoot['cw_text']
mastodon.status_post(subtoot['toot_text'], in_reply_to_id=parent_toot_id, visibility=subtoot['visibility'], spoiler_text=cw_text)
def init(api_base_url, app_name, client_cred_file, user_cred_file, totp):
# Prompt user to find out if they want to continue if the client_cred_file exists already -- shouldn't happen twice per docs
if (os.path.exists(client_cred_file)):
sys.exit('init should only ever be called once, try login instead, if that fails. delete ' + client_cred_file + ' and re-run init')
# Create app for API
Mastodon.create_app(
app_name,
api_base_url = api_base_url,
scopes=['read', 'write', 'follow'],
to_file = client_cred_file
)
# Login to seed login credentials
login(api_base_url, client_cred_file, user_cred_file, totp)
def login(api_base_url, client_cred_file, user_cred_file, totp):
# Prompt for user/password (NEVER store them on disk!)
email = input('what is the e-mail that was used to setup the bot account on ' + api_base_url + '? ')
password = getpass.getpass('what is the password for the account? ')
# Setup Mastodon API
mastodon = Mastodon(
client_id = client_cred_file,
api_base_url = api_base_url
)
totp_code = None
if totp:
print('TOTP enabled, open the link (below) in a browser to authorize access')
print(mastodon.auth_request_url())
print()
totp_code = input("enter the code you received from the url: ")
# Login and cache credential
if totp_code is None:
mastodon.log_in(
email,
password,
scopes=['read', 'write', 'follow'],
to_file = user_cred_file
)
else:
mastodon.log_in(
code = totp_code,
scopes=['read', 'write', 'follow'],
to_file = user_cred_file
)
def toot(api_base_url, client_cred_file, user_cred_file, cw_text, toot, subtoots):
# Setup Mastodon API
mastodon = Mastodon(
client_id = client_cred_file,
access_token = user_cred_file,
api_base_url = api_base_url
)
# Process toot
parent_toot = mastodon.status_post(toot, spoiler_text=cw_text)
parent_toot_id = parent_toot['id']
# Handle any sub-toots
if subtoots is not None:
for subtoot in subtoots:
mastodon.status_post(subtoot, in_reply_to_id=parent_toot_id, visibility='unlisted')
# Return the parent toot dict just in case it's needed elsewhere
return parent_toot
return parent_toot
# Default implementation on whether or not to include an RSS article
# Returns true and has the same method signature as what end users can setup for custom logic
def default_include_article(article):
return True
''' Default implementation of RSS processing logic
Extended via external file if necessary '''
if article is None:
return False
return True
def rss(api_base_url, client_cred_file, user_cred_file, custom_article_include_file, rss_feed, rss_article_limit, rss_cache_file, cw_text):
def rss(config):
''' Parse RSS feed and toot any new articles '''
# Setup custom include logic before opening any database connections
include_article_fn = default_include_article
if 'custom_logic_include_file' in config['rss'] and os.path.exists(os.path.abspath(config['rss']['custom_logic_include_file'])):
from importlib.machinery import SourceFileLoader
custom_logic = SourceFileLoader('custom.logic', os.path.abspath(config['rss']['custom_logic_include_file'])).load_module()
include_article_fn = custom_logic.include_article
# Setup custom include logic before opening any database connections
include_article_fn = default_include_article
if custom_article_include_file is not None and os.path.exists(os.path.abspath(custom_article_include_file)):
from importlib.machinery import SourceFileLoader
custom_logic = SourceFileLoader('custom.logic', os.path.abspath(custom_article_include_file)).load_module()
include_article_fn = custom_logic.include_article
# Crash on reading the feed before doing any database operations or tooting
feed = feedparser.parse(config['rss']['feed'])
# Crash on reading the feed before doing any database operations or tooting
feed = feedparser.parse(rss_feed)
# Get access to cache
conn = sqlite3.connect(config['rss']['cache_file'])
cursor = conn.cursor()
# Get access to cache
conn = sqlite3.connect(rss_cache_file)
c = conn.cursor()
# Ensure cache table has been created
cursor.execute('create table if not exists article_cache (id varchar(256) primary key);')
# Ensure cache table has been created
c.execute('create table if not exists article_cache (id varchar(256) primary key);')
# Run through all articles in feed
for entry in feed['entries']:
if not include_article_fn(entry):
continue
# Run through all articles in feed
for entry in feed['entries']:
if not include_article_fn(entry):
continue
# Check if article is in cache already and skip if found
cursor.execute('select count(1) as found from article_cache where id = ?', (entry.id,))
if cursor.fetchone()[0] > 0:
continue
# Check if article is in cache already and skip if found
c.execute('select count(1) as found from article_cache where id = ?', (entry.id,))
if c.fetchone()[0] > 0:
continue
# Toot article
toot(config, entry)
# Toot article
toot_text = entry.title + '\n\n' + entry.link + '\n\n' + entry.published
toot(api_base_url, client_cred_file, user_cred_file, cw_text, toot_text, None)
# Cache article
cursor.execute('insert into article_cache values (?)', (entry.id,))
conn.commit()
# Cache article
c.execute('insert into article_cache values (?)', (entry.id,))
conn.commit()
# Cleanup connection to sqlite database for rss cache
conn.close()
# Cleanup connection to sqlite database for rss cache
conn.close()
if __name__ == '__main__':
# Global CLI arguments/options
parser = argparse.ArgumentParser( )
parser.add_argument('--config', help='path to config file', required=True)
subparsers = parser.add_subparsers(help='commands', dest='command')
# Actions / commands
init_parser = subparsers.add_parser('init', help='initialize credentials')
init_parser.add_argument('--totp', help='use totp login (requires user to open URL in browser and then enter token to bot during init/login)',
action='store_true')
login_parser = subparsers.add_parser('login', help='login to instance if credentials have expired')
login_parser.add_argument('--totp', help='use totp login (requires user to open URL in browser and then enter token to bot during init/login)',
action='store_true')
toot_parser = subparsers.add_parser('toot', help='send configured toot')
rss_parser = subparsers.add_parser('rss', help='cross post articles from an rss feed')
# Parse CLI arguments
args = parser.parse_args()
# Make sure a command was specified
if args.command is None:
sys.exit('command must be specified')
# Make sure the config file specified exists
config_path = os.path.abspath(args.config)
if not os.path.exists(config_path):
sys.exit('invalid path to config file')
# Read/parse config file
config = configparser.ConfigParser(allow_no_value=False)
config.read_file(codecs.open(config_path, "r", "utf8"))
# Setup local variables
config_app_name = config['config']['app_name']
config_api_base_url = config['config']['api_base_url']
config_client_cred_file = config['config']['client_cred_file']
config_user_cred_file = config['config']['user_cred_file']
config_totp = False
if 'totp' in args:
config_totp = args.totp
# Ensure client_cred_file path is valid
config_client_cred_file = os.path.abspath(config_client_cred_file)
if not os.path.exists(os.path.split(config_client_cred_file)[0]):
sys.exit('client_cred_file directory does not exist')
# Warn user that the config file WILL be created
if not os.path.exists(config_client_cred_file):
print('warning: client_cred_file will be created')
# Ensure user_cred_file path is valid
config_user_cred_file = os.path.abspath(config_user_cred_file)
if not os.path.exists(os.path.split(config_user_cred_file)[0]):
sys.exit('user_cred_file directory does not exist')
if not os.path.exists(config_user_cred_file):
print('warning: user_cred file will be created')
# Pull out CW Text if present (valid for both toot and rss modes)
config_cw_text = None
if config.has_option('toots', 'cw_text'):
config_cw_text = config['toots']['cw_text']
# Deal with init command
if args.command == 'init':
init(config_api_base_url, config_app_name, config_client_cred_file, config_user_cred_file, config_totp)
# Deal with login command
if args.command == 'login':
login(config_api_base_url, config_client_cred_file, config_user_cred_file, config_totp)
# Deal with toot command
if args.command == 'toot':
# Setup main toot text (applies to RSS as well -- is prefix for RSS toots
config_toot = config['toots']['main_toot']
# Ensure total toot length <= 500 characters
if len(config_toot) > 500:
sys.exit('toot length must be <= 500 characters')
# Setup sub toots from config
config_sub_toots = dict(config.items('toots'))
# Remove the main toots from the dict of sub toots
config_sub_toots.pop('main_toot')
# If CW text is specified, pop it from dict of sub toots
if config_cw_text is not None:
config_sub_toots.pop('cw_text')
if len(config_sub_toots.values()) > 0:
# Throw away the keys, we really only care about the items in the list'
config_sub_toots = list(config_sub_toots.values())
else:
config_sub_toots = None
toot(config_api_base_url, config_client_cred_file, config_user_cred_file, config_cw_text, config_toot, config_sub_toots)
# Deal with rss command
if args.command == 'rss':
config_custom_article_include_file = None
if config.has_option('rss', 'custom_article_include_file'):
config_custom_article_include_file = config['rss']['custom_article_include_file']
config_rss_feed = config['rss']['feed']
config_rss_article_limit = int(config['rss']['article_limit'])
if config_rss_article_limit < 1 or config_rss_article_limit > 150:
sys.exit("rss article limit must be greater than 0 and less than 150")
config_rss_cache_file = config['rss']['cache_file']
if not os.path.exists(os.path.split(config_rss_cache_file)[0]):
sys.exit('rss_cache_file directory does not exist')
if not os.path.exists(config_rss_cache_file):
print('warning: rss_cache_file file will be created')
rss(config_api_base_url, config_client_cred_file, config_user_cred_file, config_custom_article_include_file, config_rss_feed, config_rss_article_limit, config_rss_cache_file, config_cw_text)
# Global CLI arguments/options
PARSER = argparse.ArgumentParser()
PARSER.add_argument('--config', help='path to config file', required=True)
SUBPARSERS = PARSER.add_subparsers(help='commands', dest='command')
# Actions / commands
INIT_PARSER = SUBPARSERS.add_parser('init', help='initialize credentials')
INIT_PARSER.add_argument('--totp',
help='use totp login (requires user to open URL in browser and then enter token to bot during init/login)',
action='store_true')
LOGIN_PARSER = SUBPARSERS.add_parser('login', help='login to instance if credentials have expired')
LOGIN_PARSER.add_argument('--totp',
help='use totp login (requires user to open URL in browser and then enter token to bot during init/login)',
action='store_true')
TOOT_PARSER = SUBPARSERS.add_parser('toot', help='send configured toot')
RSS_PARSER = SUBPARSERS.add_parser('rss', help='cross post articles from an rss feed')
# Parse CLI arguments
ARGS = PARSER.parse_args()
# Make sure a command was specified
if ARGS.command is None:
sys.exit('command must be specified')
# Make sure the config file specified exists
CONFIG_PATH = os.path.abspath(ARGS.config)
if not os.path.exists(CONFIG_PATH):
sys.exit('invalid path to config file')
# Read/parse config file
CONFIG = None
# Fix unicode special character error
with codecs.open(CONFIG_PATH, "r", "utf8") as stream:
CONFIG = YAML(typ='safe').load(stream)
# Add TOTP flag to CONFIG which is passed to each function
if 'totp' in ARGS:
CONFIG['config']['totp'] = ARGS.totp
# Ensure client_cred_file path is valid
if not os.path.exists(os.path.abspath(os.path.split(CONFIG['config']['client_cred_file'])[0])):
sys.exit('client_cred_file directory does not exist')
# Warn user that the config file WILL be created
if not os.path.exists(os.path.abspath(CONFIG['config']['client_cred_file'])):
print('warning: client_cred_file will be created')
# Ensure user_cred_file path is valid
if not os.path.exists(os.path.abspath(os.path.split(CONFIG['config']['user_cred_file'])[0])):
sys.exit('user_cred_file directory does not exist')
if not os.path.exists(os.path.abspath(CONFIG['config']['user_cred_file'])):
print('warning: user_cred file will be created')
# Deal with init command
if ARGS.command == 'init':
init(CONFIG)
# Deal with login command
if ARGS.command == 'login':
login(CONFIG)
# Deal with toot command
if ARGS.command == 'toot':
# Ensure main toot is <= 500 characters
TOOT_LENGTH = len(CONFIG['toot']['toot_text'])
if 'cw_text' in CONFIG['toot']:
TOOT_LENGTH = TOOT_LENGTH + len(CONFIG['toot']['cw_text'])
if TOOT_LENGTH > 500:
sys.exit('toot length must be <= 500 characters (including cw_text)')
# Ensure sub toots are <= 500 characters
if 'subtoots' in CONFIG['toot']:
for subtoot in CONFIG['toot']['subtoots']:
TOOT_LENGTH = len(subtoot['toot_text'])
if 'cw_text' in subtoot:
TOOT_LENGTH = TOOT_LENGTH + len(subtoot['cw_text'])
if TOOT_LENGTH > 500:
sys.exit('sub toot length must be <= 500 characters (including cw_text)')
toot(CONFIG)
# Deal with rss command
if ARGS.command == 'rss':
if 'custom_logic_include_file' in CONFIG['rss']:
if not os.path.exists(os.path.abspath(CONFIG['rss']['custom_logic_include_file'])):
sys.exit('custom logic file does not exist!')
# Verify RSS cache file folder exists
if not os.path.exists(os.path.abspath(os.path.split(CONFIG['rss']['cache_file'])[0])):
sys.exit('rss cache_file directory does not exist')
# Warn if RSS cache file doesn't exist
if not os.path.exists(os.path.abspath(os.path.split(CONFIG['rss']['cache_file'])[0])):
print('warning: rss_cache_file file will be created')
rss(CONFIG)

Loading…
Cancel
Save