81bfd087016a9fb5cf10a54f8dbe90bb4c9550bb
[pdt.git] / pdt.sh
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
24 _pdtsh_file="$(readlink -f -- "${BASH_SOURCE[0]}")"
25 _pdtsh_dir="${_pdtsh_file%/*}"
26 _pdtsh_tweet="$_pdtsh_dir/t.py"
27
28 # usage: tweet [-PROFILE_NAME] [POST...]
29 # Uses variables as input:
30 # $media for image file path.
31 # $alt_text for image alt text.
32 tweet() {
33 local oath
34 oath=$HOME/.rainbow_oauth
35 if [[ $1 == -* ]]; then
36 rm -f $oath
37 ln -s $oath$1 $oath
38 shift
39 fi
40 source ~/src/twitter/venv/bin/activate
41 {
42 printf "%s\n" "$*"
43 if [[ $media ]]; then
44 printf "%s\n" "$media"
45 if [[ $alt_text ]]; then
46 printf "%s\n" "$alt_text"
47 fi
48 fi
49 } | $_pdtsh_tweet
50 deactivate
51 }
52
53 # usage: rbow [-PROFILE_NAME] [COMMAND]
54 #
55 # Wrapper for rainbowstream to use multiple logins and tweet directly
56 # from the command line. PROFILE_NAME can be anything you want.
57 rbow() {
58 local oath f
59 oath=$HOME/.rainbow_oauth
60 if [[ $1 == -* ]]; then
61 rm -f $oath
62 ln -s $oath$1 $oath
63 shift
64 fi
65 source ~/src/rainbowstream/venv/bin/activate
66 if (($#)); then
67 f=$(mktemp)
68 cat >$f <<'EOF'
69 # adds a short delay after each send for more reliable operation
70 set force_conservative 1
71 spawn "rainbowstream"
72 # wait for prompt
73 expect -nocase timeout {exit 1} "@*]: "
74 set cmd [lindex $argv 0];
75 send "$cmd\r\r"
76 expect -nocase timeout {exit 1} "@*]: "
77 # this may not be needed. didnt test
78 sleep 2
79 send "q\r"
80 interact
81 EOF
82 expect $f "$*" || { echo error: expect failed; deactivate; rm -f $f; return 1; }
83 rm -f $f
84 else
85 rainbowstream
86 fi
87 deactivate
88 }
89
90
91 # usage: toot [-PROFILE_NAME] [TOOT_ARGS]
92 toot() {
93 local mast_profile
94 source ~/src/toot/venv/bin/activate
95 if [[ $1 == -* ]]; then
96 mast_profile=${1#-}
97 shift
98 command toot activate --quiet $mast_profile || { deactivate; return 1; }
99 fi
100 command toot "$@" || { deactivate; return 1; }
101 deactivate
102 }
103
104
105 # post to mastodon + twitter + gnu social post
106 #
107 # If posting video, it only goes to twitter. Other accounts can be
108 # posted to manually with free software.
109 #
110 # Alt text only goes to mastodon.
111 #
112 # usage: pdt [-s mastodon|twitter|gnusocial] [--dbd] [-m IMAGE_FILE] [-a ALT_TEXT] [-v VIDEO_PATH] [POST]
113 # -s mastodon|twitter|gnusocial = post to a single social network.
114 # -m IMAGE_FILE = Uploads IMAGE_FILE. if MEDIA_FILE.txt exists, the last line of that file will be used as ALT_TEXT
115 # unless -a has been used.
116 pdt() {
117 local video media twitter_account gs_account mastodon_account video gs_arg network
118 local do_mastodon do_twitter do_gnusocial
119 local -a toot_args
120 if [[ $pdttest ]]; then
121 twitter_account=iank
122 gs_account=fsfes
123 mastodon_account=fsftest@hostux.social
124 else
125 twitter_account=fsf
126 gs_account=fsf
127 mastodon_account=fsf@hostux.social
128 fi
129 video=false
130 do_mastodon=true
131 # no twitter by default.
132 do_twitter=false
133 do_gnusocial=true
134 alt_text=
135 while [[ $1 == -* ]]; do
136 case $1 in
137 -s)
138 network="$2"
139 do_mastodon=false
140 do_twitter=false
141 do_gnusocial=false
142 case $network in
143 mastodon)
144 do_mastodon=true
145 ;;
146 twitter)
147 do_twitter=true
148 ;;
149 gnusocial)
150 do_gnusocial=true
151 ;;
152 *)
153 echo "pdt: error: expected -s mastodon|twitter|gnusocial"
154 return 1
155 ;;
156 esac
157 shift 2
158 ;;
159 -a)
160 alt_text="$2"
161 shift 2
162 ;;
163 -m)
164 media="$2"
165 shift 2
166 toot_args+=(--media "$media" )
167 gs_arg="-F media=@$media"
168 ;;
169 --dbd)
170 twitter_account=dbd
171 gs_account=dbd
172 mastodon_account=endDRM@hostux.social
173 shift
174 ;;
175 -v)
176 video=true
177 media="$2"
178 shift 2
179 ;;
180 esac
181 done
182 if [[ $media ]]; then
183 if [[ ! -e $media ]]; then
184 echo "error: file not found $media"
185 return 1
186 fi
187 if [[ $media == *\ * ]]; then
188 echo "error: file path contains a space. move it to non-space path"
189 return 1
190 fi
191 if [[ ! $alt_text && -r $media.txt && -s $media.txt ]]; then
192 alt_text=$(tail -n1 $media.txt)
193 fi
194 if [[ $alt_text ]]; then
195 toot_args+=(--description "$alt_text" )
196 fi
197 fi
198 # if we have no argument
199 if (( ! $# )); then
200 read -r -p "input PDT text: " input
201 set -- "$input"
202 fi
203 if [[ $- == *i* ]]; then
204 echo "About to PDT the following line. Press enter to confirm or ctrl-c to quit:"
205 echo "$*"
206 read -r
207 fi
208 if $video; then
209 local oath
210 oath=$HOME/.rainbow_oauth
211 rm -f $oath
212 ln -s ${oath}-$twitter_account $oath
213 python3 ~/src/video-tweet/async-upload.py "$media" "$*"
214 return
215 fi
216 fails=()
217 if $do_twitter; then
218 if ! tweet -$twitter_account "$*"; then
219 fails+=(tweet)
220 fi
221 fi
222 if $do_mastodon; then
223 if ! toot -$mastodon_account post "$*" "${toot_args[@]}"; then
224 fails+=(toot)
225 fi
226 fi
227 if $do_gnusocial; then
228 # https://gnusocial.net/doc/twitterapi
229 if ! curl -o /dev/null -sS -u "$gs_account:$(cat ~/.gnusocial_login-$gs_account)" \
230 $gs_arg -F "status=$*" https://status.fsf.org/api/statuses/update.xml; then
231 fails+=(gnu-social)
232 fi
233 fi
234 if (( ${#fails[@]} )); then
235 printf "$(tput setaf 5)█$(tput sgr0)%.0s" $(eval echo "{1..${COLUMNS:-60}}"); echo
236 echo "FSF ERROR: ${fails[*]} might not have posted" >&2
237 fi
238 }
239
240 # Usage: toot-setup
241 #
242 # Only run manually for testing.
243 #
244 # this expects pdt-pip-setup has been run already, and it doesn't setup
245 # errhandle.
246 #
247 # note: auth info is stored at ~/.config/toot/config.json
248 pdt-toot-setup() {
249 local -a twitter_accounts mastodon_accounts
250 if [[ $pdttest ]]; then
251 twitter_accounts=(iank)
252 mastodon_accounts=(fsftest)
253 else
254 twitter_accounts=(fsf dbd)
255 mastodon_accounts=(fsf endDRM)
256 fi
257
258 rm -rf ~/src/toot
259 mkdir -p ~/src/toot
260 cd ~/src/toot
261 # on t11, got myself into a situation where when doing pip install virtualenv,
262 # it gave an error that /usr/bin/pip didn't exist, so i did
263 # sudo ln -s /home/iank/.local/bin/pip /usr/bin
264 #
265 python3 -m virtualenv -p python3 venv
266 source venv/bin/activate
267 # pip freeze after a pip install, as of 2022-11-28
268 cat >requirements.txt <<'EOF'
269 beautifulsoup4==4.11.1
270 certifi==2022.9.24
271 charset-normalizer==2.1.1
272 idna==2.8
273 requests==2.22.0
274 soupsieve==2.3.2.post1
275 toot==0.29.0
276 urllib3==1.25.8
277 urwid==2.1.2
278 wcwidth==0.2.5
279 EOF
280
281 # new 2022-11 packages
282 # beautifulsoup4==4.11.1
283 # certifi==2022.9.24
284 # charset-normalizer==2.1.1
285 # idna==3.4
286 # requests==2.28.1
287 # soupsieve==2.3.2.post1
288 # toot==0.29.0
289 # urllib3==1.26.13
290 # urwid==2.1.2
291 # wcwidth==0.2.5
292
293 # old 2019 packages
294 # beautifulsoup4==4.8.2
295 # certifi==2019.11.28
296 # chardet==3.0.4
297 # idna==2.8
298 # requests==2.22.0
299 # soupsieve==1.9.5
300 # toot==0.25.2
301 # urllib3==1.25.8
302 # urwid==2.1.0
303 # wcwidth==0.1.8
304
305 # i mixed in some old packages to get newer toot working on t9.
306
307 python3 -m pip install -r requirements.txt
308 cd -
309 deactivate
310 for account in ${mastodon_accounts[@]}; do
311 if ! toot activate $account@hostux.social &>/dev/null; then
312 printf "$(tput setaf 5)█$(tput sgr0)%.0s" $(eval echo "{1..${COLUMNS:-60}}");
313 echo "Please login to the account named \"$account\" on https://hostux.social in your main browser then press enter."
314 echo "WARNING: if you log into an account other than \"$account\", this won't work"
315 read -r
316 toot login -i hostux.social
317 fi
318 done
319 }
320
321 # Usage: pdt-pip-setup
322 pdt-pip-setup() {
323 if [[ ! -e ~/.local/bin/pip ]]; then
324 tmp=$(mktemp)
325 pyver=$(python3 --version | sed -r 's/.*(3\.[0-9]+).*/\1/')
326 echo fyi: detected pyver: $pyver. this should look something like 3.6
327
328 if dpkg --compare-versions 3.6 ge $pyver; then
329 # The bootstrap script at https://bootstrap.pypa.io/get-pip.py required 3.7+
330 # when i wrote this.
331 wget -O$tmp https://bootstrap.pypa.io/pip/$pyver/get-pip.py
332 else
333 wget -O$tmp https://bootstrap.pypa.io/get-pip.py
334 fi
335 python3 $tmp --user
336 hash -r
337 fi
338 python3 -m pip install --user -U virtualenv
339 }
340
341 # generally only meant to be called internally from pdt-setup
342 pdt-twitter-setup() {
343 # twitter setup
344 mkdir -p ~/src/twitter
345 cd ~/src/twitter
346 python3 -m virtualenv -p python3 venv
347 source venv/bin/activate
348 # as of 2022-11-30
349 cat >requirements.txt <<'EOF'
350 certifi==2022.9.24
351 twitter==1.19.6
352 EOF
353 python3 -m pip install -r requirements.txt
354 deactivate
355 cd -
356
357 # note: we could ditch rainbowstream altogether and just pass around
358 # oath api key string in a password file or something if we learned
359 # how to get it from the new twitter client im using. I'm just keeping
360 # this because it works and I like the format of how it stores its
361 # auth secrets.
362 cd ~/src/rainbowstream
363 python3 -m virtualenv -p python3 venv
364 source venv/bin/activate
365 python3 -m pip install -r requirements.txt
366 python3 -m pip install -e .
367 deactivate
368
369 for account in ${twitter_accounts[@]}; do
370 if [[ ! -s ~/.rainbow_oauth-$account ]]; then
371 printf "$(tput setaf 5)█$(tput sgr0)%.0s" $(eval echo "{1..${COLUMNS:-60}}");
372 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"
373 read -r
374 rbow -$account
375 fi
376 done
377 for account in ${twitter_accounts[@]}; do
378 if [[ ! -s $HOME/.rainbow_oauth-$account ]]; then
379 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"
380 return 1
381 fi
382 done
383 if [[ ! $pdttest ]] && diff -q $HOME/.rainbow_oauth-fsf $HOME/.rainbow_oauth-dbd &>/dev/null; then
384 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"
385 return 1
386 fi
387 cd -
388 }
389
390
391 # Usage: pdt-setup
392 pdt-setup() {
393
394 start_dir="$PWD"
395 mkdir -p ~/src
396 cd ~/src
397 for repo in errhandle rainbowstream video-tweet; do
398 if [[ -e $repo ]]; then
399 cd $repo
400 git fetch
401 git reset --hard origin/master
402 git clean -xfffd
403 cd ..
404 else
405 git clone https://vcs.fsf.org/git/$repo.git
406 fi
407 done
408 cd "$start_dir"
409 source ~/src/errhandle/err
410 cp ~/src/rainbowstream/rainbowstream/colorset/config ~/.rainbow_config.json
411
412 pdt-pip-setup
413
414 pdt-toot-setup
415
416 pdt-twitter-setup
417
418
419 for account in dbd fsf; do
420 if [[ ! -s ~/.gnusocial_login-$account ]]; then
421 printf "$(tput setaf 5)█$(tput sgr0)%.0s" $(eval echo "{1..${COLUMNS:-60}}");
422 read -r -p "please enter the password for $account@status.fsf.org > " pass
423 touch ~/.gnusocial_login-$account
424 chmod 600 ~/.gnusocial_login-$account
425 printf "%s\n" "$pass" > ~/.gnusocial_login-$account
426 fi
427 done
428
429 err-allow
430 echo "pdt-setup complete"
431 }