#!/bin/bash # pdt: post to FSF social media via command line # Copyright (C) 2021 Ian Kelling # SPDX-License-Identifier: AGPL-3.0-or-later # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU Affero 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 Affero General Public License for more details. # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . # This is meant to be sourced. PATH="$HOME/.local/bin:$PATH" _pdtsh_file="$(readlink -f -- "${BASH_SOURCE[0]}")" _pdtsh_dir="${_pdtsh_file%/*}" _pdtsh_tweet="$_pdtsh_dir/t.py" # usage: tweet [-PROFILE_NAME] [POST...] # Uses variables as input: # $media for image file path. # $alt_text for image alt text. tweet() { local oath oath=$HOME/.rainbow_oauth if [[ $1 == -* ]]; then rm -f $oath ln -s $oath$1 $oath shift fi source ~/src/twitter/venv/bin/activate { printf "%s\n" "$*" if [[ $media ]]; then printf "%s\n" "$media" if [[ $alt_text ]]; then printf "%s\n" "$alt_text" fi fi } | $_pdtsh_tweet deactivate } # usage: rbow [-PROFILE_NAME] [COMMAND] # # Wrapper for rainbowstream to use multiple logins and tweet directly # from the command line. PROFILE_NAME can be anything you want. rbow() { local oath f oath=$HOME/.rainbow_oauth if [[ $1 == -* ]]; then rm -f $oath ln -s $oath$1 $oath shift fi source ~/src/rainbowstream/venv/bin/activate if (($#)); then f=$(mktemp) cat >$f <<'EOF' # adds a short delay after each send for more reliable operation set force_conservative 1 spawn "rainbowstream" # wait for prompt expect -nocase timeout {exit 1} "@*]: " set cmd [lindex $argv 0]; send "$cmd\r\r" expect -nocase timeout {exit 1} "@*]: " # this may not be needed. didnt test sleep 2 send "q\r" interact EOF expect $f "$*" || { echo error: expect failed; deactivate; rm -f $f; return 1; } rm -f $f else rainbowstream fi deactivate } # usage: toot [-PROFILE_NAME] [TOOT_ARGS] toot() { local mast_profile source ~/src/toot/venv/bin/activate if [[ $1 == -* ]]; then mast_profile=${1#-} shift command toot activate --quiet $mast_profile || { deactivate; return 1; } fi command toot "$@" || { deactivate; return 1; } deactivate } # post to mastodon + twitter + gnu social post # # If posting video, it only goes to twitter. Other accounts can be # posted to manually with free software. # # Alt text only goes to mastodon. # # usage: pdt [-s mastodon|twitter|gnusocial] [--dbd] [-m IMAGE_FILE] [-a ALT_TEXT] [-v VIDEO_PATH] [POST] # -s mastodon|twitter|gnusocial = post to a single social network. # -m IMAGE_FILE = Uploads IMAGE_FILE. if MEDIA_FILE.txt exists, the last line of that file will be used as ALT_TEXT # unless -a has been used. pdt() { local video media twitter_account gs_account mastodon_account video gs_arg network local do_mastodon do_twitter do_gnusocial local -a toot_args if [[ $pdttest ]]; then twitter_account=iank gs_account=fsfes mastodon_account=fsftest@hostux.social else twitter_account=fsf gs_account=fsf mastodon_account=fsf@hostux.social fi video=false do_mastodon=true do_twitter=true do_gnusocial=true alt_text= while [[ $1 == -* ]]; do case $1 in -s) network="$2" do_mastodon=false do_twitter=false do_gnusocial=false case $network in mastodon) do_mastodon=true ;; twitter) do_twitter=true ;; gnusocial) do_gnusocial=true ;; *) echo "pdt: error: expected -s mastodon|twitter|gnusocial" return 1 ;; esac shift 2 ;; -a) alt_text="$2" shift 2 ;; -m) media="$2" shift 2 toot_args+=(--media "$media" ) gs_arg="-F media=@$media" ;; --dbd) twitter_account=dbd gs_account=dbd mastodon_account=endDRM@hostux.social shift ;; -v) video=true media="$2" shift 2 ;; esac done if [[ $media ]]; then if [[ ! -e $media ]]; then echo "error: file not found $media" return 1 fi if [[ $media == *\ * ]]; then echo "error: file path contains a space. move it to non-space path" return 1 fi if [[ ! $alt_text && -r $media.txt && -s $media.txt ]]; then alt_text=$(tail -n1 $media.txt) fi if [[ $alt_text ]]; then toot_args+=(--description "$alt_text" ) fi fi # if we have no argument if (( ! $# )); then read -r -p "input PDT text: " input set -- "$input" fi if [[ $- == *i* ]]; then echo "About to PDT the following line. Press enter to confirm or ctrl-c to quit:" echo "$*" read -r fi if $video; then local oath oath=$HOME/.rainbow_oauth rm -f $oath ln -s ${oath}-$twitter_account $oath python3 ~/src/video-tweet/async-upload.py "$media" "$*" return fi fails=() if $do_twitter; then if ! tweet -$twitter_account "$*"; then fails+=(tweet) fi fi if $do_mastodon; then if ! toot -$mastodon_account post "$*" "${toot_args[@]}"; then fails+=(toot) fi fi if $do_gnusocial; then # https://gnusocial.net/doc/twitterapi if ! curl -o /dev/null -sS -u "$gs_account:$(cat ~/.gnusocial_login-$gs_account)" \ $gs_arg -F "status=$*" https://status.fsf.org/api/statuses/update.xml; then fails+=(gnu-social) fi fi if (( ${#fails[@]} )); then printf "$(tput setaf 5)█$(tput sgr0)%.0s" $(eval echo "{1..${COLUMNS:-60}}"); echo echo "FSF ERROR: ${fails[*]} might not have posted" >&2 fi } # Usage: toot-setup # # Only run manually for testing. # # this expects pdt-pip-setup has been run already, and it doesn't setup # errhandle. # # note: auth info is stored at ~/.config/toot/config.json pdt-toot-setup() { local -a twitter_accounts mastodon_accounts if [[ $pdttest ]]; then twitter_accounts=(iank) mastodon_accounts=(fsftest) else twitter_accounts=(fsf dbd) mastodon_accounts=(fsf endDRM) fi rm -rf ~/src/toot mkdir -p ~/src/toot cd ~/src/toot # on t11, got myself into a situation where when doing pip install virtualenv, # it gave an error that /usr/bin/pip didn't exist, so i did # sudo ln -s /home/iank/.local/bin/pip /usr/bin # python3 -m virtualenv -p python3 venv source venv/bin/activate # pip freeze after a pip install, as of 2022-11-28 cat >requirements.txt <<'EOF' beautifulsoup4==4.11.1 certifi==2022.9.24 charset-normalizer==2.1.1 idna==2.8 requests==2.22.0 soupsieve==2.3.2.post1 toot==0.29.0 urllib3==1.25.8 urwid==2.1.2 wcwidth==0.2.5 EOF # new 2022-11 packages # beautifulsoup4==4.11.1 # certifi==2022.9.24 # charset-normalizer==2.1.1 # idna==3.4 # requests==2.28.1 # soupsieve==2.3.2.post1 # toot==0.29.0 # urllib3==1.26.13 # urwid==2.1.2 # wcwidth==0.2.5 # old 2019 packages # beautifulsoup4==4.8.2 # certifi==2019.11.28 # chardet==3.0.4 # idna==2.8 # requests==2.22.0 # soupsieve==1.9.5 # toot==0.25.2 # urllib3==1.25.8 # urwid==2.1.0 # wcwidth==0.1.8 # i mixed in some old packages to get newer toot working on t9. python3 -m pip install -r requirements.txt cd - deactivate for account in ${mastodon_accounts[@]}; do if ! toot activate $account@hostux.social &>/dev/null; then printf "$(tput setaf 5)█$(tput sgr0)%.0s" $(eval echo "{1..${COLUMNS:-60}}"); echo "Please login to the account named \"$account\" on https://hostux.social in your main browser then press enter." echo "WARNING: if you log into an account other than \"$account\", this won't work" read -r toot login -i hostux.social fi done } # Usage: pdt-pip-setup pdt-pip-setup() { if [[ ! -e ~/.local/bin/pip ]]; then tmp=$(mktemp) pyver=$(python3 --version | sed -r 's/.*(3\.[0-9]+).*/\1/') echo fyi: detected pyver: $pyver. this should look something like 3.6 if dpkg --compare-versions 3.6 ge $pyver; then # The bootstrap script at https://bootstrap.pypa.io/get-pip.py required 3.7+ # when i wrote this. wget -O$tmp https://bootstrap.pypa.io/pip/$pyver/get-pip.py else wget -O$tmp https://bootstrap.pypa.io/get-pip.py fi python3 $tmp --user hash -r fi python3 -m pip install --user -U virtualenv } # generally only meant to be called internally from pdt-setup pdt-twitter-setup() { # twitter setup mkdir -p ~/src/twitter cd ~/src/twitter python3 -m virtualenv -p python3 venv source venv/bin/activate # as of 2022-11-30 cat >requirements.txt <<'EOF' certifi==2022.9.24 twitter==1.19.6 EOF python3 -m pip install -r requirements.txt deactivate cd - # note: we could ditch rainbowstream altogether and just pass around # oath api key string in a password file or something if we learned # how to get it from the new twitter client im using. I'm just keeping # this because it works and I like the format of how it stores its # auth secrets. cd ~/src/rainbowstream python3 -m virtualenv -p python3 venv source venv/bin/activate python3 -m pip install -r requirements.txt python3 -m pip install -e . deactivate for account in ${twitter_accounts[@]}; do if [[ ! -s ~/.rainbow_oauth-$account ]]; then printf "$(tput setaf 5)█$(tput sgr0)%.0s" $(eval echo "{1..${COLUMNS:-60}}"); echo "Please login to the account named \"$account\" on twitter in your main browser then press enter. After rainbowstream prompt loads, quit by typing q then enter" read -r rbow -$account fi done for account in ${twitter_accounts[@]}; do if [[ ! -s $HOME/.rainbow_oauth-$account ]]; then echo "pdt-setup error: expected non-empty file at $HOME/.rainbow_oauth-$account by this point. try reruning pdt-setup and logging in to the correct twitter account in browser or contact ian" return 1 fi done if [[ ! $pdttest ]] && diff -q $HOME/.rainbow_oauth-fsf $HOME/.rainbow_oauth-dbd &>/dev/null; then echo "pdt-setup error: error, $HOME/.rainbow_oauth-fsf $HOME/.rainbow_oauth-dbd are the same. Did you follow the instructions closely and log into fsf and then dbd when prompted? try reruning pdt-setup and doing that" return 1 fi cd - } # Usage: pdt-setup pdt-setup() { start_dir="$PWD" mkdir -p ~/src cd ~/src for repo in errhandle rainbowstream video-tweet; do if [[ -e $repo ]]; then cd $repo git fetch git reset --hard origin/master git clean -xfffd cd .. else git clone https://vcs.fsf.org/git/$repo.git fi done cd "$start_dir" source ~/src/errhandle/err cp ~/src/rainbowstream/rainbowstream/colorset/config ~/.rainbow_config.json pdt-pip-setup pdt-toot-setup pdt-twitter-setup for account in dbd fsf; do if [[ ! -s ~/.gnusocial_login-$account ]]; then printf "$(tput setaf 5)█$(tput sgr0)%.0s" $(eval echo "{1..${COLUMNS:-60}}"); read -r -p "please enter the password for $account@status.fsf.org > " pass touch ~/.gnusocial_login-$account chmod 600 ~/.gnusocial_login-$account printf "%s\n" "$pass" > ~/.gnusocial_login-$account fi done err-allow echo "pdt-setup complete" }