| 1 | #!/bin/bash |
| 2 | # pdt: post to FSF social media via command line |
| 3 | # Copyright (C) 2021 Ian Kelling |
| 4 | # SPDX-License-Identifier: AGPL-3.0-or-later |
| 5 | |
| 6 | # This program is free software: you can redistribute it and/or modify |
| 7 | # it under the terms of the GNU Affero General Public License as published by |
| 8 | # the Free Software Foundation, either version 3 of the License, or |
| 9 | # (at your option) any later version. |
| 10 | |
| 11 | # This program is distributed in the hope that it will be useful, |
| 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | # GNU Affero General Public License for more details. |
| 15 | |
| 16 | # You should have received a copy of the GNU Affero General Public License |
| 17 | # along with this program. If not, see <https://www.gnu.org/licenses/>. |
| 18 | |
| 19 | # This is meant to be sourced. |
| 20 | |
| 21 | PATH="$HOME/.local/bin:$PATH" |
| 22 | |
| 23 | # usage: rbow [-PROFILE_NAME] [COMMAND] |
| 24 | # |
| 25 | # Wrapper for rainbowstream to use multiple logins and tweet directly |
| 26 | # from the command line. PROFILE_NAME can be anything you want. |
| 27 | rbow() { |
| 28 | local oath f |
| 29 | oath=$HOME/.rainbow_oauth |
| 30 | if [[ $1 == -* ]]; then |
| 31 | rm -f $oath |
| 32 | ln -s $oath$1 $oath |
| 33 | shift |
| 34 | fi |
| 35 | source ~/src/rainbowstream/venv/bin/activate |
| 36 | if (($#)); then |
| 37 | f=$(mktemp) |
| 38 | cat >$f <<'EOF' |
| 39 | # adds a short delay after each send for more reliable operation |
| 40 | set force_conservative 1 |
| 41 | spawn "rainbowstream" |
| 42 | # wait for prompt |
| 43 | expect -nocase timeout {exit 1} "@*]: " |
| 44 | set cmd [lindex $argv 0]; |
| 45 | send "$cmd\r\r" |
| 46 | expect -nocase timeout {exit 1} "@*]: " |
| 47 | # this may not be needed. didnt test |
| 48 | sleep 2 |
| 49 | send "q\r" |
| 50 | interact |
| 51 | EOF |
| 52 | expect $f "$*" || { echo error: expect failed; deactivate; rm -f $f; return 1; } |
| 53 | rm -f $f |
| 54 | else |
| 55 | rainbowstream |
| 56 | fi |
| 57 | deactivate |
| 58 | } |
| 59 | |
| 60 | diaspora() { |
| 61 | source ~/src/diaspy/venv/bin/activate |
| 62 | python3 ~/src/jan-pona-mute/jan-pona-mute.py "$@" || { deactivate; return 1; } |
| 63 | deactivate |
| 64 | } |
| 65 | |
| 66 | # usage: toot [-PROFILE_NAME] [TOOT_ARGS] |
| 67 | toot() { |
| 68 | local account |
| 69 | source ~/src/toot/venv/bin/activate |
| 70 | if [[ $1 == -* ]]; then |
| 71 | account=${1#-} |
| 72 | shift |
| 73 | command toot activate --quiet $account || { deactivate; return 1; } |
| 74 | fi |
| 75 | command toot "$@" || { deactivate; return 1; } |
| 76 | deactivate |
| 77 | } |
| 78 | |
| 79 | |
| 80 | # post to mastodon + twitter + gnu social post + diaspora |
| 81 | # |
| 82 | # Diaspora is skipped if posting to dbd accounts. |
| 83 | # |
| 84 | # If posting video, it only goes to twitter. Other accounts can be |
| 85 | # posted to manually with free software. |
| 86 | # |
| 87 | # usage: pdt [--dbd] [-m MEDIA_FILE] [-v VIDEO_PATH] [POST] |
| 88 | pdt() { |
| 89 | local video media rbow_account gs_account mastodon_account dbd video rbow_arg toot_arg dia_arg gs_arg |
| 90 | if [[ $pdttest ]]; then |
| 91 | rbow_account=iank |
| 92 | gs_account=fsfes |
| 93 | mastodon_account=iank@hostux.social |
| 94 | else |
| 95 | rbow_account=fsf |
| 96 | gs_account=fsf |
| 97 | mastodon_account=fsf@hostux.social |
| 98 | fi |
| 99 | dbd=false |
| 100 | video=false |
| 101 | while [[ $1 == -* ]]; do |
| 102 | case $1 in |
| 103 | -m) |
| 104 | media="$2" |
| 105 | shift 2 |
| 106 | rbow_arg=" --i $media" |
| 107 | toot_arg="--media $media" |
| 108 | dia_arg="-p $media " |
| 109 | gs_arg="-F media=@$media" |
| 110 | ;; |
| 111 | --dbd) |
| 112 | dbd=true |
| 113 | rbow_account=dbd |
| 114 | gs_account=dbd |
| 115 | mastodon_account=endDRM@hostux.social |
| 116 | shift |
| 117 | ;; |
| 118 | -v) |
| 119 | video=true |
| 120 | media="$2" |
| 121 | shift 2 |
| 122 | ;; |
| 123 | esac |
| 124 | done |
| 125 | if [[ $media ]]; then |
| 126 | if [[ ! -e $media ]]; then |
| 127 | echo "error: file not found $media" |
| 128 | return 1 |
| 129 | fi |
| 130 | if [[ $media == *\ * ]]; then |
| 131 | echo "error: file path contains a space. move it to non-space path" |
| 132 | return 1 |
| 133 | fi |
| 134 | fi |
| 135 | # if we have no argument |
| 136 | if (( ! $# )); then |
| 137 | read -r -p "input PDT text: " input |
| 138 | set -- "$input" |
| 139 | fi |
| 140 | if [[ $- == *i* ]]; then |
| 141 | echo "About to PDT the following line. Press enter to confirm or ctrl-c to quit:" |
| 142 | echo "$input" |
| 143 | read |
| 144 | fi |
| 145 | if $video; then |
| 146 | local oath |
| 147 | oath=$HOME/.rainbow_oauth |
| 148 | account=fsf |
| 149 | if $dbd; then |
| 150 | account=dbd |
| 151 | fi |
| 152 | rm -f $oath |
| 153 | ln -s ${oath}-$account $oath |
| 154 | python3 ~/src/video-tweet/async-upload.py "$media" "$*" |
| 155 | return |
| 156 | fi |
| 157 | fails=() |
| 158 | if ! rbow -$rbow_account t "$*" $rbow_arg; then |
| 159 | fails+=(tweet) |
| 160 | fi |
| 161 | if ! toot -$mastodon_account post "$*" $toot_arg; then |
| 162 | fails+=(toot) |
| 163 | fi |
| 164 | if ! $dbd; then |
| 165 | if ! printf "post %s\n" "${dia_arg}$*" | diaspora; then |
| 166 | fails+=(diaspora) |
| 167 | fi |
| 168 | fi |
| 169 | # https://gnusocial.net/doc/twitterapi |
| 170 | if ! curl -o /dev/null -sS -u "$gs_account:$(cat ~/.gnusocial_login-$gs_account)" \ |
| 171 | $gs_arg -F "status=$*" https://status.fsf.org/api/statuses/update.xml; then |
| 172 | fails+=(gnu-social) |
| 173 | fi |
| 174 | if (( ${#fails[@]} )); then |
| 175 | printf "$(tput setaf 5)█$(tput sgr0)%.0s" $(eval echo "{1..${COLUMNS:-60}}"); echo |
| 176 | echo "FSF ERROR: ${fails[*]} might not have posted" >&2 |
| 177 | fi |
| 178 | } |
| 179 | |
| 180 | # Usage: pdt-setup |
| 181 | pdt-setup() { |
| 182 | if [[ $pdttest ]]; then |
| 183 | mastodon_accounts=(iank) |
| 184 | else |
| 185 | mastodon_accounts=(fsf endDRM) |
| 186 | fi |
| 187 | |
| 188 | mkdir -p ~/src |
| 189 | cd ~/src |
| 190 | for repo in errhandle rainbowstream diaspy jan-pona-mute video-tweet; do |
| 191 | if [[ -e $repo ]]; then |
| 192 | cd $repo |
| 193 | git fetch |
| 194 | git reset --hard origin/master |
| 195 | git clean -xfffd |
| 196 | cd .. |
| 197 | else |
| 198 | git clone https://vcs.fsf.org/git/$repo.git |
| 199 | fi |
| 200 | done |
| 201 | source ~/src/errhandle/err |
| 202 | cp ~/src/rainbowstream/rainbowstream/colorset/config ~/.rainbow_config.json |
| 203 | |
| 204 | if [[ ! -e ~/.local/bin/pip ]]; then |
| 205 | tmp=$(mktemp) |
| 206 | wget -O$tmp https://bootstrap.pypa.io/get-pip.py |
| 207 | python3 $tmp --user |
| 208 | hash -r |
| 209 | fi |
| 210 | |
| 211 | |
| 212 | if [[ ! -e ~/.local/bin/virtualenv ]]; then |
| 213 | python3 -m pip install --user virtualenv |
| 214 | fi |
| 215 | |
| 216 | cd ~/src/rainbowstream |
| 217 | python3 -m virtualenv -p python3 venv |
| 218 | source venv/bin/activate |
| 219 | python3 -m pip install -r requirements.txt |
| 220 | python3 -m pip install -e . |
| 221 | deactivate |
| 222 | |
| 223 | |
| 224 | # This repo's upstream is https://alexschroeder.ch/cgit/diaspy which is |
| 225 | # recommended in the install instructions here: |
| 226 | # https://alexschroeder.ch/cgit/jan-pona-mute/about/ |
| 227 | cd ~/src/diaspy |
| 228 | python3 -m virtualenv -p python3 venv |
| 229 | source venv/bin/activate |
| 230 | python3 -m pip install -r requirements.txt |
| 231 | python3 -m pip install -e . |
| 232 | deactivate |
| 233 | |
| 234 | rm -rf ~/src/toot |
| 235 | mkdir -p ~/src/toot |
| 236 | cd ~/src/toot |
| 237 | python3 -m virtualenv -p python3 venv |
| 238 | source venv/bin/activate |
| 239 | # pip freeze after a pip install. |
| 240 | cat >requirements.txt <<'EOF' |
| 241 | beautifulsoup4==4.8.2 |
| 242 | certifi==2019.11.28 |
| 243 | chardet==3.0.4 |
| 244 | idna==2.8 |
| 245 | requests==2.22.0 |
| 246 | soupsieve==1.9.5 |
| 247 | toot==0.25.2 |
| 248 | urllib3==1.25.8 |
| 249 | urwid==2.1.0 |
| 250 | wcwidth==0.1.8 |
| 251 | EOF |
| 252 | python3 -m pip install -r requirements.txt |
| 253 | deactivate |
| 254 | |
| 255 | |
| 256 | if [[ ! -s ~/.config/jan-pona-mute/login ]]; then |
| 257 | mkdir ~/.config/jan-pona-mute |
| 258 | account=fsf@framasphere.org |
| 259 | read -r -p "enter the password for $account > " pass |
| 260 | # background: format for this found using the save command |
| 261 | cat >~/.config/jan-pona-mute/login <<EOF |
| 262 | account $account |
| 263 | password $pass |
| 264 | login |
| 265 | EOF |
| 266 | fi |
| 267 | |
| 268 | for account in dbd fsf; do |
| 269 | if [[ ! -s ~/.rainbow_oauth-$account ]]; then |
| 270 | printf "$(tput setaf 5)█$(tput sgr0)%.0s" $(eval echo "{1..${COLUMNS:-60}}"); |
| 271 | echo "Please login to $account on twitter in your main browser then press enter. After rainbowstream prompt loads, quit with command q" |
| 272 | read -r |
| 273 | rbow -$account |
| 274 | fi |
| 275 | if [[ ! -s ~/.gnusocial_login-$account ]]; then |
| 276 | printf "$(tput setaf 5)█$(tput sgr0)%.0s" $(eval echo "{1..${COLUMNS:-60}}"); |
| 277 | read -r -p "please enter the password for $account@status.fsf.org > " pass |
| 278 | touch ~/.gnusocial_login-$account |
| 279 | chmod 600 ~/.gnusocial_login-$account |
| 280 | printf "%s\n" "$pass" > ~/.gnusocial_login-$account |
| 281 | fi |
| 282 | done |
| 283 | |
| 284 | for account in ${mastodon_accounts[@]}; do |
| 285 | if ! toot activate $account@hostux.social &>/dev/null; then |
| 286 | printf "$(tput setaf 5)█$(tput sgr0)%.0s" $(eval echo "{1..${COLUMNS:-60}}"); |
| 287 | echo "Please login to $account on https://hostux.social in your main browser then press enter." |
| 288 | read -r |
| 289 | toot login -i hostux.social |
| 290 | fi |
| 291 | done |
| 292 | err-allow |
| 293 | } |