538 righe
		
	
	
		
			17 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			538 righe
		
	
	
		
			17 KiB
		
	
	
	
		
			Bash
		
	
	
		
			Executable File
		
	
	
	
	
| #!/bin/bash
 | |
| _name="txtsh"
 | |
| _author="mio"
 | |
| _desc="(t[e^]kst\"ish), n. a Bash shell client for twtxt, a microblogging \
 | |
| service."
 | |
| _version="0.3"
 | |
| _moddate="2017-03-05"
 | |
| _license="BSD-3"
 | |
| 
 | |
| 
 | |
| # Defaults
 | |
| _def_conf="$HOME/.config/$_name/config"
 | |
| 
 | |
| # Config options
 | |
| _options=(\
 | |
| "nick = user" \
 | |
| "twtfile = $HOME/.config/$_name/twtxt.txt" \
 | |
| "twturl = https://example.tld/twtxt.txt" \
 | |
| "check_following = True" \
 | |
| "use_pager = False" \
 | |
| "porcelain = False" \
 | |
| "disclose_identity = False" \
 | |
| "character_limit = 140" \
 | |
| "character_warning = 140" \
 | |
| "limit_timeline = 20" \
 | |
| "timeout = 5.0" \
 | |
| "sorting = descending" \
 | |
| "pre_tweet_hook = \"\"" \
 | |
| "post_tweet_hook = \"\"" \
 | |
| # The options below are not part of twtxt spec
 | |
| "character_limit_on = True" \
 | |
| "character_warning_on = True" \
 | |
| "limit_search = 20" \
 | |
| "editor = vi" \
 | |
| )
 | |
| _registries=(\
 | |
| "https://registry.twtxt.org/api/plain/" \
 | |
| )
 | |
| 
 | |
| # (Formatting) Unicode symbols and ANSI escape codes
 | |
| _fmt_mkr="🞚 "
 | |
| _fmt_url="⛺ "
 | |
| _fmt_date="⌚ "
 | |
| _fmt_bold="\033[1m"
 | |
| _fmt_reset="\033[0m"
 | |
| _fmt_ts="+%Y-%m-%d %H:%M %Z"
 | |
| 
 | |
| 
 | |
| # Create config and data files
 | |
| create_config() {
 | |
|     mkdir -p "$(dirname $_def_conf)"
 | |
| 
 | |
|     # Replace default values with user input
 | |
|     if [ -n "$1" ]; then _options[0]="nick = $1"
 | |
|     else _options[0]="nick = $(whoami)"; fi
 | |
|     if [ -n "$2" ]; then _options[1]="twtfile = $2"; fi
 | |
|     if [ -n "$3" ]; then _options[2]="twturl = $3"; fi
 | |
| 
 | |
|     # Output config
 | |
|     echo -e "[twtxt]" > $_def_conf
 | |
|     for opt in "${_options[@]}"; do
 | |
|         echo -e "$opt" >> $_def_conf
 | |
|     done
 | |
|     echo -e "\n[following]\n\n[registries]" >> $_def_conf
 | |
|     for opt in "${_registries[@]}"; do
 | |
|         echo -e "$opt" >> $_def_conf
 | |
|     done
 | |
| 
 | |
|     # Create twtfile if it doesn't exist
 | |
|     if [ ! -f "${_options[1]/* = /}" ]; then
 | |
|         mkdir -p "$(dirname ${_options[1]/* = /})"
 | |
|         touch "${_options[1]/* = /}"
 | |
|     fi
 | |
| }
 | |
| 
 | |
| 
 | |
| # Load config file
 | |
| load_config() {
 | |
|     if [ ! -f $_def_conf ]; then
 | |
|         echo -e "Error: no config file found. Try running \
 | |
| \"$_name quickstart\" first."; exit 1
 | |
|     else
 | |
|         # Load config into array
 | |
|         local old_ifs=$IFS
 | |
|         local csection
 | |
|         readarray buffer < $_def_conf
 | |
|         IFS=$'\n'
 | |
|         for line in ${buffer[@]}; do
 | |
|             case "$line" in
 | |
|                 \[twtxt\]) csection="twtxt";;
 | |
|                 \[following\]) csection="following";;
 | |
|                 \[registries\]) csection="registries";;
 | |
|             esac
 | |
|             # Store followings and registries in one variable each
 | |
|             if [ "$csection" = "following" ] && \
 | |
|                 [ ! "$line" = "[following]" ]; then
 | |
|                 following="${following}$line\n"
 | |
|             fi
 | |
|             if [ "$csection" = "registries" ] && \
 | |
|                 [ ! "$line" = "[registries]" ]; then
 | |
|                 registries=("${registries[@]}" "$line")
 | |
|             fi
 | |
|             # Set variables for the other options
 | |
|             if [ -n "$line" ] && [[ ! "$line" =~ "[" ]] && \
 | |
|                 [ ! "$csection" = "following" ] && \
 | |
|                 [ ! "$csection" = "registries" ]; then
 | |
|                 # if: user config value is empty, reset to default value
 | |
|                 # else: assign value
 | |
|                 if [[ "${line/* = /}" =~ "=" || -z "${line/* = /}" ]] && \
 | |
|                     [ "$csection" = "twtxt" ]; then
 | |
|                     for opt in "${_options[@]}"; do
 | |
|                         if [[ "$opt" =~ "${line/ */}" ]]; then
 | |
|                             eval "${opt/ = */}=${opt/* = /}"
 | |
|                         fi
 | |
|                     done
 | |
|                 else
 | |
|                     eval "${line/ = */}=${line/* = /}"
 | |
|                 fi
 | |
|             fi
 | |
|         done
 | |
|         IFS=$old_ifs
 | |
|     fi
 | |
| }
 | |
| 
 | |
| 
 | |
| # Edit config values
 | |
| edit_config() {
 | |
|     if [ -z "$1" ]; then
 | |
|         echo -e "Please specify a setting and value."; exit 1
 | |
|     elif [ -n "$1" ] && [ -n "$2" ]; then
 | |
|         # Edit values
 | |
|         load_config
 | |
|         case "$1" in
 | |
|             nick) sed -i "s|nick = $nick|nick = $2|" $_def_conf;;
 | |
|             twtfile) sed -i "s|twtfile = $twtfile|twtfile = $2|" $_def_conf;;
 | |
|             twturl) sed -i "s|twturl = $twturl|twturl = $twturl|" $_def_conf;;
 | |
|             *) echo -e "See \"$_name config\" for options."
 | |
|         esac
 | |
|     else
 | |
|         # Output current values
 | |
|         load_config
 | |
|         case "$1" in
 | |
|             nick) echo -e "$nick";;
 | |
|             twtfile) echo -e "$twtfile";;
 | |
|             twturl) echo -e "$twturl";;
 | |
|             edit) eval $editor "$_def_conf";;
 | |
|             *) cat $_def_conf;;
 | |
|         esac
 | |
|     fi
 | |
| }
 | |
| 
 | |
| 
 | |
| # Wrap curl commands with presets
 | |
| # to manage flags for all outgoing requests
 | |
| curlp() {
 | |
|     local ccmd="curl -L -s --connect-timeout $timeout"
 | |
| 
 | |
|     # Include user-agent string with outgoing requests if enabled
 | |
|     if [ "$disclose_identity" = "True" ]; then
 | |
|         ccmd="${ccmd} -A $_name (twtxt)/$_version (+$twturl; @$nick)"
 | |
|     fi
 | |
| 
 | |
|     case $1 in
 | |
|         post) echo -e "$($ccmd -X POST $2)";;
 | |
|         src) echo -e "$($ccmd $2)";;
 | |
|         status) echo -e $($ccmd -I $2 | head -n 1 | cut -f 2 -d " ");;
 | |
|     esac
 | |
| }
 | |
| 
 | |
| 
 | |
| # Format a string containing twtxt data for display
 | |
| format_twdata() {
 | |
|     local tdata ts twt
 | |
|     local old_ifs=$IFS
 | |
| 
 | |
|     # Limit number of results and sort order
 | |
|     case $1 in
 | |
|         search)
 | |
|             tdata=`echo -e "$2" | tail -n $limit_search`
 | |
|             if [ "$sorting" = "descending" ]; then
 | |
|                 tdata=$(echo -e "$tdata" | sort -r)
 | |
|             fi;;
 | |
|         timeline*)
 | |
|             tdata=`echo -e "$2" | tail -n $limit_timeline`
 | |
|             if [ "$sorting" = "descending" ]; then
 | |
|                 tdata=$(echo -e "$tdata" | sort -r)
 | |
|             fi;;
 | |
|     esac
 | |
| 
 | |
|     # Display data
 | |
|     # Porcelain view
 | |
|     if [ "$porcelain" = "True" ]; then
 | |
|         echo -e "$tdata"; exit 0
 | |
|     fi
 | |
|     # Formatted view
 | |
|     IFS=$'\n'
 | |
|     for item in ${tdata[@]}; do
 | |
|         case $1 in
 | |
|             search|timeline)
 | |
|                 # Format: nick, twturl, timestamp, tweet
 | |
|                 # Disable ANSI codes when use_pager is enabled
 | |
|                 if [ "$use_pager" = "True" ]; then
 | |
|                     _fmt_bold=""; _fmt_reset=""
 | |
|                 fi
 | |
|                 # Format fields
 | |
|                 ts=$(echo ${item} | cut -f 3)
 | |
|                 echo -e $_fmt_mkr${_fmt_bold}$(echo ${item} | cut -f 1)\
 | |
| ${_fmt_reset}
 | |
|                 echo -e $_fmt_url$(echo ${item} | cut -f 2)
 | |
|                 echo -e $_fmt_date$(date -d$ts $_fmt_ts)
 | |
|                 twt=`echo ${item} | cut -f 4`
 | |
|                 if [ "$character_limit_on" = "True" ] && \
 | |
|                     [ ${#twt} -gt $character_limit ]; then
 | |
|                     twt=$(echo -e "$twt" | cut -c 1-$character_limit)
 | |
|                 fi
 | |
|                 # Check tweet is not empty string
 | |
|                 # e.g. user search has no tweets
 | |
|                 if [ -z "$twt" ]; then echo "";
 | |
|                 else echo -e $twt"\n"; fi;;
 | |
|             timeline_me)
 | |
|                 # Format: timestamp, tweet
 | |
|                 ts=$(echo ${item} | cut -f 1)
 | |
|                 echo -e $_fmt_date$(date -d$ts $_fmt_ts)
 | |
|                 echo -e $(echo ${item} | cut -f 2)"\n";;
 | |
|         esac
 | |
|     done
 | |
|     IFS=$old_ifs
 | |
| }
 | |
| 
 | |
| 
 | |
| # Check for pager before output if available and enabled
 | |
| # Takes data formatting type and twtxt data string as inputs
 | |
| output_twdata() {
 | |
|     local fdata
 | |
|     local if_pager=`whereis less | grep "/less"`
 | |
|     if [ "$use_pager" = "True" ] && [ -n "$if_pager" ]; then
 | |
|         fdata=`format_twdata "$1" "$2"`
 | |
|         echo -e "$fdata" > $(dirname $_def_conf)/pager.tmp
 | |
|         less $(dirname $_def_conf)/pager.tmp
 | |
|         rm -rf $(dirname $_def_conf)/pager.tmp
 | |
|     else
 | |
|         format_twdata "$1" "$2"
 | |
|     fi
 | |
| }
 | |
| 
 | |
| 
 | |
| # Add a source to following list
 | |
| follow() {
 | |
|     local is_following=$(echo $following | grep "$2")
 | |
|     if [ -n "$1" ] && [ -n "$2" ] && [ -z "$is_following" ]; then
 | |
|         sed -i "s|\[following\]|\[following\]\n$1\ =\ $2|" $_def_conf
 | |
|         echo -e "You are now following ${_fmt_bold}$1${_fmt_reset}."
 | |
|     elif [ -n "$is_following" ]; then
 | |
|         echo -e "You are already following ${_fmt_bold}$1${_fmt_reset}."
 | |
|     else
 | |
|         echo -e "Usage: $_name follow [nick] [url]"
 | |
|     fi
 | |
| }
 | |
| 
 | |
| 
 | |
| # List sources user is following
 | |
| following() {
 | |
|     if [ -z "$following" ]; then
 | |
|         echo -e "You haven't followed anyone yet."
 | |
|     else
 | |
|         local fol
 | |
|         local fstatus="404"
 | |
|         printf "$following" | while read line; do
 | |
|             # Format: nick @ twturl (status_code)
 | |
|             fol="${_fmt_bold}${line/ = */}${_fmt_reset} @ ${line/* = /}"
 | |
|             if [ "$check_following" = "True" ]; then
 | |
|                 fstatus=`curlp "status" "${line/* = /}"`
 | |
|                 fol="${fol} ($fstatus)"
 | |
|             fi
 | |
|             echo -e "$fol"
 | |
|         done
 | |
|     fi
 | |
| }
 | |
| 
 | |
| 
 | |
| # Interactive setup
 | |
| quickstart() {
 | |
|     echo -e "---------- $_name quickstart ----------\n"
 | |
|     # Check for existing conf
 | |
|     if [ -f "$_def_conf" ]; then
 | |
|         read -p "A config file already exists. Overwrite it? [Y/n] " ow_config
 | |
|         while [[ ! "$ow_config" = "y"  &&  ! "$ow_config" = "n" ]]; do
 | |
|             read -p "Please answer 'y' or 'n': " ow_config
 | |
|         done
 | |
|         if [ "$ow_config" = "n" ]; then
 | |
|             mv $_def_conf $_def_conf-$(date +"%Y-%m-%d-%H%M");
 | |
|             echo -e "Old config copied to $_def_conf-$(date +"%Y-%m-%d-%H%M")"
 | |
|         fi
 | |
|     fi
 | |
| 
 | |
|     read -p "What's your nick? [$(whoami)] " nick
 | |
|     read -p "Where do you want to store your tweets? (Enter full path) \
 | |
| [${_options[1]/* = /}] " twtfile
 | |
|     read -p "Where can others find your tweets? (File should end in .txt) \
 | |
| [${_options[2]/* = /}] " twturl
 | |
|     create_config "$nick" "$twtfile" "$twturl"
 | |
|     echo -e "Config created in $(dirname $_def_conf). Ready to tweet!"
 | |
| }
 | |
| 
 | |
| 
 | |
| # Add user to registry
 | |
| api_add_user() {
 | |
|      local resp=`curlp "post" \
 | |
|          "${registries[0]}users?url=$twturl&nickname=$nick"`
 | |
|     case $resp in
 | |
|         OK) echo -e "Your nick has been added to the registry. Hooray!";;
 | |
|         *) echo -e "Error: $resp";;
 | |
|     esac
 | |
| }
 | |
| 
 | |
| 
 | |
| # Search registry for a tag, tweet or user
 | |
| api_search() {
 | |
|     if [ -n "$1" ] && [ -n "$2" ]; then
 | |
|         local src_data
 | |
|         case $1 in
 | |
|             keyword) src_data=`curlp "src" "${registries[0]}tweets?q=$2"`;;
 | |
|             tag) src_data=`curlp "src" "${registries[0]}tags/$2"`;;
 | |
|             user) src_data=`curlp "src" "${registries[0]}users?q=$2"`;;
 | |
|             *) echo "Invalid search option. Options: keyword, tag, user";;
 | |
|         esac
 | |
|         if [ -z "$src_data" ]; then
 | |
|             echo -e "No search results found."; exit 0
 | |
|         else
 | |
|             output_twdata "search" "$src_data"
 | |
|         fi
 | |
|     else
 | |
|         echo -e "Usage: $_name search keyword|tag|user [keyword]"
 | |
|     fi
 | |
| }
 | |
| 
 | |
| 
 | |
| # Get timeline by type: user, mentions (registry), public (registry)
 | |
| api_timeline() {
 | |
|     local ftype src_data
 | |
|     case $1 in
 | |
|         me)
 | |
|             ftype="timeline_me"
 | |
|             src_data=`cat $twtfile`
 | |
|             if [ -z "$src_data" ]; then
 | |
|                 echo -e "Nothing to see yet. Try tweeting first?"; exit 0
 | |
|             fi;;
 | |
|         mentions)
 | |
|             ftype="timeline"
 | |
|             src_data=`curlp "src" "${registries[0]}mentions?url=$twturl"`
 | |
|             if [ -z "$src_data" ]; then
 | |
|                 echo -e "No mentions found."; exit 0
 | |
|             fi;;
 | |
|         public)
 | |
|             ftype="timeline"
 | |
|             src_data=`curlp "src" "${registries[0]}tweets"`
 | |
|             if [ -z "$src_data" ]; then
 | |
|                 echo -e "No tweets found."; exit 0
 | |
|             fi;;
 | |
|         *) echo -n "Usage: $_name timeline me|mentions|public"; exit 1;;
 | |
|     esac
 | |
|     output_twdata "$ftype" "$src_data"
 | |
| }
 | |
| 
 | |
| 
 | |
| # View another user's timeline given a nick or source url
 | |
| view() {
 | |
|     local src src_data
 | |
|     if [ -n "$1" ]; then
 | |
|         # if: check whether it's a source and attempt to resolve url
 | |
|         # elif: check for nick match
 | |
|         # else: exit with no match found
 | |
|         if [[ "$1" =~ "http://" || "$1" =~ "https://" ]]; then
 | |
|             src_data=`curlp "src" "$1"`
 | |
|         elif [ -z "$src_data" ]; then
 | |
|             src=`cat $_def_conf | grep "$1"`
 | |
|             if [ -n "$src" ]; then
 | |
|                 src_data=`curlp "src" "${src/* = /}"`
 | |
|             fi
 | |
|         else
 | |
|             echo -e "No tweets found from nick/source."; exit 1
 | |
|         fi
 | |
|         echo -e "Timeline of ${_fmt_bold}$1${_fmt_reset}:\n"
 | |
|         output_twdata "timeline_me" "$src_data"
 | |
|     else
 | |
|         echo -e "Usage: $_name view [source]"
 | |
|     fi
 | |
| }
 | |
| 
 | |
| 
 | |
| # Load tweet hook commands
 | |
| tweet_hook() {
 | |
|     local hook msg
 | |
|     case "$1" in
 | |
|         pre)
 | |
|             hook="$pre_tweet_hook"
 | |
|             msg="Running pre-tweet hook ...";;
 | |
|         post)
 | |
|             hook="$post_tweet_hook"
 | |
|             msg="Running post-tweek hook ...";;
 | |
|     esac
 | |
|     hook=`echo -e "$hook" | sed "s|\"||g"`
 | |
|     if [ -n "$hook" ]; then
 | |
|         echo -e "$msg"
 | |
|         eval "$hook" || exit 1
 | |
|         echo -e "Done."
 | |
|     fi
 | |
| }
 | |
| 
 | |
| 
 | |
| # Add a tweet to user timeline
 | |
| tweet() {
 | |
|     if [ ! -f "$twtfile" ]; then touch "$twtfile"; fi
 | |
|     # Launch external editor to compose
 | |
|     local buffer=$(dirname $_def_conf)/tweet.tmp
 | |
|     $editor $buffer
 | |
| 
 | |
|     # Check for empty tweet
 | |
|     if [ ! -f $buffer ] || [ -z "$(cat $buffer)" ]; then exit 0; fi
 | |
| 
 | |
|     # Alert user if character limit warning is enabled and limit exceeded
 | |
|     local twt="$(cat $buffer)"
 | |
|     while [ ${#twt} -gt $character_warning ] && \
 | |
|         [ "$character_warning_on" = "True" ]; do
 | |
|     read -p "Your tweet is ${#twt} chars long (limit $character_warning \
 | |
| chars). What would you like to do? [e]dit / [i]gnore : " resp_limit
 | |
|         while [[ ! "$resp_limit" = "e" &&  ! "$resp_limit" = "i" ]]; do
 | |
|             read -p "Please answer 'e' (edit) or 'i' (ignore) : " \
 | |
|                 resp_limit
 | |
|         done
 | |
|         case $resp_limit in
 | |
|             e) $editor $buffer;;
 | |
|             i) break;;
 | |
|         esac
 | |
|     done
 | |
| 
 | |
|     # Save tweet
 | |
|     tweet_hook "pre"
 | |
|     echo -e "$(date +%FT%T%:z)\t$(cat $buffer)" >> $twtfile
 | |
|     rm -rf $buffer
 | |
|     echo -e "Tweet added. Cheers!"
 | |
|     tweet_hook "post"
 | |
| }
 | |
| 
 | |
| 
 | |
| # Add new tweet (inline variant)
 | |
| # Limitation: like most bash shell scripts, using special characters at
 | |
| # the prompt without manual escaping will cause bash to throw an error.
 | |
| # Retained for convenience.
 | |
| quicktweet() {
 | |
|     if [ ! -f "$twtfile" ]; then touch "$twtfile"; fi
 | |
|     if [ -z "$1" ]; then
 | |
|         echo -e "No empty tweets, please. Try again?"; exit 0
 | |
|     fi
 | |
| 
 | |
|     # Alert user if character limit warning is enabled and limit exceeded
 | |
|     local twt="$1"
 | |
|     while [ ${#twt} -gt $character_warning ] && \
 | |
|         [ "$character_warning_on" = "True" ]; do
 | |
|     read -p "Your tweet is ${#twt} chars long (limit $character_warning \
 | |
| chars). What would you like to do? [e]dit / [i]gnore : " resp_limit
 | |
|         while [[ ! "$resp_limit" = "e" &&  ! "$resp_limit" = "i" ]]; do
 | |
|             read -p "Please answer 'e' (edit) or 'i' (ignore) : " \
 | |
|                 resp_limit
 | |
|         done
 | |
|         case $resp_limit in
 | |
|             e) read -e -i "$twt" -p "Edit tweet: " twt;;
 | |
|             i) break;;
 | |
|         esac
 | |
|     done
 | |
| 
 | |
|     # Save tweet
 | |
|     tweet_hook "pre"
 | |
|     echo -e "`date +%FT%T%:z`\t$twt" >> $twtfile
 | |
|     echo -e "Tweet added. Cheers!"
 | |
|     tweet_hook "post"
 | |
| }
 | |
| 
 | |
| 
 | |
| # Unfollow a source
 | |
| unfollow() {
 | |
|     local is_following=$(echo $following | grep "$1")
 | |
|     if [ -n "$1" ] && [ -n "$is_following" ]; then
 | |
|         sed -i "s|.*$1|__unfollowdelete|" $_def_conf
 | |
|         printf "$(cat $_def_conf | sed "/__unfollowdelete/d")" > $_def_conf
 | |
|         echo -e "You have unfollowed \
 | |
| ${_fmt_bold}${is_following/ = */}${_fmt_reset} @ $1."
 | |
|     elif [ -z "$is_following" ]; then
 | |
|         echo -e "You're not currently following this source."
 | |
|     else
 | |
|         echo -e "Please specify a source url."
 | |
|     fi
 | |
| }
 | |
| 
 | |
| 
 | |
| # Command switch
 | |
| case "$1" in
 | |
|     config)     edit_config $2 $3;;
 | |
|     follow)     load_config; follow $2 $3;;
 | |
|     following)  load_config; following;;
 | |
|     qt)         load_config; quicktweet "$2";;
 | |
|     quickstart) quickstart;;
 | |
|     register)   load_config; api_add_user;;
 | |
|     search)     load_config; api_search $2 $3;;
 | |
|     timeline)   load_config; api_timeline $2;;
 | |
|     tweet)      load_config; tweet;;
 | |
|     unfollow)   load_config; unfollow $2;;
 | |
|     view)       load_config; view $2 $3;;
 | |
|     --version|version)  echo -e "$_name v. $_version";;
 | |
|     *)  echo -e "$_name — $_desc\n\n\
 | |
| Usage: $_name [command] [args]\n\n\
 | |
| Commands:\n\
 | |
|     config\t\tEdit the config file\n\
 | |
|     follow\t\tAdd a new source to follow\n\
 | |
|     following\t\tList sources you are following\n\
 | |
|     qt\t\t\tQuickly add a new tweet\n\
 | |
|     quickstart\t\tSet up $_name (run this first!)\n\
 | |
|     register\t\tAdd your nick to registry (optional for discoverability)\n\
 | |
|     search\t\tSearch registry by keyword, tag or user\n\
 | |
|     timeline\t\tView your timeline, mentions or public timeline\n\
 | |
|     tweet\t\tAdd a new tweet from a preferred text editor\n\
 | |
|     unfollow\t\tRemove a source from your following list\n\
 | |
|     view\t\tView a source by url\n\
 | |
|     version\t\tShow the version\n\
 | |
|     help\t\tShow this help message\n\n\
 | |
| Examples:\n\
 | |
|     $_name config nick MyNick\t\t\tChange your nick to \"MyNick\"\n\
 | |
|     $_name config edit\t\t\t\tEdit config file in an editor (default: vi)\n\
 | |
|     $_name follow Foo https://foo.tld/twtxt.txt\tFollow user \"Foo\"\n\
 | |
|     $_name search tag blog\t\t\tSearch registry for tweets with \"blog\" tag\n\
 | |
|     $_name timeline me\t\t\t\tView your timeline\n\
 | |
|     $_name qt \"Hello world!\"\t\t\tTweet a message\n\
 | |
|     $_name unfollow https://foo.tld/twtxt.txt\tRemove url from followings\n\
 | |
|     $_name view Foo\t\t\t\tView user Foo's twtxt if already on followings\n\
 | |
|     $_name view https://foo.tld/twtxt.txt\tView source url\n\
 | |
| ";;
 | |
| esac
 |