Initial commit

main
mio 2023-04-21 02:31:52 +00:00
commit 802412ba22
48 changed files with 4507 additions and 0 deletions

3
.gitignore vendored 100644
View File

@ -0,0 +1,3 @@
__pycache__
*.swp
ftg/config.yml

75
dotfiles/muttrc 100644
View File

@ -0,0 +1,75 @@
set realname = "mio" # Name in from field
set from = "mio@tilde.town" # From field
set hostname = "tilde.town" # @[hostname]
# Folders
set folder = ~/Mail/inbox # mail folder
set spoolfile = /var/mail/mio # mail source folder
set tmpdir = ~/Mail/tmp # Temp folder
set postponed = ~/Mail/drafts # Drafts folder
set record = ~/Mail/sent # Sent folder
set trash = ~/Mail/trash # Trash folder
# Folders displayed in the sidebar
mailboxes = ~/Mail/inbox
mailboxes = ~/Mail/drafts
mailboxes = ~/Mail/sent
mailboxes = ~/Mail/trash
mailboxes = ~/Mail/town
mailboxes = ~/Mail/list
# Mark all new as read
macro index A \
"<tag-pattern>~N<enter><tag-prefix><clear-flag>N<untag-pattern>.<enter>" \
"mark all read"
# Go to folders
macro index,pager gd <change-folder>~/Mail/drafts<enter> "drafts"
macro index,pager gs <change-folder>~/Mail/sent<enter> "sent"
macro index,pager gt <change-folder>~/Mail/trash<enter> "trash"
# Save messages to folders
bind index,pager f noop
macro index fl "<tag-prefix-cond><save-message>~/Mail/list<enter><end-cond><sidebar-open>"
macro index ft "<tag-prefix-cond><save-message>~/Mail/town<enter><end-cond><sidebar-open>"
bind attach S save-entry # Save attachment
bind compose f edit-from # Edit from field
bind index,pager G group-reply # Group reply
bind index,pager j sidebar-next # Scroll down sidebar
bind index,pager k sidebar-prev # Scroll up sidebar
bind index,pager l list-reply # List reply
bind index,pager o sidebar-open # Open folder listed in sidebar
bind index D purge-message # Delete D flag mail
bind pager <down> next-line # Scroll down in message
bind pager <up> previous-line # Scroll up in message
# For threaded view oldest to newest. Reverse if sorting by newest first.
bind pager <left> previous-undeleted # Previous message
bind pager <right> next-undeleted # Next message
set sidebar_visible = yes # Show sidebar
set sidebar_width = 20 # Sidebar width
set sidebar_format = "%B%* %?N? [%N]?" # Display: mailbox [new]
set sidebar_short_path = yes # Shorten mailbox paths
set postpone = yes # Save to drafts
set copy = yes # Save copy of sent mail
set maildir_trash = yes # d flag mail handling
set delete = yes # Move d flag mail on exit
set check_new = yes # Check mail with mailbox open
set mail_check = 600 # Check interval in seconds
set editor = "vim" # Editor to compose mail
set charset = "utf-8" # Character encoding fallback
set send_charset = "utf-8:iso-8859-2:iso-8859-1" # Encoding for sent mail
set content_type = "text/plain" # Message body Content-Type
unset confirmappend # No prompt for mail append
unmy_hdr * # Clear preset headers
unset user_agent # Disable user-agent header
set hidden_host = yes # Skip hostname
set edit_headers = yes # Allow editing headers
set use_from = yes # Use custom from
set use_domain = yes # Use custom domain
ignore * # Hide all headers except
unignore from date subject to cc # select fields

110
dotfiles/tmux.conf 100644
View File

@ -0,0 +1,110 @@
# Keybindings ----------------------------------------------------------------
# Prefix
unbind C-b
set-option -g prefix `
bind-key ` send-prefix
# Close|new|select windows and sessions
bind X kill-window
bind C-x kill-session
bind n new-window
bind N new-session
bind s choose-window
bind S choose-session
# Switch windows
bind [ previous-window
bind ] next-window
# Split windows to panes
bind V split-window -h
bind H split-window -v
# Navigate panes
bind j select-pane -D
bind k select-pane -U
bind h select-pane -L
bind l select-pane -R
# Resize panes
bind -r C-j resize-pane -D 5
bind -r C-k resize-pane -U 5
bind -r C-h resize-pane -L 5
bind -r C-l resize-pane -R 5
# Reload tmux config
bind R source-file ~/.tmux.conf \; display "Config reloaded"
# Copy mode
bind c copy-mode
bind p paste-buffer
# Mouse mode
set -g mouse on
# Appearance -----------------------------------------------------------------
# Enable 256 colours
set-option -g default-terminal "tmux-256color"
# Colours
# See https://commons.wikimedia.org/wiki/File:Xterm_256color_chart.svg
CLR1="#dfafff" # magenta
CLR2="#dadada" # light grey 253
CLR3="#c6c6c6" # light grey 251
CLR4="#767676" # medium grey 243
CLR5="#6c6c6c" # medium grey 242
CLR6="#303030" # dark grey 236
CLR7="#262626" # dark grey 235
CLR8="#121212" # dark grey 233
CLR9="#e4e4e4" # light grey 254
CLR10="#875f87" # dark magenta
# Cursor
set-option -ag terminal-overrides ",tmux*:Cr=\E]12;$CLR1\007"
# Panes
set -g window-style "fg=$CLR2,bg=$CLR6"
set -g window-active-style "fg=$CLR3,bg=$CLR7"
# Status bar
# Status refresh rate (s)
set -g status-interval 60
set -g status-bg $CLR8
set -g status-fg $CLR2
# Simple style
# To select a style, uncomment the "set -g" lines under the style
# Uncomment only one set at a time
SM_SESSION=" #[bold]#S "
SM_TIME=" #[bold]%m/%d %H:%M "
SM_USERS=" #(who | sort --key=1,1 --unique | wc -l) townies "
SM_DISK=" ~#(whoami) #(du -hs $HOME | cut -f1 -d ' ') "
SM_MAIL=" #(cat /var/spool/mail/$USER | grep ' ' && echo '✉')"
SM_LEFT="#[fg=$CLR6,bg=$CLR1]$SM_SESSION"
SM_WIN="#[fg=$CLR4,bg=$CLR8] #I #[fg=$CLR2,bg=$CLR8]#W "
SM_WIN_CUR="#[fg=$CLR5,bg=$CLR6] #I #[fg=$CLR1,bg=$CLR6,bold]#W "
SM_RIGHT=" #[fg=$CLR3]$SM_DISK $SM_MAIL $SM_USERS #[fg=$CLR6,bg=$CLR1]$SM_TIME"
set -g status-left "$SM_LEFT"
set -g window-status-format "$SM_WIN"
set -g window-status-current-format "$SM_WIN_CUR"
set -g status-right-length 50
set -g status-right "$SM_RIGHT"
# Airline style
AL_SESSION=" #[bold]⛁ #S "
AL_TIME=" ⌚ #[bold]%b %d %H:%M "
AL_USERS=" ⛑ #(who | sort --key=1,1 --unique | wc -l) townies "
AL_DISK=" ⌂ #(du -hs $HOME | cut -f1 -d ' ') "
SM_MAIL=" #(cat /var/spool/mail/$USER | grep ' ' && echo '✉') "
AL_LEFT="#[fg=$CLR6,bg=$CLR1]$AL_SESSION#[fg=$CLR1,bg=$CLR8]"
AL_WIN="#[fg=$CLR4,bg=$CLR8] #I #[fg=$CLR2,bg=$CLR8]#W #[fg=$CLR3,bg=$CLR8,bold] "
AL_WIN_CUR="#[fg=$CLR8,bg=$CLR2,bold]#[fg=$CLR4,bg=$CLR2,bold] #I #[fg=$CLR8]#W #[fg=$CLR2,bg=$CLR8]"
AL_RIGHT="#[fg=$CLR10,bg=$CLR8,bold]#[fg=$CLR9,bg=$CLR10] $AL_DISK $AL_MAIL $AL_USERS #[fg=$CLR1,bg=$CLR10]#[fg=$CLR6,bg=$CLR1,dim]$AL_TIME"
#set -g status-left "$AL_LEFT"
#set -g window-status-format "$AL_WIN"
#set -g window-status-current-format "$AL_WIN_CUR"
#set -g status-right-length 80
#set -g status-right "$AL_RIGHT"

397
dotfiles/vimrc 100644
View File

@ -0,0 +1,397 @@
" Basic settings -------------------------------------------------------------
set shell=/bin/bash " Set default shell
set encoding=utf-8 " Set file encoding to UTF-8
set nomodeline " Disable modeline (run code on file open)
set nocompatible " Reset vi compatibility presets
set backspace=indent,eol,start " Enable backspace key in insert mode
syntax on " Enable syntax highlighting
set clipboard=unnamedplus " Use system clipboard
set textwidth=79 " Column width to 79
set colorcolumn=79 " Set column to highlight for textwidth
set wrap " Visual/soft line wrap
set linebreak " Only insert breaks on breakat chars
set nolist " Disable line break, see:
set wrapmargin=0 " vim.wikia.com > tip 989
set autoindent " Auto-indent on line break
set smartindent " Smart indent
set shiftwidth=2 " Set indentation
set tabstop=2
set softtabstop=2
set expandtab " Convert tab to spaces
set shiftround " Indent in multiples of shiftwidth
set cursorline " Highlight current line
set title " Show file title
set number " Show line numbers
set ruler " Show cursor position
set showbreak=" Show line breaks
set showmatch " Show matching opening/closing char
set showcmd " Show command used (bottom bar)
set showmode " Show current mode (bottom bar)
set wildmenu " Enable menu for autocomplete options
set wildmode=list:longest,full " List matches by longest common sections, all
set wildignore=*.tmp,*~ " Exclude some filetypes from wildmenu
set splitbelow " Horizontal split below
set splitright " Vertical split to the right
set hidden " Switch between buffers without save prompt
" To autosave: :set autowrite or autowriteall
set foldenable " Enable code folding
set foldmethod=indent " See :h foldmethod for other options
set foldlevelstart=10 " 0 = all folds closed, 99 = all folds open
set lazyredraw " No redraw when running macros
set cryptmethod=blowfish " Set default encryption method
set history=1000 " Set no. of lines in undo history
set viminfo= " Disable viminfo
" To move viminfo to ~/.vim instead:
" set viminfo+=n~/.vim/viminfo
set noswapfile " Disable *.swp files
set noerrorbells " Disable sounds
set visualbell
set t_vb=
set incsearch " Start searching on input
set nohlsearch " Disble search highlighting
set ignorecase " Ignore case in search for lowercase input
set smartcase " Case-sensitive search for mixed case input
set spelllang=en_gb " Set spell check language
set timeoutlen=2000 " Set timeout (ms) for key mappings
set autochdir " Change into a file's directory on open
colorscheme gruvbox " See :color <tab> for options
set background=dark " Set light/dark bg for themes that use it
" Highlight trailing whitespace
" https://vi.stackexchange.com/q/8563
highlight ExtraWhitespace guibg=#870000 ctermbg=088
match ExtraWhitespace /\s\+$\|\t/
augroup ExtraWhitespace
au!
au BufWinEnter * match ExtraWhitespace /\s\+$/
au InsertEnter * match ExtraWhitespace /\s\+\%#\@<!$/
au InsertLeave * match ExtraWhitespace /\s\+$/
augroup END
if !has('gui_running')
set t_Co=256 " Use 256 colorscheme in terminal mode
set mouse=n " Disable mouse support
endif
" Omni Complete
set omnifunc=syntaxcomplete#Complete " Enable autocompletion
set complete=".,b,i" " Include current buffer, current file
" other loaded buffers
set dictionary+=~/.vim/dict/other " Set custom dict
" Key mappings ---------------------------------------------------------------
" Set <leader> key
let mapleader=','
" Move to beginning/end of line
nmap b 0
nmap e $
" Move to prev/next visual row
nmap j gj
nmap k gk
" Keep indent block selected
" https://github.com/bling/dotvim
vmap < <gv
vmap > >gv
" Remap ctrl+d to toggle between shell and vim
nmap <c-d> :sh<cr>
" Disable ctrl+z to avoid accidentally stopping vim
nmap <c-z> <nop>
" Move page down/up
nmap <c-j> <c-f>
nmap <c-k> <c-b>
" Buffer navigation — go to N, prev, next, left, right
nmap <leader>bg :ls<cr>:b
nmap <leader>bh :bprev<cr>
nmap <leader>bl :bnext<cr>
nmap <space>[ <c-w>h<cr>
nmap <space>] <c-w>l<cr>
" Buffer width resize — decrease, increase
nmap <leader>b- :vertical res -5<cr>
nmap <leader>b= :vertical res +5<cr>
" Buffer loading — close, new (vsplit), reload, new (same split)
nmap <leader>bc :bw<cr>
nmap <leader>bn :enew<cr>
nmap <leader>br :e<cr>
nmap <leader>bv <c-w>v:enew<cr>
" Backup file in current buffer
nmap <leader>bk :call BackupFile()<cr>
" Map delete to a black hole register (separate from cut/paste register)
map <leader>d "_d
" Map expression register (used to evaluate expressions)
imap <leader>ee <c-r>=
" Code folding — collapse all, expand all, toggle current fold
nmap <leader>fc zM<cr>
nmap <leader>fe zR<cr>
nmap <leader>ft za<cr>
" Git commands
nmap <leader>ga :!git add .<cr>
nmap <leader>gb :!git branch -b
nmap <leader>gc :!git commit -m
nmap <leader>gca :!git commit<cr>
nmap <leader>gco :!git checkout
nmap <leader>gd :!git diff<cr>
nmap <leader>gf :!git fetch<cr>
nmap <leader>gg :!git grep
nmap <leader>gl :!git log<cr>
nmap <leader>gm :!git merge<cr>
nmap <leader>gph :!git push<cr>
nmap <leader>gpl :!git pull<cr>
nmap <leader>gs :!git status<cr>
" New markdown note
nmap <leader>md :call AddNewFile('$HOME/', '', 'md')<cr>
" Toggle netrw browser
nmap <silent> <leader>nt :call ToggleNetrw()<cr>
" Map OmniComplete
imap <leader>o <c-x><c-o>
" Insert paste into file from cat input
" https://stackoverflow.com/a/2545242
nmap <leader>pp :r! cat<cr>
" Search for selection, prompt for replacement, replace all in file
" https://stackoverflow.com/a/31172452
vnoremap <leader>sa "0y<esc>:%s/<c-r>0//g<left><left>
" Prompt for search/replace text, replace all in selection
vnoremap <leader>sr :s///g<left><left><left>
" Toggle spell check
nmap <leader>sc :setl spell!<cr>
" Search in current directory
nmap <leader>sd :!grep -R <left>
" Toggle search term highlighting
nmap <leader>sh :set nohlsearch!<cr>
" Sessions — load (waits for file input), save
nmap <leader>sl :source $HOME/.vim/sessions/
nmap <leader>ss :mksession! $HOME/.vim/sessions/
" Tab navigation — close, prev, next, new
" To go directly to tab n: [n]gt
nmap <leader>tc :tabc<cr>
nmap <leader>th :tabp<cr>
nmap <leader>tl :tabn<cr>
nmap <leader>tn :tabe<cr>
" Edit/refresh to apply vimrc changes
nmap <leader>ve :tabe $MYVIMRC<cr>
nmap <leader>vr :source $MYVIMRC<cr>
" Show word count
" https://unix.stackexchange.com/a/145293
nmap <leader>wc g<c-g><cr>
vmap <leader>wc :s/\S\+//gn<cr>
" Trim leading whitespace
" https://unix.stackexchange.com/a/29619
" To reset cursor at first selected line: vmap <leader>wsl :%le<cr>
vmap <leader>wl :normal 0dw<cr>
" Trim trailing whitespace
" http://oualline.com/vim-cook.html#trim
" https://vim.fandom.com/wiki/Remove_unwanted_spaces
nmap <leader>wst :1,$s/[ <tab>]*$//<cr>
vmap <leader>wst :s/\s\+$//<cr>
" Netrw
" ----------------------------------------------------------------------------
" Settings
let g:netrw_banner = 0 " Hide info header
let g:netrw_browse_split = 3 " 0: reuse window, 1: hsplit, 2: vsplit,
" 3: new tab, 4: previous window
let g:netrw_dirhistmax = 0 " 0: disable history/bookmarks
let g:netrw_keepdir = 0 " Sync dir view and change dir paths
let g:netrw_liststyle = 0 " 0: thin, 1: long, 2: wide, 3: tree
let g:netrw_winsize = 25 " Set pane width
" Set the default path for the scratchpad used by buffer functions
" Default path: ~/.vim/scratchpad
let g:scratch_dir = 'scratchpad'
" Map keys within the file browser
" https://vonheikemen.github.io/devlog/tools/using-netrw-vim-builtin-file-explorer/
fun! MapNetrwKeys()
" Toggle hidden file visibility
nmap <buffer> . gh
" Files — copy, delete, move, rename, select
nmap <buffer> fc mc
nmap <buffer> fd D
nmap <buffer> fm mm
nmap <buffer> fr R
nmap <buffer> v mf
" Go back in history
nmap <buffer> h u
" Close file preview buffer
nmap <buffer> P <C-w>z
endfun
aug netrw_keymaps
au!
autocmd filetype netrw call MapNetrwKeys()
augroup END
" Toggle the file browser
" https://stackoverflow.com/questions/5006950/setting-netrw-like-nerdtree
fun! g:ToggleNetrw()
Lexplore
vertical resize 25
endfun
" Functions ------------------------------------------------------------------
" Trim trailing whitespace (can be used on filetype)
" e.g. au BufWrite *.* :call DeleteExtraWS()
" https://amix.dk/vim/vimrc.html
fun! g:DeleteExtraWS()
exe 'normal mz'
%s/\s\+$//ge
exe 'normal `z'
endfun
" Create a new file
fun! g:AddNewFile(path, name, ext)
let date = strftime('%Y-%m-%d')
let fn = '-' . a:name
if a:name == ''
let fn = ''
endif
exe 'tabe' a:path . '/' . date . fn . '.' . a:ext
endfun
" Backup the file in the current buffer
" https://www.ibm.com/developerworks/library/l-vim-script-2/index.html
fun! g:BackupFile()
let b:timestamp = strftime('%Y%m%d%H%M%S')
return writefile(getline(1,'$'), bufname('%') . '-' . b:timestamp)
endfun
" Wrap a word/selection in brackets
" Based on http://learnvimscriptthehardway.stevelosh.com/chapters/09.html
" and https://superuser.com/a/875160
let g:WrapWordChars = {
\ '<':'>', '{':'}', '[':']', '(':')', '"':'"', "'":"'", '`':'`',
\ }
fun! g:WrapWord(prefix)
" Temporarily disable Auto Pairs plugin if enabled
if exists('b:autopairs_enabled')
let l:isapenabled = b:autopairs_enabled
let b:autopairs_enabled = 0
endif
" Add key mappings to prefix + char for insert and select modes
for c in keys(g:WrapWordChars)
exe 'inoremap <silent>' . a:prefix . c . ' <esc>viw<esc>a' .
\ g:WrapWordChars[c] . '<esc>bi' . c . '<esc>ea<right>'
exe 'vnoremap <silent>' . a:prefix . c . ' xi' . c .
\ g:WrapWordChars[c] . '<esc>P'
endfor
" Restore user settings
if exists('b:autopairs_enabled')
let b:autopairs_enabled = l:isapenabled
endif
endfun
call g:WrapWord('<leader>w')
" Filetypes ------------------------------------------------------------------
filetype plugin indent on
" Group autocommand calls to avoid duplicates whenever writing to buffer
" http://learnvimscriptthehardway.stevelosh.com/chapters/14.html
" Set indentation by filetype (not set with other basic settings as they will
" override filetype setl whenever vimrc is reloaded)
" shiftwidth: indent/unindent width
" tabstop: tab width in spaces (view)
" softtabstop: tab width in spaces (edit)
augroup Filetypes
au!
" CSS/Sass
au FileType css,sass,scss setl shiftwidth=2 tabstop=2 softtabstop=2
\ omnifunc=csscomplete#CompleteCSS
" Gophermap needs real <tab> to convert maps
au BufRead,BufNewFile gophermap setl noexpandtab shiftwidth=4 tabstop=4
\ softtabstop=0 textwidth=70 colorcolumn=70
" HTML
au Filetype htm,html setl
\ foldmethod=indent indentkeys= shiftwidth=2 tabstop=2 softtabstop=2
" Markdown
au FileType markdown,md,mkd setl spell shiftwidth=2 tabstop=2 softtabstop=2
" Vim-inspired app configs
au BufRead,BufNewFile vifmrc set filetype=vim
au BufRead,BufNewFile vimperatorrc set filetype=vim
au FileType vim,vimrc,vimrc* set shiftwidth=2 tabstop=2 softtabstop=2
augroup END
" Plugins --------------------------------------------------------------------
" vim-emoji-complete — insert emoji
let g:emoji_complete_overwrite_standard_keymaps = 0
imap <leader>em <Plug>(emoji-start-complete)
" ALE — syntax check/linting
" Run lint manually
let g:ale_lint_on_enter = 0
let g:ale_lint_on_save = 0
let g:ale_lint_on_text_changed = 'never'
nmap <leader>ld :call ale#cursor#ShowCursorDetail()<cr>
nmap <leader>li :ALELint<cr>
nmap <leader>lj <Plug>(ale_next_wrap)
nmap <leader>lk <Plug>(ale_previous_wrap)
" Auto Pairs — auto-close brackets
let g:AutoPairs = {
\ '<':'>', '{':'}', '[':']', '(':')',
\ '"':'"', "'":"'", '`':'`',
\ '<!--':'-->',
\ '{%':'%}', '{#':'#}',
\ }
" Remap/disable unneeded mappings
let g:AutoPairsMapCR = 0
let g:AutoPairsMultilineClose = 0
let g:AutoPairsShortcutFastWrap = '<nop>'
let g:AutoPairsShortcutJump = '<leader>aj'
let g:AutoPairsShortcutToggle = '<leader>ap'
" commentary.vim — code commenting
imap <leader>/ :Commentary<cr>
vmap <leader>/ :Commentary<cr>
" snippet.vim — code/template expansion
" Key mappings in snipmate.vim/after/plugin/snipMate.vim
let g:snippets_dir = '~/.vim/snippets'
imap <leader>. <c-r>=TriggerSnippet()<cr>

View File

@ -0,0 +1,27 @@
home:
dir: "/home/user/public_gopher/ftg"
url: "/~user/ftg"
title: "feed the gopher\n\n"
info: |
A RSS feed service to browse headlines from gopher. All feeds are
unofficial and unrelated to the listed sites.
updated: "Last updated: "
timestamp: "%Y/%m/%d %H:%M %z"
nav_back: "Return to the feeds list"
temp: "The list is currently being updated. Please check again later."
feeds:
- title: tilde news
url: https://tilde.news/rss
permalink: tilde-news
- title: tildes.net ~tech
url: https://tildes.net/~tech/topics.rss
permalink: tildes-tech
update:
user_agent: "feedthegopher/0.1"
feed_file: "feed.xml"
hash_file: ".hash"
sleep: 1
skip_cache:
- youtube.com/watch

272
ftg/formatter.py 100644
View File

@ -0,0 +1,272 @@
# A set of filters to format HTML to text with barebones Markdown-style markup.
import re
class HtmlToFText:
strip_tags1 = {
# Remove comments first, which may wrap around other tags
"<!--": "-->",
# Remove classes, ids and extraneous attributes
" class=\"": "\"", " id=\"": "\"",
}
strip_tags2 = {
# Remove doctype, tags and inner html
"<!": ">",
"<applet": "</applet>",
"<aside": "</aside>",
"<base": "</base>",
"<canvas": "</canvas>",
"<form": "</form>",
"<button": "</button>",
"<input": "</input>",
"<label": "</label>",
"<head": "</head>",
"<iframe": "</iframe>",
"<menu": "</menu>",
"<nav": "</nav>",
"<noscript": "</noscript>",
"<param": "</param>",
"<progress": "</progress>",
"<rp": "</rp>",
"<script": "</script>",
"<style": "</style>",
# Remove non-functional empty links after stripping classes/ids
"<a href=\"#\"": "</a>",
"<a>": "</a>",
# Remove the tags themselves but not the inner html
"<article": ">", "</article": ">",
"<body": ">", "</body": ">",
"<div": ">", "</div": ">",
"<footer": ">", "</footer": ">",
"<header": ">", "</header": ">",
"<html": ">", "</html": ">",
"<main": ">", "</main": ">",
"<section": ">", "</section": ">",
"<span": ">", "</span": ">",
"<title": ">", "</title": ">",
# Remove currently unsupported tags
"<center": ">", "</center": ">",
"<frame": ">", "</frame": ">",
"<small": ">", "</small": ">",
# "<audio": ">", "</audio": ">",
# "<video": ">", "</video": ">",
# "<map": ">", "</map": ">",
}
strip_ws = ["\n\n", "\t", " "]
format_tags1 = {
">\n<": "><",
"<blockquote>": "\n>",
}
format_tags2 = {
"<address>": "[[address]]\n", "</address>": "\n",
"<em>": "*", "</em>": "*",
"<i>": "*", "</i>": "*",
"<h1>": "\n# ", "</h1>": "\n",
"<h2>": "\n## ", "</h2>": "\n",
"<h3>": "\n### ", "</h3>": "\n",
"<h4>": "\n#### ", "</h4>": "\n",
"<h5>": "\n##### ", "</h5>": "\n",
"<h6>": "\n###### ", "</h6>": "\n",
"<hr>": "\n---\n", "<hr/>": "\n---\n", "<hr />": "\n---\n",
"<br>": "\n", "<br/>": "\n", "<br />": "\n",
"<blockquote>": "\n>", "</blockquote>": "\n",
"<cite>": "**", "</cite>": "**",
"<code>": "`", "</code>": "`",
"<del>": "~~", "</del>": "~~",
"<ins>": "**", "</ins>": "**",
"<dl>": "\n", "</dl>": "",
"<dt>": "", "</dt>": ": ",
"<dd>": "", "</dd>": "\n",
"<figcaption>": "*", "</figcaption>": "*",
"<figure>": "Fig. ", "</figure>": "",
"<mark>": "***", "</mark>": "***",
"<p>": "\n", "</p>": "\n",
"<pre>": "\n```\n", "</pre>": "\n```\n",
"<q>": "«", "</q>": "»",
"<ruby>": "", "</ruby>": "",
"<rt>": " (", "</rt>": ")",
"<s>": "[[~~", "</s>": "~~]]",
"<strong>": "**", "</strong>": "**",
"<sub>": "", "</sub>": "",
"<sup>": "^", "</sup>": "^",
"<b>": "**", "</b>": "**",
"<u>": "__", "</u>": "__",
"&nbsp;": "\n\n",
"&#39;": "'",
"&rsquo;": "'", "&rsquo;": "'",
"&ldquo;": "\"", "&rdquo;": "\"",
"&ndash;": "",
"&copy;": "©",
}
def filter_strip_tags(self, html):
"""Strip extraneous html tags."""
txt = html
# Some tags need to be stripped before others
for tag in self.strip_tags1:
txt = re.sub(tag + ".*?" + self.strip_tags1[tag], "", txt,
flags=re.DOTALL)
for tag in self.strip_tags2:
txt = re.sub(tag + ".*?" + self.strip_tags2[tag], "", txt,
flags=re.DOTALL)
return txt
def filter_whitespace(self, html):
"""Strip extra whitespaces often found in dynamically-generated source
files."""
txt = html
for ws in self.strip_ws:
txt = txt.replace(ws, "")
return "".join(txt.split("\n\n"))
def filter_format_tags(self, html):
"""Translate select structure and format-related tags to Markdown-like
syntax."""
txt = html
for tag in self.format_tags1:
txt = txt.replace(tag, self.format_tags1[tag])
for tag in self.format_tags2:
txt = txt.replace(tag, self.format_tags2[tag])
return txt
def filter_img(self, html):
"""Translate image tags to Markdown syntax."""
txt = html
attrs = {"src": "", "title": "", "alt": ""}
imgs = re.findall("<img [a-z].*?/>", txt, flags=re.DOTALL)
for i in imgs:
for a in attrs:
if (" " + a + "=\"") in i:
attrs[a] = i.split(" " + a + "=\"")[1].split("\"")[0]
elif (" " + a + "='") in i:
attrs[a] = i.split(" " + a + "='")[1].split("'")[0]
elif (" " + a + "=") in i:
attrs[a] = i.split(" " + a + "=")[1].split(" ")[0]
if attrs["title"] != "":
md_link = "![" + attrs["alt"] + "](" + attrs["src"] + " \"" + \
attrs["title"] + "\")"
else:
md_link = "![" + attrs["alt"] + "](" + attrs["src"] + ")"
txt = txt.replace(i, md_link)
return txt
def filter_links(self, html):
"""Translate links to Markdown syntax."""
txt = html
links = re.findall("<a [a-z].*?</a>", txt, flags=re.DOTALL)
attrs = {"href": "", "title": ""}
md_link = ""
for l in links:
if " href=\"" in l:
attrs["href"] = l.split(" href=\"")[1].split("\"")[0]
attrs["title"] = l.split(">")[1].strip("</a>")
elif " href='" in l:
attrs["href"] = l.split(" href='")[1].split("'")[0]
attrs["title"] = l.split(">")[1].strip("</a>")
elif " href=" in l:
attrs["href"] = l.split(" href=")[1].split(" ")[0]
attrs["title"] = l.split(">")[1].strip("</a>")
if (attrs["href"] != "") and (attrs["title"] != ""):
md_link = "[" + attrs["title"] + "](" + attrs["href"] + ")"
txt = txt.replace(l, md_link)
return txt
def filter_embed(self, html):
"""Translate embed tags to Markdown links."""
txt = html
embeds = re.findall("<embed [a-z].*?>", txt, flags=re.DOTALL)
src = ""
for e in embeds:
if " src=\"" in e:
src = e.split(" src=\"")[1].split("\"")[0]
elif " src='" in e:
src = e.split(" src='")[1].split("'")[0]
elif " src=" in e:
src = e.split(" src=")[1].split(">")[0]
if src != "":
txt = txt.replace(e, "[embed](" + src + ")")
return txt
def filter_abbr(self, html):
"""Format abbr tags, e.g. `<abbr title="Hypertext Markup
Language">HTML</abbr>` -> `HTML [[abbr: Hypertext Markup Language]]`"""
txt = html
abbrs = re.findall("<abbr [a-z].*?</abbr>", txt, flags=re.DOTALL)
attrs = {"abbr": "", "title": ""}
abbrev = ""
for a in abbrs:
if " title=\"" in a:
attrs["title"] = a.split(" title=\"")[1].split("\"")[0]
attrs["abbr"] = a.split(">")[1].strip("</abbr>")
elif " title='" in a:
attrs["title"] = a.split(" title='")[1].split("'")[0]
attrs["abbr"] = a.split(">")[1].strip("</abbr>")
elif " title=" in a:
attrs["title"] = a.split(" title=")[1].split(" ")[0]
attrs["abbr"] = a.split(">")[1].strip("</abbr>")
if (attrs["title"] != "") and (attrs["abbr"] != ""):
abbrev = attrs["abbr"] + "[[abbr: " + attrs["title"] + "]]"
txt = txt.replace(l, abbrev)
return txt
def filter_time(self, html):
"""Format time tags, e.g. `<time datetime="1970-01-01">Today</time>` ->
`Today (1970-01-01)`."""
txt = html
timestamps = re.findall("<time.*?</time>", txt)
attrs = {"title": "", "datetime": ""}
for t in timestamps:
attrs["title"] = t.split(">")[1].strip("</time>")
if " datetime=\"" in t:
attrs["datetime"] = t.split(" datetime=\"")[1].split("\"")[0]
elif " datetime='" in t:
attrs["datetime"] = t.split(" datetime='")[1].split("'")[0]
elif " datetime=" in t:
attrs["datetime"] = t.split(" datetime=")[1].split(">")[0]
ts = attrs["title"]
if attrs["datetime"] != "":
ts += " (" + attrs["datetime"] + ")"
txt = txt.replace(t, ts)
return txt
def filter_ol(self, html):
"""Parse ordered lists. Only single-level lists are currently
supported."""
txt = html
ol = re.findall("<ol>.*?</ol>", txt, flags=re.DOTALL)
for o in ol:
li = o.replace("</li>", "").replace("</ol>", "").split("<li>")
md = ""
for l in range(1, len(li)):
md += str(l) + ". " + li[l].lstrip()
txt = txt.replace(o, "\n" + md)
return txt
def filter_ul(self, html):
"""Parse unordered lists. Only single-level lists are currently
supported (nested lists will be flattened)."""
txt = html
while ("<ul>" in txt) or ("<li>" in txt):
txt = txt.replace("<li></li>","")
txt = txt.replace("<ul>", "\n").replace("</ul>", "")
txt = txt.replace("<li>", "- ").replace("</li>", "\n")
return txt
def convert(self, html):
"""Run format filters on html string."""
text = self.filter_strip_tags(html)
text = self.filter_whitespace(text)
text = self.filter_format_tags(text)
text = self.filter_img(text)
text = self.filter_links(text)
text = self.filter_embed(text)
text = self.filter_abbr(text)
text = self.filter_time(text)
text = self.filter_ol(text)
text = self.filter_ul(text)
return text

49
ftg/hashi.py 100644
View File

@ -0,0 +1,49 @@
import hashlib
import urllib3
import os
class Hashi:
url_headers = {"user-agent": "hashi (a file hash checker)/0.1"}
hash_algorithm = hashlib.sha256()
encoded = "utf-8"
def __init__(self):
"""Initialise libraries."""
self.http = urllib3.PoolManager(headers=self.url_headers)
urllib3.disable_warnings()
self.hash_update = self.hash_algorithm.update
self.hash_hex = self.hash_algorithm.hexdigest
def fetch_url(self, url, path):
"""Fetch a remote text url and save the contents as an UTF-8 file."""
resp = self.http.request("GET", url)
os.makedirs(path.rsplit("/", 1)[0], exist_ok=True)
with open(path, "w", encoding=self.encoded) as fh:
fh.write(resp.data.decode(self.encoded))
def get_hash(self, file_path):
"""Given a text file path, get the hash of the file contents."""
with open(file_path, "r") as fh:
bf = fh.read()
self.hash_update(bf.encode(self.encoded))
return self.hash_hex()
def check_hash(self, file_path, hash_path):
"""Compare a file hash with another previously saved hash. Return a
dictionary with a boolean indicating whether the file hash has changed,
the old and new hashes."""
has_change = {"changed": False, "old": "", "new": ""}
cached_hash = ""
new_hash = self.get_hash(file_path)
try:
with open(hash_path, "r") as fh:
cached_hash = fh.read()
except FileNotFoundError:
# Treat as changed (a call to update)
has_change = {"changed": True, "old": "", "new": new_hash}
if new_hash != cached_hash:
has_change = {"changed": True, "old": cached_hash, "new": new_hash}
return has_change

225
ftg/main.py 100644
View File

@ -0,0 +1,225 @@
import feedparser
import urllib3
import yaml
import os
from shutil import rmtree
from sys import exit
from time import sleep, strftime
from hashi import Hashi
from formatter import HtmlToFText
class FTG:
def init(self, config):
"""Load the config. Please call this first before other methods."""
self.conf = self.parse_yaml(config)
self.hh = Hashi()
def run(self):
"""Download feeds and generate gophermaps."""
any_change = False
count = 0
all_feeds = []
for f in self.conf["feeds"]:
# Check feed for changes
dir_path = self.conf["home"]["dir"] + "/" + f["permalink"]
feed_path = dir_path + "/" + self.conf["update"]["feed_file"]
hash_path = dir_path + "/" + self.conf["update"]["hash_file"]
self.hh.fetch_url(f["url"], feed_path)
check = self.hh.check_hash(feed_path, hash_path)
# Build a list of feed data to regenerate the home map
f["path"] = feed_path
all_feeds.append(self.parse_rss(f))
if check["changed"]:
print("Getting update ...")
any_change = True
# Put up placeholder home map while downloading feed items
self.gen_home_map([], mode="temp")
self.parse_file_list(all_feeds[count]["items"], dir_path)
# Cache feed hash
with open(hash_path, "w") as fh:
fh.write(check["new"])
# Regenerate the map
self.gen_feed_map(all_feeds[count])
else:
print("Feed is up-to-date.")
count += 1
sleep(self.conf["update"]["sleep"])
# If any of the feeds have changed, regenerate the home map
# to ensure the permalinks to feed maps are current
if any_change:
self.gen_home_map(all_feeds)
def parse_yaml(self, yml):
"""Open a YAML file and return a dictionary of values."""
try:
fh = open(yml, "r")
data = yaml.safe_load(fh)
fh.close()
except:
print("Error: could not load config.")
exit(1)
return data
def parse_rss(self, feed):
"""Given a dictionary with a feed url, title, permalink and feed file
path, parse the url and return a feed data dictionary."""
if ("url" not in feed) or (feed["url"] == None) or \
("permalink" not in feed) or (feed["permalink"] == None):
print("Error: missing/empty field. Please check config.")
exit(1)
try:
print("Parsing " + feed["permalink"] + " ...")
resp = feedparser.parse(feed["path"])
except:
print("Error: could not parse (" + feed["url"] + ")")
exit(1)
# Insert custom fields
resp["url"] = feed["url"]
resp["permalink"] = feed["permalink"]
if ("title" in feed) and (feed["title"] != None):
resp["display_title"] = feed["title"]
else:
resp["display_title"] = resp["channel"]["title"]
return resp
def check_filetype(self, url):
"""Given a resource url, return a dictionary containing the gopher
filetype and file extension."""
meta = {}
meta["ext"] = url.rsplit(".", 1)[1]
domain = url.rsplit(".", 1)[0]
if meta["ext"] == "gif":
meta["type"] = "g"
elif (meta["ext"] == "png") or (meta["ext"].lower() == "jpg") or \
(meta["ext"].lower() == "jpeg"):
meta["type"] = "I"
elif meta["ext"] == "pdf":
meta["type"] = "d"
else:
meta = {"type": "0", "ext": "txt"}
# Return empty meta if site matches keywords list
for kw in self.conf["update"]["skip_cache"]:
if (kw in domain) or (kw in meta["ext"]):
meta = {}
break
return meta
def get_file(self, url, ext, path):
"""Save a link to file given the url, extension and file path."""
# Initialise urllib and include user-agent with request
hdrs = {"user-agent": self.conf["update"]["user_agent"]}
http = urllib3.PoolManager(headers=hdrs)
# Disable ssl warnings
urllib3.disable_warnings()
resp = http.request("GET", url)
fmt = HtmlToFText()
if ext == "txt":
try:
txt = fmt.convert(resp.data.decode("utf-8"))
with open(path, "w", encoding="utf-8") as fh:
fh.write(txt)
except UnicodeDecodeError:
# Attempt to work around "codec can't decode byte" error
# if certain this is a txt/html file
txt = fmt.convert(resp.data.decode("ISO-8859-1"))
with open(path, "w", encoding="ISO-8859-1") as fh:
fh.write(txt)
else:
try:
with open(path, "wb") as fh:
fh.write(resp.data)
except:
with open(path, "w") as fh:
fh.write("An error occurred while saving the file." + \
"Please notify the administrator.")
def parse_file_list(self, file_list, path):
"""Given a list of file urls and target directory path, save the links
as files to the path."""
count = 0
# Make sure path exists
os.makedirs(path, exist_ok=True)
for i in file_list:
count += 1
file_meta = self.check_filetype(i["link"])
if "ext" in file_meta:
print("Downloading item (" + str(count) + "/" + \
str(len(file_list)) + ") ...")
file_path = path + "/" + str(count) + "." + \
file_meta["ext"]
self.get_file(i["link"], file_meta["ext"], file_path)
sleep(self.conf["update"]["sleep"])
def clear_cache(self, path, *args, **kwargs):
"""Given a directory path and removal mode, remove the selections.
Modes: dirs, files, all"""
mode = kwargs.get("mode", "")
if (mode == "dirs") or (mode == ""):
for rt, dirs, files in os.walk(path):
for d in dirs:
rmtree(path + "/" + d)
elif (mode == "files") or (mode == ""):
for rt, dirs, files in os.walk(path):
for f in files:
os.remove(path + "/" + f)
def gen_home_map(self, feed_data, *args, **kwargs):
"""Write the top-level gophermap."""
if kwargs.get("mode", "") == "temp":
print("Placing temporary gophermap at " + \
self.conf["home"]["dir"] + " ...")
os.makedirs(self.conf["home"]["dir"], exist_ok=True)
with open(self.conf["home"]["dir"] + "/gophermap", "w") as fh:
fh.write(self.conf["home"]["title"] + \
self.conf["home"]["info"] + "\r\n" + \
self.conf["home"]["temp"])
else:
print("Generating gophermap at " + self.conf["home"]["dir"] + \
" ...")
os.makedirs(self.conf["home"]["dir"], exist_ok=True)
with open(self.conf["home"]["dir"] + "/gophermap", "w") as fh:
fh.write(self.conf["home"]["title"] + \
self.conf["home"]["info"] + "\r\n" + \
self.conf["home"]["updated"] + \
strftime((self.conf["home"]["timestamp"])) + "\n\n\n")
for f in feed_data:
fh.write("1" + f["display_title"] + "\t" + \
f["permalink"] + "\n")
def gen_feed_map(self, feed_data):
"""Given a data dictionary for a feed source, write a feed
gophermap."""
dir_path = self.conf["home"]["dir"] + "/" + feed_data["permalink"]
os.makedirs(dir_path, exist_ok=True)
self.clear_cache(dir_path)
count = 0
print("Generating gophermap " + feed_data["permalink"] + " ...")
with open(dir_path + "/gophermap", "w") as fh:
# Info text
fh.write(feed_data["display_title"] + "\r\n\n" + \
"1" + self.conf["home"]["nav_back"] + "\t" + \
self.conf["home"]["url"] + "\r\n\n" + \
"hWebsite" + "\tURL:" + feed_data["channel"]["link"] + \
"\r\n" + "hFeed" + "\tURL:" + feed_data["url"] + "\r\n\n")
# Item links
for i in feed_data["items"]:
count += 1
fh.write("h" + i["title"] + "\tURL:" + i["link"] + "\r\n")
file_meta = self.check_filetype(i["link"])
if "ext" in file_meta:
fh.write(file_meta["type"] + "(" + file_meta["ext"] + \
")\t" + str(count) + "." + file_meta["ext"] + "\r\n")
if ("author" in i) and (i["author"] != ""):
fh.write("author: " + i["author"] + "\n")
if ("date" in i) and (i["date"] != ""):
fh.write("posted: " + i["date"] + "\n")
fh.write("\n")
ftg = FTG()
ftg.init("config.yml")
ftg.run()

9
ftg/readme.md 100644
View File

@ -0,0 +1,9 @@
# feed the gopher
A RSS feed service to browse headlines from gopher.
## Requirements
- python 3
- modules: feedparser pyyaml urllib3

88
opml/mug.of.opml 100644
View File

@ -0,0 +1,88 @@
<?xml version="1.0"?>
<opml version="2.0">
<head>
<title>the most delicious opml on friend planet</title>
<dateCreated>Fri, 12 Aug 2022 01:08:10 +0000</dateCreated>
<dateModified>Fri, 12 Aug 2022 01:08:10 +0000</dateModified>
<ownerName>barista</ownerName>
<ownerEmail>barista@me.acdw.net</ownerEmail>
<ownerId>https://tildegit.org/mio/opml</ownerId>
<docs>http://dev.opml.org/spec2.html</docs>
</head>
<body>
<outline text="blogs">
<outline type="rss" text="acdw's casa"
xmlUrl="https://acdw.casa/feed.xml" htmlUrl="https://acdw.casa/" description="No description available"/>
<outline type="rss" text="blog // ~ben"
xmlUrl="https://tilde.team/~ben/blog/index.xml" htmlUrl="https://tilde.team/~ben/blog/" description="Recent content on blog // ~ben"/>
<outline type="rss" text="Benjamin Wil"
xmlUrl="https://benjaminwil.info/feed.xml" htmlUrl="https://benjaminwil.info/" description="No description available"/>
<outline type="rss" text="p1k3::feed"
xmlUrl="https://p1k3.com/feed" htmlUrl="https://p1k3.com/" description="No description available"/>
<outline type="rss" text="chrismanbrown.gitlab.io"
xmlUrl="https://chrisman.github.io/rss.xml" htmlUrl="https://chrismanbrown.gitlab.io/" description="chrisman blog"/>
<outline type="rss" text="Dozens and Dragons"
xmlUrl="https://dozensanddragons.neocities.org/rss.xml" htmlUrl="https://dozensanddragons.neocities.org" description="ttrpg blog"/>
<outline type="rss" text="Society For Putting Things On Top Of Other Things"
xmlUrl="https://society.neocities.org/rss.xml" htmlUrl="https://society.neocities.org/" description="putting things on top of other things since 1991"/>
<outline type="rss" text="It's Pro Toad and Superb Owl"
xmlUrl="https://git.tilde.town/dozens/protoadandsuperbowl/raw/branch/master/feed.xml" htmlUrl="https://git.tilde.town/dozens/protoadandsuperbowl" description="It's Pro Toad and Superb Owl!"/>
<outline type="rss" text=" Oatmeal"
xmlUrl="https://eli.li/feed.rss" htmlUrl="https://eli.li" description=" The feed of updates for Oatmeal "/>
<outline type="rss" text="Aaron Brady"
xmlUrl="https://www.insom.me.uk/feed.xml" htmlUrl="https://www.insom.me.uk/" description="No description available"/>
<outline type="rss" text="kindrobot"
xmlUrl="https://tilde.town/~kindrobot/index.xml" htmlUrl="https://tilde.town/~kindrobot/" description="Recent content on kindrobot"/>
<outline type="rss" text="~lucidiot's wiki"
xmlUrl="https://envs.net/~lucidiot/rss.xml" htmlUrl="https://envs.net/~lucidiot/" description="latest articles on ~lucidiot's personal wiki"/>
<outline type="rss" text="Brainshit"
xmlUrl="https://brainshit.fr/rss" htmlUrl="https://brainshit.fr" description="Site communautaire de partage de connaissances inutiles et d'inepties pseudo-scientifiques."/>
<outline type="rss" text="~lucidiot's ideas"
xmlUrl="https://tilde.town/~lucidiot/ideas/rss.xml" htmlUrl="https://tilde.town/~lucidiot/ideas/" description="random ideas published by ~lucidiot"/>
<outline type="rss" text="#fridaypostcard"
xmlUrl="https://tilde.town/~lucidiot/fridaypostcard.xml" htmlUrl="http://tilde.town/~jumblesale/fp.html" description="to contribute, share a link to an image on irc with the text #fridaypostcard. updated every friday"/>
<outline type="rss" text="m455.casa"
xmlUrl="https://m455.casa/feed.rss" htmlUrl="https://m455.casa" description="RSS feed for m455.casa"/>
<outline type="rss" text="Rick Carlino's Blog"
xmlUrl="https://rickcarlino.com/rss/feed.rss" htmlUrl="https://rickcarlino.com" description="The personal blog of Rick Carlino, a software tinkerer."/>
<outline type="rss" text="lipu pi jan Niko"
xmlUrl="https://tilde.town/~nihilazo/index.xml" htmlUrl="https://tilde.town/~nihilazo/" description="Recent content on lipu pi jan Niko"/>
<outline type="rss" text="(lambda (x) (create x))"
xmlUrl="https://lambdacreate.com/static/feed.xml" htmlUrl="http://lambdacreate.com" description="A blog held together entirely by lua, coffee, and crazy ideas."/>
<outline type="rss" text="coolguy.website"
xmlUrl="https://coolguy.website/rss/" htmlUrl="https://coolguy.website/" description="The personal homepage of friend, writer, code witch,"/>
</outline>
<outline text="gemlogs">
<outline type="rss" text="bakersdozen gemlog"
xmlUrl="https://portal.mozz.us/gemini/breadpunk.club/~bakersdozen/gemlog/atom.xml" htmlUrl="https://portal.mozz.us/gemini/breadpunk.club/~bakersdozen/gemlog/" description="No description available"/>
<outline type="rss" text="~mio/gemlog"
xmlUrl="https://portal.mozz.us/gemini/tilde.town/~mio/log/atom.xml" htmlUrl="https://portal.mozz.us/gemini/tilde.town/~mio/log/" description="No description available"/>
</outline>
<outline text="podcasts">
<outline type="rss" text="Lowtech Radio Gazette"
xmlUrl="http://lambdacreate.com/static/ltrg/feed.xml" htmlUrl="http://lambdacreate.com" description="A podcast recorded, produced, and published entirely on junk computers!"/>
<outline type="rss" text="trash cat tech chat"
xmlUrl="https://podcast.librepunk.club/tctc/ogg.xml" htmlUrl="https://podcast.librepunk.club/tctc/" description="No description available"/>
<outline type="rss" text="Tilde Whirl Tildeverse Podcast"
xmlUrl="https://tilde.town/~dozens/podcast/rss.xml" htmlUrl="https://tilde.town/~dozens/podcast/index.html" description="the greatest tildeverse podcast in the world"/>
</outline>
<outline text="weeds">
<outline type="rss" text="dozens weed"
xmlUrl="https://tilde.town/~dozens/rsspect/feed.xml" htmlUrl="https://tilde.town/~dozens/rsspect/feed.xml" description="my special little weed in my special little garden"/>
<outline type="rss" text="vgnfdblg"
xmlUrl="https://supervegan.neocities.org/feed.xml" htmlUrl="https://supervegan.neocities.org/feed.xml" description="a vegan food blog"/>
<outline type="rss" text="backgammon with dozens"
xmlUrl="http://tilde.town/~dozens/backgammon/rss.xml" htmlUrl="http://tilde.town/~dozens/backgammon/" description="No description available"/>
<outline type="rss" text="RSRSSS"
xmlUrl="https://envs.net/~lucidiot/rsrsss/feed.xml" htmlUrl="https://envs.net/~lucidiot/rsrsss/feed.xml" description="Really Simple Really Simple Syndication Syndication probably just shitposts and feels#8212; An RSS feed about RSS feeds"/>
<outline type="rss" text="m455's weed"
xmlUrl="https://m455.casa/weed/weed.rss" htmlUrl="https://m455.casa/weed/" description="probably just shitposts and feels"/>
</outline>
<outline text="misc">
<outline type="rss" text="linkbudz rss feed"
xmlUrl="https://linkbudz.m455.casa/feed.rss" htmlUrl="https://linkbudz.m455.casa" description="links shared by friends"/>
<outline type="rss" text="Glorious Trainwrecks - Make Games Constantly Forever"
xmlUrl="https://www.glorioustrainwrecks.com/rss.xml" htmlUrl="https://www.glorioustrainwrecks.com" description="No description available"/>
</outline>
</body>
</opml>

157
opml/opml.sh 100755
View File

@ -0,0 +1,157 @@
#!/bin/bash
# This script has not been tested with other POSIX shells.
_whoami="opml.sh"
_npc="<barista>"
_readlinkpls=`readlink -f $0`
_whereami=`dirname "$_readlinkpls"`
_wheresauce="$_whereami/urls"
_wheremug="$_whereami/mug.of.opml"
_when_picked=`date -Ru`
_when_tasted="$_when_picked"
_jam_proxy="https://portal.mozz.us/gemini/"
_gob_proxy="https://gopher.tildeverse.org/"
__twinkle="the most delicious opml on planet earth"
__barista="barista"
__pail="mail@example.tld"
__lid="https://git.tilde.town/mio/scripts/src/branch/main/opml"
__an="<?xml version=\"1.0\"?>
<opml version=\"2.0\">
<head>
<title>{{twinkle}}</title>
<dateCreated>{{when_picked}}</dateCreated>
<dateModified>{{when_tasted}}</dateModified>
<ownerName>{{barista}}</ownerName>
<ownerEmail>{{pail}}</ownerEmail>
<ownerId>{{lid}}</ownerId>
<docs>http://dev.opml.org/spec2.html</docs>
</head>
<body>"
__orange=" <outline text=\"{{kat}}\">"
__pecan=" <outline type=\"rss\" text=\"{{sprinkle}}\"
xmlUrl=\"{{seed}}\" htmlUrl=\"{{pearl}}\" description=\"{{luncheon}}\"/>"
__mousse=" </outline>"
__latte=" </body>\n</opml>"
an() {
test -f $_wheresauce || (echo "$_npc sauce or it won't happen" && exit 1)
test -z "$1" || __twinkle="$1"
test -z "$2" || __barista="$2"
test -z "$3" || __pail="$3"
test -z "$4" || __lid="$4"
test -z "$5" || _wheremug="$5"
# Header
echo -e "$_npc okay, it'll take a few minutes, why don't you get a cuppa in
the meantime? oh wait ..."
echo "$_npc *grabs a mug*"
echo -e "$__an" > $_wheremug.tmp
sed -i "s/{{twinkle}}/$__twinkle/g" $_wheremug.tmp
sed -i "s/{{when_picked}}/$_when_picked/g" $_wheremug.tmp
sed -i "s/{{when_tasted}}/$_when_tasted/g" $_wheremug.tmp
sed -i "s/{{barista}}/$__barista/g" $_wheremug.tmp
sed -i "s/{{pail}}/$__pail/g" $_wheremug.tmp
sed -i "s|{{lid}}|$__lid|g" $_wheremug.tmp
}
orange_pecan_mousse() {
# Feeds
first_kat="true"
while read lime; do
is_kat=`echo "$lime" | awk '{ print substr($0, 1, 1) }' | grep '\['`
is_char=`echo "$lime" | awk '{ print substr($0, 1, 1) }' | grep -E '\[|#'`
# Category
if [ -n "$is_kat" ] && [ "$first_kat" == "false" ]; then
echo -e "$__mousse" >> $_wheremug.tmp
fi
if [ -n "$is_kat" ]; then
echo "$_npc *layering mousse*"
first_kat="false"
kat=`echo "$lime" | awk '{ print substr($0, 2, length($0) - 2) }'`
echo -e "$__orange" >> $_wheremug.tmp
sed -i "s/{{kat}}/$kat/" $_wheremug.tmp
fi
# Feed URL
test -n "$is_char" || seed=`echo "$lime" | awk '{ print $1 }'`
if [ ! -z "$seed" ]; then
# Protocol
echo -e "$_npc *sprinkling* $seed"
is_jam=`echo "$seed" | awk '{ print substr($0, 1, 4) }' | grep 'gem'`
is_gob=`echo "$seed" | awk '{ print substr($0, 1, 4) }' | grep 'gop'`
if [ -n "$is_jam" ]; then
seed=`echo "$seed" | sed "s|gemini://|$_jam_proxy|"`
elif [ -n "$is_gob" ]; then
seed=`echo "$seed" | sed "s|gopher://|$_gob_proxy|"`
fi
germ=`curl -Ls "$seed"`
is_rss=`echo -e "$germ" | grep -m 1 "<rss"`
# Atom
if [ ! -n "$is_rss" ]; then
# Select the first instance of the closing tag and trim chars after,
# find opening tag and trim chars before, clean inner html
sprinkle=`echo -e "$germ" | grep -m 1 "<title" |
sed "/<\/title>/ s/<\/title>.*//1" |
sed "s/.*<title>//1" | sed "s/.*<title type=\"html\">//1" |
sed "s/<!\[CDATA\[//" | sed "s/\]\]>//" | sed "s/ //"`
# RSS
else
sprinkle=`echo -e "$germ" | grep -m 1 "<title>" |
sed "s/.*<title>//" | sed "s/<\/title>.*//" |
sed "s/<!\[CDATA\[//" | sed "s/\]\]>//" | sed "s/ //"`
pearl=`echo -e "$germ" | grep -m 1 "<link>" |
sed "s/.*<link>//" | sed "s/<\/link>.*//" | sed "s/ //"`
luncheon=`echo -e "$germ" | grep -m 1 "<description>" |
sed "s/.*<description>//" | sed "s/<\/description>.*//" |
sed "s/<!\[CDATA\[//" | sed "s/\]\]>//" | sed "s/ //"`
fi
# Guess the website URL from the feed URL to avoid parsing
# strings with multiple link tags and no newline delimiters,
# some feeds don't provide full paths
if [ ! -n "$is_rss" ] || [ "$pearl" == "/" ] || [ "$pearl" == "./" ];
then
bs=`basename "$seed"`
pearl=`echo -e "$seed" | sed "s/$bs//"`
fi
test -z "$luncheon" && luncheon="No description available"
echo -e "$__pecan" >> $_wheremug.tmp
sed -i "s|{{sprinkle}}|$sprinkle|g" $_wheremug.tmp
sed -i "s|{{seed}}|$seed|g" $_wheremug.tmp
sed -i "s|{{pearl}}|$pearl|g" $_wheremug.tmp
sed -i "s|{{luncheon}}|$luncheon|g" $_wheremug.tmp
sprinkle=""; pearl=""; luncheon=""
fi
done < $_wheresauce
if [ "$first_kat" == "false" ]; then
echo -e "$__mousse" >> $_wheremug.tmp
fi
}
latte() {
# Closing
echo -e "$__latte" >> $_wheremug.tmp
mv $_wheremug.tmp $_wheremug
if [ -f $_wheremug ]; then
echo "$_npc your OPML is ready, enjoy!"
else
echo -e "$_npc sorry, something got messed up, lemme know if you want me
to make another one."
rm -r $_wheremug.tmp
fi
}
case "$1" in
make)
an "$2" "$3" "$4" "$5" "$6"
orange_pecan_mousse
latte
;;
*) echo -e "$_npc $_whoami make|welp [title] [author] [author-email] \
[author-url] [output-file]";;
esac

30
opml/readme.md 100644
View File

@ -0,0 +1,30 @@
# Orange Pecan Mousse Latte
An OPML generator script for acdw's [friend planet].
Originally written as a joke at lucidiot's request.
An improved version by acdw is available at the [casa pages] repo.
[friend planet]: https://acdw.casa/fwends/
[casa pages]: https://tildegit.org/casa/pages/src/branch/main/opml
## Usage
- Add some RSS feeds to a `urls` file in the same directory as the script.
- Run the script:
```
opml.sh make [title] [author] [author-email] [author-url] [output-file]
```
Example:
```
opml.sh make "mug of opml" "barista" "barista@dev.null" \
"https://tildegit.org/casa/pages" ~/mug.of.opml
```
## License
[GCL](https://acdw.casa/gcl/)

11
opml/urls 100644
View File

@ -0,0 +1,11 @@
# Add RSS feeds here, one per line. Please see example below.
[podcasts]
# active listening - acdw
https://junk.acdw.net/active_listening/feed.xml
# low tech radio gazette - wsinatra
http://lambdacreate.com/static/ltrg/feed.xml
# tildewhirl - dozens
https://tilde.town/~dozens/podcast/rss.xml
# trashcat tech chat - trashcat
https://podcast.librepunk.club/tctc/ogg.xml

10
readme.md 100644
View File

@ -0,0 +1,10 @@
# Readme
Dotfiles, assorted scripts and content made for tilde.town and other tildes.
Some are no longer being developed, but are made available for viewing. People
are welcome to adapt them for their own use.
## License
BSD-3, unless otherwise noted.

View File

@ -0,0 +1,6 @@
# SAIBA-80
A simple markovify example to generate group names using lists of 1980s game
titles, toys, films and television series as sources.
Run: `python3 saiba80.py`

4
saiba80/saiba80 100755
View File

@ -0,0 +1,4 @@
#!/bin/sh
cd ~mio/bin/saiba80
python3 ./saiba80.py

22
saiba80/saiba80.py 100644
View File

@ -0,0 +1,22 @@
import markovify
with open("sources/combined.txt", "r") as f:
data = f.readlines()
corpus = ""
for row in data:
corpus += row.strip() + ". "
model = markovify.Text(corpus, state_size=1)
gen = None
for r in range(1):
while gen == None:
gen = model.make_short_sentence(140)
remove_chars = [".", ":", "?", "!", ", ", "A ", "Any ", "By ", "Can't ",
"For ", "From ", "In ", "Me ", "Movie", "Of ", "The ", "To ",
"What ", "When ", "Where ", "With ", "You ", "and "]
for chars in remove_chars:
gen = gen.replace(chars, "")
gen = gen.replace(chars.lower(), "")
print(gen)

View File

@ -0,0 +1,31 @@
import markovify
with open("sources/games.txt", "r") as f:
data_games = f.readlines()
with open("sources/toys.txt", "r") as f:
data_toys = f.readlines()
with open("sources/tv.txt", "r") as f:
data_tv = f.readlines()
corpus_games = ""
for row in data_games:
corpus_games += row.strip() + ". "
model_games = markovify.Text(corpus_games, state_size=1)
corpus_toys = ""
for row in data_toys:
corpus_toys += row.strip() + ". "
model_toys = markovify.Text(corpus_toys, state_size=1)
corpus_tv = ""
for row in data_tv:
corpus_tv += row.strip() + ". "
model_tv = markovify.Text(corpus_tv, state_size=1)
model = markovify.combine([model_games, model_toys, model_tv], [1, 1, 3])
gen = None
for r in range(1):
while gen == None:
gen = model.make_short_sentence(50)
print(gen.replace(".", "").replace(":", ""))

View File

@ -0,0 +1,804 @@
ABM
Akalabeth: World of Doom
Apple-Oids
Armor Attack
Astro Fighter
Astro Invader
Attack Force
Auto Racing
B-1 Nuclear Bomber
Balloon Bomber
Baseball
Basketball
Battlezone
Berzerk
Bill Budge's Space Album
Boxing
Carnival
Cheeky Mouse
Computer Bismarck
Computer Conflict
Conflict 2500
Cosmic Patrol
Crazy Balloon
Crazy Climber
Dodge 'Em
Dogfight
Dragon-Quest Adventure
Dragster
Eamon
Fishing Derby
Galactic Attack
Galactic Empire
Galactic Trader
Galaxy Invasion
Golf
Gunfighter
Heli Fire
Hellfire Warrior
Horse Racing
Invaders from Space
Invasion From Outer Space
Kaitei Takara Sagashi
King & Balloon
Labyrinth
Lords of Karma
The Mean Checkers Machine
Meta-Trek
Micro-80 Pinball Machine
Midway Campaign
Missile Command
Mission Asteroid
Mission Escape!
Money Madness
Monster Mash & Battleship
Moon Cresta
Musician
Mystery House
N-Sub
NASL Soccer
Navarone
Network
North Atlantic Convoy Raider
Odyssey: The Compleat Apventure
Olympic Decathlon
Pac-Man
Parsector V
Pelé's Soccer
Phoenix
Pinball
Planet Miners
Polaris
The Prisoner
Project Omega
Rally-X
Red Baron
Rescue at Rigel
Rip-Off
Rogue
Sea Battle
Sea Duel
The Search Series
Skiing
Slag
Space and Sport Games
Space Firebird
Space Games-3
Space Invaders II
Space Monster
Space Panic
Space Zap
Spectar
Star Castle
Star Cruiser
Star Warrior
Stellar Adventure
Stone of Sisyphys
Stratovox
Super Invasion
Spacewar
Super Nova
Tank Battalion
Targ
Time Traveler
Tycoon
Warlords
The Warp Factor
Windfall: The Oil Crisis Game
Winged Samurai
Wizard and the Princess
Wizard of Wor
Zork I
Space Invaders
Galaxian
Lunar Lander
Asteroids
Battlezone
Berzerk
Centipede
Missile Command
Pac-Man
Phoenix
Rally-X
Star Castle
Wizard of Wor
Defender
Tempest
Donkey Kong
Frogger
Scramble
Galaga
Gorf
Ms. Pac-Man
Qix
Vanguard
BurgerTime
Dig Dug
Donkey Kong Junior
Front Line
Joust
Jungle King
Kangaroo
Moon Patrol
Pengo
Pole Position
Q*bert
Robotron 2084
Gravitar
Time Pilot
Tron
Xevious
Zaxxon
Crystal Castles
Dragon's Lair
Elevator Action
Gyruss
Mappy
Mario Bros.
Sinistar
Spy Hunter
Star Trek
Star Wars
Tapper
Track & Field
1942
Karate Champ
Kung-Fu Master
Paperboy
Punch-Out!!
Action Force
Action Max
The Adventures of the Galaxy Rangers
Aerobie
AG Bear
Alphie
American Girl
Arcadia 2001
Armatron
Army Ants
Army men
Astro Wars
Atari 2600
Atari 7800
Atari Lynx
Atari XEGS
Baby Blinkins
Babyland General Hospital
Bally Astrocade
Bandai LCD Solarpower
Barbie and the Rockers
Barnyard Commandos
Bathing Beauties
Batman
Battle Beasts
Big Trak
Bionic Six
Blackstar
Blue Comet SPT Layzner
Boglin
BraveStarr
Britains Deetail
Cabbage Patch Kids
Capsela
Captain Power and the Soldiers of the Future
Care Bears
The Centurions
Changeables
Chatter Telephone
Chō Kōsoku Galvion
Chogokin
Coleco Gemini
ColecoVision
Computer Warriors
Construx
Convertors
Dancouga
Dangaioh
Defenders of the Earth
Diaclone
Dino-Riders
Domino Rally
Double Loves
Dragon Ball
Dungeons & Dragons Computer Fantasy Game
Eagle Force
Easy-Bake Oven
Entertech
Entex Adventure Vision
Entex Select-A-Game
Etch A Sketch
Family Computer Network System
Ferrorama
Fire Away
Fistful Of Monsters
Food Fighters
G.I. Joe
G.I. Joe: A Real American Hero
Galaga X6
Galaxian 2
Game & Watch
Game Boy
Garbage Pail Kids
The Get Along Gang
Get in Shape Girl
The Glo Friends
Glo Worm
Gobots
God Mazinger
Godaikin
Golden Girl and the Guardians of the Gemstones
Götz
Guess Who?
Gund Snuffles
Gundam
Hades Project Zeorymer
He-Man
Hit Stix
Hollywoods
Hot Looks
The Hugga Bunch
The Infaceables
Inhumanoids
Intellivision
Jayce and the Wheeled Warriors
Jem
Jenga
Kenner Star Wars
Keshi
Keypers
Kinkeshi
Kinnikuman
Koosh ball
Lady Lovely Locks
Lazer Tag
Lego Technic
Lolo
M.A.S.K.
Machine Robo
Madballs
Magic 8-Ball
Magical Emi, the Magic Star
Magnavox Odyssey
Majo-kit
Manglor
Maple Town
Masters of the Universe
Maxie's World
Merlin
Micro Change
Micro Machines
Microman
Micronauts
Microvision
Milton
Mini-Munchman
Missing Link
Monchhichi
Monkgomery
MoonDreamers
Mr. Potato Head
Munchman
My Buddy
My Child
My First Sony
My Little Pony
My Pet Monster
N.I.N.J.A.S.
Nerf
Nerf Blaster
Nerful
Nickelodeon Toys
Nintendo Entertainment System
Omnibot
The Other World
Pillow people
Pin Art
Pocket Rockers
Pokémon
Police Academy
Poochie
Popples
Pound Puppies
Power Lords
Powertrack
Princess of Power
Professor's Cube
Puck
Puffalump
Pyraminx
Rainbow Brite
The Real Ghostbusters
Record Breakers: World of Speed
Ring Raiders
Robo Force
Robotix
Rock Lords
Rose Petal Place
Rubik's Clock
Rubik's Cube
Rubik's Magic
Rubik's Revenge
Rubik's Snake
The Saga of Crystar
Sea Wees
Secret Wars
Sectaurs
SG-1000
She-Ra
SilverHawks
Simon
Skateboard Smack-Ups
Skip-It
Sky Commanders
Slime
Slip 'N Slide
The Smurfs merchandising
Space Pets
Speak & Spell
Spinja
Spiral Zone
Splatter Up
Star Bird
Star Fairies
Starcom: The U.S. Space Force
Starriors
Steel Monsters
Strawberry Shortcake
Super Powers Collection
Super Soaker
Sweet Secrets
Sylvanian Families
Talk 'n Play
The Talking Mickey Mouse
The Talking Mother Goose
Tandy-12
Teddy Ruxpin
Teenage Mutant Ninja Turtles
Tente
Terebikko
Teresa
ThunderCats
Transformers
Transformers: Victory
TurboGrafx-16
Vectrex
Video Challenger
Visionaries: Knights of the Magical Light
Wacky WallWalker
Watchimals
WereBear
The Wild Puffalumps
Wrestling Superstars
Wrinkles
Zoids
Berlin Alexanderplatz
Magnum PI
Nightline
Strumpet City
Yes Minister
The Smurfs
Hill Street Blues
The People's Court
Wetten
Dynasty
Postman Pat
Falcon Crest
Only Fools and Horses
Brideshead Revisited
Cheers
Knight Rider
Family Ties
Countdown
Remington Steele
Brookside
The Mysterious Cities of Gold
Chiquilladas
The Snowman
Boys from the Blackstuff
Newhart and St. Elsewhere
The A-Team
The Day After
He-Man and the Masters of the Universe
Fraggle Rock
Reading Rainbow
G.I. Joe: A Real American Hero
Press Your Luck
The Joy of Painting
An Englishman Abroad
Blackadder and Terrahawks
Airwolf
Blue Thunder
Murder, She Wrote
Miami Vice
Who's the Boss?
The Cosby Show
The Transformers
Night Court
Tales from the Darkside
Thomas the Tank Engine and Friends
Santa Barbara
The Jewel in the Crown
MacGyver
Growing Pains
ThunderCats
Neighbours
Mr. Belvedere
Larry King Live
Edge of Darkness
Winter Evening in Gagra
The Max Headroom Show
EastEnders
The Golden Girls
WrestleMania
The Oprah Winfrey Show
Perfect Strangers
Double Dare
Designing Women
Dragon Ball
Casualty
Pee-wee's Playhouse
Saint Seiya
The Singing Detective
Pingu
Full House
Duck Tales
Headbangers Ball
Thirtysomething
Teenage Mutant Ninja Turtles
ChuckleVision
Ramayan
Fireman Sam
Inspector Morse
The Bold and the Beautiful
Married... with Children
Star Trek: The Next Generation
21 Jump Street
Gen Jack Wow!
Roseanne
Mystery Science Theater 3000
In the Heat of the Night
Murphy Brown
Yo! MTV Raps
Garfield and Friends
America's Most Wanted
The Wonder Years
The Simpsons
Chip 'n Dale Rescue Rangers
Baywatch
Seinfeld
Saved By The Bell
American Gladiators
Wallace and Gromit
Hey Dude
Red Dwarf
America's Funniest Home Videos
Quantum Leap
COPS
Agatha Christie's Poirot
Shining Time Station
Family Matters
Coach
Inside Edition
9 to 5
The Agency
Airplane!
Alien Dead
Alligator
Altered States
American Gigolo
Antropophagus
Any Which Way You Can
Arrebato Rapture
Atlantic City
The Awakening
Babylon
A Bad Son
Bad Timing
The Baltimore Bullet
Berlin Alexanderplatz
The Big Brawl
The Big Red One
Bizalom
Confidence
The Black Marble
The Blood of Hussain
The Blue Lagoon
The Blues Brothers
Bon Voyage, Charlie Brown
Breaker Morant
Bronco Billy
Brubaker
Caboblanco
Caddyshack
The Candidate
Can't Stop the Music
Carny
Chann Pardesi
A Change of Seasons
The Changeling
Cheech & Chong's Next Movie
Children's Island
La Cicala
The Cicada
City of Women
The Club
Coal Miner's Daughter
The Competition
The Constant Factor
The Crime of Cuenca
Cruising
Cutting It Short
Dadar Kirti
Deeds of My Elder Brother
Death Watch
Delusion
Die Laughing
The Dogs of War
Dostana
Friendship
Elisita
The Elephant Man
Encounters of the Spooky Kind
Every Man for Himself
The Exterminator
Fade to Black
The Falls
Fame
Fatso
The Final Countdown
The First Deadly Sin
First Family
Flash Gordon
The Fog
Foolin' Around
The Formula
Foxes
Friday the 13th
The Gamekeeper
Gamera: Super Monster
Germany
Gilda Live
Gloria
The Gods Must Be Crazy
Good Riddance
The Great Rock 'n' Roll Swindle
Hangar 18
Health
Heart Beat
He Knows You're Alone
Heaven's Gate
Herbie Goes Bananas
Hero at Large
Hey Babe!
Hirak Rajar Deshe
The Hit
Home Movies
Hopscotch
How to Beat the High Cost of Living
Humanoids from the Deep
The Hunter
The Idolmaker
Inferno
Inside Moves
The Island
It's My Turn
The Jazz Singer
Just Tell Me What You Want
Karz
Kagemusha
Khubsoorat
Klondike Fever
Ladies' Choice
The Last Flight of Noah's Ark
The Last Married Couple in America
The Last Metro
The Last Witness
The Lathe of Heaven
Legend of Tianyun Mountain
The Life and Times of Rosie the Riveter
Little Darlings
Little Miss Marker
The Long Good Friday
The Long Riders
Loulou
Loving Couples
The Magician of Lublin
The Man with Bogart's Face
Manila by Night
McVicar
Melvin and Howard
Midnight Madness
The Mirror Crack'd
Mon oncle d'Amérique
Motel Hell
Mother's Day
The Mountain Men
My Bodyguard
The Nest
Nightkill
Nijinsky
The Ninth Configuration
O Megalexandros
Oh
One Trick Pony
The Orchestra Conductor
Ordinary People
Out of the Blue
Pepi
Palermo or Wolfsburg
Permanent Vacation
The Pilot
Playing for Time
Popeye
Pray TV
Private Benjamin
The Private Eyes
Prom Night
Qurbani
Raging Bull
Resurrection
The Return of the King
Return of the Secaucus 7
El Retorno del Hombre Lobo
Roadie
Le Roi et l'oiseau
Rockshow
Rough Cut
Rude Boy
Sällskapsresan
The Sea Wolves
See You in the Next War
Seems Like Old Times
Serial
The Shining
Simon
A Small Circle of Friends
Smokey and the Bandit II
Solo Sunny
Somewhere in Time
Special Treatment
Star Wars Episode V: The Empire Strikes Back
Stardust Memories
Stir Crazy
The Stunt Man
Sunday Lovers
Superman II
Terror Train
La terrazza
That Sinking Feeling
The Shining
Those Lips
Times Square
To Love the Damned
Tom Horn
Tribute
Twelve Months
The Unseen
Up the Academy
Urban Cowboy
Used Cars
Virus
Le Voyage en douce
Waiter
The Watcher in the Woods
When Time Ran Out
Where the Buffalo Roam
Wholly Moses!
Who's Singin' Over There?
Why Would I Lie?
Willie & Phil
Windows
Witches' Brew
Xanadu
The Young Master
Yūgure made
Zigeunerweisen
Fiber Helmet
Aviator
Big Hair
Bodysuit
Bolo Tie
Bon Chic Bon
Bowl Cut
Bustier
Cagoule
Cardigan
Cartwheel Hat
Cornrows
Crop Top
Cut-Off
Denim Skirt
Devilock
Devoré
Burnout
Dolphin Shorts
Dreadlocks
Feathered Hair
Fedora
Flattop
Flip Flops
Friendship Bracelet
Gel Bracelet
Goth
Gothic
Grunge
Hair Crimp
Halterneck
Harem Pants
Headband
Heavy Metal
Hi-Top Fade
High-Rise
High-Top
Homburg Hat
Hotpants
Booty Shorts
Induction Cut
Jelly Shoes
Jheri Curl
Kerchief
La Sape
Leather Shirt
Leg Warmer
Leggins
Leotard
M-1965 Field Jacket
Miniskirt
Mohair
Mohawk
Mullet
Muscle Shirt
Mushroom Hat
Newsboy Cap
Nose Pierce
Parachute Pants
Pencil Skirt
Poet Shirt
Polo Shirt
Pork Pie Hat
Power Dressing
Preppy
Punk
PVC
Quiff
Rah-Rah Shirt
Rattail
Safari Boot
Safety Pin
Shoulder Pads
Shutter Shades
Slap Bracelet
Slim-Fit Pants
Slip
Slouch Socks
Stackhat
Sundress
Suspenders
Sweater Vest
Toe Socks
Tracksuit
Trilby
Trucker Hat
Tube Top
Varsity Jacket

View File

@ -0,0 +1,89 @@
Fiber Helmet
Aviator
Big Hair
Bodysuit
Bolo Tie
Bon Chic Bon
Bowl Cut
Bustier
Cagoule
Cardigan
Cartwheel Hat
Cornrows
Crop Top
Cut-Off
Denim Skirt
Devilock
Devoré
Burnout
Dolphin Shorts
Dreadlocks
Feathered Hair
Fedora
Flattop
Flip Flops
Friendship Bracelet
Gel Bracelet
Goth
Gothic
Grunge
Hair Crimp
Halterneck
Harem Pants
Headband
Heavy Metal
Hi-Top Fade
High-Rise
High-Top
Homburg Hat
Hotpants
Booty Shorts
Induction Cut
Jelly Shoes
Jheri Curl
Kerchief
La Sape
Leather Shirt
Leg Warmer
Leggins
Leotard
M-1965 Field Jacket
Miniskirt
Mohair
Mohawk
Mullet
Muscle Shirt
Mushroom Hat
Newsboy Cap
Nose Pierce
Parachute Pants
Pencil Skirt
Poet Shirt
Polo Shirt
Pork Pie Hat
Power Dressing
Preppy
Punk
PVC
Quiff
Rah-Rah Shirt
Rattail
Safari Boot
Safety Pin
Shoulder Pads
Shutter Shades
Slap Bracelet
Slim-Fit Pants
Slip
Slouch Socks
Stackhat
Sundress
Suspenders
Sweater Vest
Toe Socks
Tracksuit
Trilby
Trucker Hat
Tube Top
Varsity Jacket

View File

@ -0,0 +1,214 @@
9 to 5
The Agency
Airplane!
Alien Dead
Alligator
Altered States
American Gigolo
Antropophagus
Any Which Way You Can
Arrebato Rapture
Atlantic City
The Awakening
Babylon
A Bad Son
Bad Timing
The Baltimore Bullet
Berlin Alexanderplatz
The Big Brawl
The Big Red One
Bizalom
Confidence
The Black Marble
The Blood of Hussain
The Blue Lagoon
The Blues Brothers
Bon Voyage, Charlie Brown
Breaker Morant
Bronco Billy
Brubaker
Caboblanco
Caddyshack
The Candidate
Can't Stop the Music
Carny
Chann Pardesi
A Change of Seasons
The Changeling
Cheech & Chong's Next Movie
Children's Island
La Cicala
The Cicada
City of Women
The Club
Coal Miner's Daughter
The Competition
The Constant Factor
The Crime of Cuenca
Cruising
Cutting It Short
Dadar Kirti
Deeds of My Elder Brother
Death Watch
Delusion
Die Laughing
The Dogs of War
Dostana
Friendship
Elisita
The Elephant Man
Encounters of the Spooky Kind
Every Man for Himself
The Exterminator
Fade to Black
The Falls
Fame
Fatso
The Final Countdown
The First Deadly Sin
First Family
Flash Gordon
The Fog
Foolin' Around
The Formula
Foxes
Friday the 13th
The Gamekeeper
Gamera: Super Monster
Germany
Gilda Live
Gloria
The Gods Must Be Crazy
Good Riddance
The Great Rock 'n' Roll Swindle
Hangar 18
Health
Heart Beat
He Knows You're Alone
Heaven's Gate
Herbie Goes Bananas
Hero at Large
Hey Babe!
Hirak Rajar Deshe
The Hit
Home Movies
Hopscotch
How to Beat the High Cost of Living
Humanoids from the Deep
The Hunter
The Idolmaker
Inferno
Inside Moves
The Island
It's My Turn
The Jazz Singer
Just Tell Me What You Want
Karz
Kagemusha
Khubsoorat
Klondike Fever
Ladies' Choice
The Last Flight of Noah's Ark
The Last Married Couple in America
The Last Metro
The Last Witness
The Lathe of Heaven
Legend of Tianyun Mountain
The Life and Times of Rosie the Riveter
Little Darlings
Little Miss Marker
The Long Good Friday
The Long Riders
Loulou
Loving Couples
The Magician of Lublin
The Man with Bogart's Face
Manila by Night
McVicar
Melvin and Howard
Midnight Madness
The Mirror Crack'd
Mon oncle d'Amérique
Motel Hell
Mother's Day
The Mountain Men
My Bodyguard
The Nest
Nightkill
Nijinsky
The Ninth Configuration
O Megalexandros
Oh
One Trick Pony
The Orchestra Conductor
Ordinary People
Out of the Blue
Pepi
Palermo or Wolfsburg
Permanent Vacation
The Pilot
Playing for Time
Popeye
Pray TV
Private Benjamin
The Private Eyes
Prom Night
Qurbani
Raging Bull
Resurrection
The Return of the King
Return of the Secaucus 7
El Retorno del Hombre Lobo
Roadie
Le Roi et l'oiseau
Rockshow
Rough Cut
Rude Boy
Sällskapsresan
The Sea Wolves
See You in the Next War
Seems Like Old Times
Serial
The Shining
Simon
A Small Circle of Friends
Smokey and the Bandit II
Solo Sunny
Somewhere in Time
Special Treatment
Star Wars Episode V: The Empire Strikes Back
Stardust Memories
Stir Crazy
The Stunt Man
Sunday Lovers
Superman II
Terror Train
La terrazza
That Sinking Feeling
The Shining
Those Lips
Times Square
To Love the Damned
Tom Horn
Tribute
Twelve Months
The Unseen
Up the Academy
Urban Cowboy
Used Cars
Virus
Le Voyage en douce
Waiter
The Watcher in the Woods
When Time Ran Out
Where the Buffalo Roam
Wholly Moses!
Who's Singin' Over There?
Why Would I Lie?
Willie & Phil
Windows
Witches' Brew
Xanadu
The Young Master
Yūgure made
Zigeunerweisen

View File

@ -0,0 +1,168 @@
ABM
Akalabeth: World of Doom
Apple-Oids
Armor Attack
Astro Fighter
Astro Invader
Attack Force
Auto Racing
B-1 Nuclear Bomber
Balloon Bomber
Baseball
Basketball
Battlezone
Berzerk
Bill Budge's Space Album
Boxing
Carnival
Cheeky Mouse
Computer Bismarck
Computer Conflict
Conflict 2500
Cosmic Patrol
Crazy Balloon
Crazy Climber
Dodge 'Em
Dogfight
Dragon-Quest Adventure
Dragster
Eamon
Fishing Derby
Galactic Attack
Galactic Empire
Galactic Trader
Galaxy Invasion
Golf
Gunfighter
Heli Fire
Hellfire Warrior
Horse Racing
Invaders from Space
Invasion From Outer Space
Kaitei Takara Sagashi
King & Balloon
Labyrinth
Lords of Karma
The Mean Checkers Machine
Meta-Trek
Micro-80 Pinball Machine
Midway Campaign
Missile Command
Mission Asteroid
Mission Escape!
Money Madness
Monster Mash & Battleship
Moon Cresta
Musician
Mystery House
N-Sub
NASL Soccer
Navarone
Network
North Atlantic Convoy Raider
Odyssey: The Compleat Apventure
Olympic Decathlon
Pac-Man
Parsector V
Pelé's Soccer
Phoenix
Pinball
Planet Miners
Polaris
The Prisoner
Project Omega
Rally-X
Red Baron
Rescue at Rigel
Rip-Off
Rogue
Sea Battle
Sea Duel
The Search Series
Skiing
Slag
Space and Sport Games
Space Firebird
Space Games-3
Space Invaders II
Space Monster
Space Panic
Space Zap
Spectar
Star Castle
Star Cruiser
Star Warrior
Stellar Adventure
Stone of Sisyphys
Stratovox
Super Invasion
Spacewar
Super Nova
Tank Battalion
Targ
Time Traveler
Tycoon
Warlords
The Warp Factor
Windfall: The Oil Crisis Game
Winged Samurai
Wizard and the Princess
Wizard of Wor
Zork I
Space Invaders
Galaxian
Lunar Lander
Asteroids
Battlezone
Berzerk
Centipede
Missile Command
Pac-Man
Phoenix
Rally-X
Star Castle
Wizard of Wor
Defender
Tempest
Donkey Kong
Frogger
Scramble
Galaga
Gorf
Ms. Pac-Man
Qix
Vanguard
BurgerTime
Dig Dug
Donkey Kong Junior
Front Line
Joust
Jungle King
Kangaroo
Moon Patrol
Pengo
Pole Position
Q*bert
Robotron 2084
Gravitar
Time Pilot
Tron
Xevious
Zaxxon
Crystal Castles
Dragon's Lair
Elevator Action
Gyruss
Mappy
Mario Bros.
Sinistar
Spy Hunter
Star Trek
Star Wars
Tapper
Track & Field
1942
Karate Champ
Kung-Fu Master
Paperboy
Punch-Out!!

View File

@ -0,0 +1,8 @@
## Sources
- <https://en.wikipedia.org/wiki/Category:1980_video_games>
- <https://en.wikipedia.org/wiki/Golden_age_of_arcade_video_games>
- <https://en.wikipedia.org/wiki/Category:1980s_toys>
- <https://en.wikipedia.org/wiki/1980s_in_television#1980s>
- <https://en.wikipedia.org/wiki/1980_movies>
- <https://en.wikipedia.org/wiki/Category:1980s_fashion>

View File

@ -0,0 +1,225 @@
Action Force
Action Max
The Adventures of the Galaxy Rangers
Aerobie
AG Bear
Alphie
American Girl
Arcadia 2001
Armatron
Army Ants
Army men
Astro Wars
Atari 2600
Atari 7800
Atari Lynx
Atari XEGS
Baby Blinkins
Babyland General Hospital
Bally Astrocade
Bandai LCD Solarpower
Barbie and the Rockers
Barnyard Commandos
Bathing Beauties
Batman
Battle Beasts
Big Trak
Bionic Six
Blackstar
Blue Comet SPT Layzner
Boglin
BraveStarr
Britains Deetail
Cabbage Patch Kids
Capsela
Captain Power and the Soldiers of the Future
Care Bears
The Centurions
Changeables
Chatter Telephone
Chō Kōsoku Galvion
Chogokin
Coleco Gemini
ColecoVision
Computer Warriors
Construx
Convertors
Dancouga
Dangaioh
Defenders of the Earth
Diaclone
Dino-Riders
Domino Rally
Double Loves
Dragon Ball
Dungeons & Dragons Computer Fantasy Game
Eagle Force
Easy-Bake Oven
Entertech
Entex Adventure Vision
Entex Select-A-Game
Etch A Sketch
Family Computer Network System
Ferrorama
Fire Away
Fistful Of Monsters
Food Fighters
G.I. Joe
G.I. Joe: A Real American Hero
Galaga X6
Galaxian 2
Game & Watch
Game Boy
Garbage Pail Kids
The Get Along Gang
Get in Shape Girl
The Glo Friends
Glo Worm
Gobots
God Mazinger
Godaikin
Golden Girl and the Guardians of the Gemstones
Götz
Guess Who?
Gund Snuffles
Gundam
Hades Project Zeorymer
He-Man
Hit Stix
Hollywoods
Hot Looks
The Hugga Bunch
The Infaceables
Inhumanoids
Intellivision
Jayce and the Wheeled Warriors
Jem
Jenga
Kenner Star Wars
Keshi
Keypers
Kinkeshi
Kinnikuman
Koosh ball
Lady Lovely Locks
Lazer Tag
Lego Technic
Lolo
M.A.S.K.
Machine Robo
Madballs
Magic 8-Ball
Magical Emi, the Magic Star
Magnavox Odyssey
Majo-kit
Manglor
Maple Town
Masters of the Universe
Maxie's World
Merlin
Micro Change
Micro Machines
Microman
Micronauts
Microvision
Milton
Mini-Munchman
Missing Link
Monchhichi
Monkgomery
MoonDreamers
Mr. Potato Head
Munchman
My Buddy
My Child
My First Sony
My Little Pony
My Pet Monster
N.I.N.J.A.S.
Nerf
Nerf Blaster
Nerful
Nickelodeon Toys
Nintendo Entertainment System
Omnibot
The Other World
Pillow people
Pin Art
Pocket Rockers
Pokémon
Police Academy
Poochie
Popples
Pound Puppies
Power Lords
Powertrack
Princess of Power
Professor's Cube
Puck
Puffalump
Pyraminx
Rainbow Brite
The Real Ghostbusters
Record Breakers: World of Speed
Ring Raiders
Robo Force
Robotix
Rock Lords
Rose Petal Place
Rubik's Clock
Rubik's Cube
Rubik's Magic
Rubik's Revenge
Rubik's Snake
The Saga of Crystar
Sea Wees
Secret Wars
Sectaurs
SG-1000
She-Ra
SilverHawks
Simon
Skateboard Smack-Ups
Skip-It
Sky Commanders
Slime
Slip 'N Slide
The Smurfs merchandising
Space Pets
Speak & Spell
Spinja
Spiral Zone
Splatter Up
Star Bird
Star Fairies
Starcom: The U.S. Space Force
Starriors
Steel Monsters
Strawberry Shortcake
Super Powers Collection
Super Soaker
Sweet Secrets
Sylvanian Families
Talk 'n Play
The Talking Mickey Mouse
The Talking Mother Goose
Tandy-12
Teddy Ruxpin
Teenage Mutant Ninja Turtles
Tente
Terebikko
Teresa
ThunderCats
Transformers
Transformers: Victory
TurboGrafx-16
Vectrex
Video Challenger
Visionaries: Knights of the Magical Light
Wacky WallWalker
Watchimals
WereBear
The Wild Puffalumps
Wrestling Superstars
Wrinkles
Zoids

View File

@ -0,0 +1,108 @@
Berlin Alexanderplatz
Magnum PI
Nightline
Strumpet City
Yes Minister
The Smurfs
Hill Street Blues
The People's Court
Wetten
Dynasty
Postman Pat
Falcon Crest
Only Fools and Horses
Brideshead Revisited
Cheers
Knight Rider
Family Ties
Countdown
Remington Steele
Brookside
The Mysterious Cities of Gold
Chiquilladas
The Snowman
Boys from the Blackstuff
Newhart and St. Elsewhere
The A-Team
The Day After
He-Man and the Masters of the Universe
Fraggle Rock
Reading Rainbow
G.I. Joe: A Real American Hero
Press Your Luck
The Joy of Painting
An Englishman Abroad
Blackadder and Terrahawks
Airwolf
Blue Thunder
Murder, She Wrote
Miami Vice
Who's the Boss?
The Cosby Show
The Transformers
Night Court
Tales from the Darkside
Thomas the Tank Engine and Friends
Santa Barbara
The Jewel in the Crown
MacGyver
Growing Pains
ThunderCats
Neighbours
Mr. Belvedere
Larry King Live
Edge of Darkness
Winter Evening in Gagra
The Max Headroom Show
EastEnders
The Golden Girls
WrestleMania
The Oprah Winfrey Show
Perfect Strangers
Double Dare
Designing Women
Dragon Ball
Casualty
Pee-wee's Playhouse
Saint Seiya
The Singing Detective
Pingu
Full House
Duck Tales
Headbangers Ball
Thirtysomething
Teenage Mutant Ninja Turtles
ChuckleVision
Ramayan
Fireman Sam
Inspector Morse
The Bold and the Beautiful
Married... with Children
Star Trek: The Next Generation
21 Jump Street
Gen Jack Wow!
Roseanne
Mystery Science Theater 3000
In the Heat of the Night
Murphy Brown
Yo! MTV Raps
Garfield and Friends
America's Most Wanted
The Wonder Years
The Simpsons
Chip 'n Dale Rescue Rangers
Baywatch
Seinfeld
Saved By The Bell
American Gladiators
Wallace and Gromit
Hey Dude
Red Dwarf
America's Funniest Home Videos
Quantum Leap
COPS
Agatha Christie's Poirot
Shining Time Station
Family Matters
Coach
Inside Edition

13
thirdparty/.cadastre/home.txt vendored 100644
View File

@ -0,0 +1,13 @@
2 2
[===|===|===.==|===|===]
|(o) i ((O))|
| ((O)) /_\ (o) |
| ((o)) _/___\_ |
| (o) \=-=-=-=/ ((o))|
|--------|o o o|-------|
| ` \=+==+==+=/ ` |
| ` |_||-||_| ` |
` \==+=\=+=/=+==/ `
| _o_ ||H|[_]|H|| _o_ |
| THT /_\ THT |
[[mio|oim]] [[mio|oim]

10
thirdparty/.choochoo vendored 100644
View File

@ -0,0 +1,10 @@
_____.=-[]-===-[]-===-[]-=._____
^\.__o__,,_____,,_____,,__o__./^
" -uu- -uu- -uu- "
0 *!* . 8 @
~|^ !/* ^.%. |8 |*
_ .,|. *\!. _V.. ;|, .^\_
|=-----------mmmm-----------=|
==- /==\-/==\-mmmm-/==\-/==\ -==
\__/ \__/ \__/ \__/

24
thirdparty/.tracery/8ball vendored 100644
View File

@ -0,0 +1,24 @@
{
"origin": [
"it is certain",
"it is decidedly so",
"without a doubt",
"yes — definitely",
"you may rely on it",
"as I see it, yes",
"most likely",
"outlook good",
"yes",
"signs point to yes",
"reply hazy try again",
"ask again later",
"better not tell you now",
"cannot predict now",
"concentrate and ask again",
"don't count on it",
"my reply is no",
"my sources say no",
"outlook not so good",
"very doubtful"
]
}

98
thirdparty/.tracery/bean vendored 100644
View File

@ -0,0 +1,98 @@
{
"prefix": [
"ground-",
"jelly",
"soy"
],
"suffix": [
"acre",
"bag",
"ball",
"ery",
"fest",
"land",
"ies",
"pole",
"stalk",
"town"
],
"adjective": [
"bambara",
"black",
"black turtle",
"broad",
"bush",
"calypso",
"castor",
"climbing",
"cocoa",
"coffee",
"common",
"cranberry",
"dragon tongue",
"dwarf",
"fava",
"field",
"flageolet",
"French",
"garbanzo",
"green",
"horse",
"hyacinth",
"jack",
"kidney",
"lima",
"lupini",
"moth",
"mung",
"navy",
"pink",
"pinto",
"pole",
"rattlesnake",
"red",
"rice",
"runner",
"shell",
"sword",
"tepary",
"tongue of fire",
"urad",
"vanilla",
"velvet",
"wax",
"white",
"winged",
"yardlong",
"yellow",
"chuckwagon",
"cowboy",
"dilly",
"pickled green",
"sprouted",
"baked",
"boiled",
"popped",
"refried",
"shelled",
"steamed",
"stir-fried"
],
"dish": [
"#adjective# bean chilli",
"#adjective# bean chips",
"#adjective# bean pie",
"#adjective# bean salad",
"#adjective# bean casserole",
"#adjective# beans and rice",
"15 #adjective# bean soup",
"Guernsey #adjective# bean jar",
"senate #adjective# bean soup",
"pork and #adjective# beans"
],
"origin": [
"#dish#",
"#adjective# #adjective# bean",
"#adjective# #prefix#bean bean#suffix#"
]
}

107
thirdparty/.tracery/dinner vendored 100644
View File

@ -0,0 +1,107 @@
{
"poultry": [
"chicken", "chicken breast", "chicken thighs", "duck breast", "quail",
"turkey breast"
],
"poultry-adj": [
"grilled", "sautéed", "pan-seared", "roast"
],
"meat-cut": [
"bison meat", "brisket", "elk meat", "ham", "lamb chops", "pork",
"porkchops", "ribs", "steak", "rabbit steak", "sirloin steak",
"strip steak", "veal cutlets"
],
"meat-cut-adj": [
"barbeque", "grilled", "pan-seared", "roast"
],
"mollusc": [
"clams", "mussels"
],
"mollusc-adj": [
"baked", "boiled", "grilled", "steamed", "stir-fried"
],
"potato-adj": [
"baked", "mashed", "scalloped", "smashed"
],
"rice-adj": [
"arborio", "basmati", "brown", "fried", "glutinous", "long-grain",
"jasmine", "steamed", "sticky", "white", "wild"
],
"pasta": [
"angel hair pasta", "linguini", "macaroni", "penne", "spaghetti"
],
"pasta-meat": [
"chicken", "meatballs", "parma ham", "prosciutto"
],
"pasta-sauce": [
"alfredo sauce", "pesto alla calabrese", "pesto alla genovese",
"pesto alla siciliana", "tomato sauce"
],
"pasta-pair": [
"gnocchi with truffle", "gnudi with ricotta", "three-cheese lasagna",
"beef ravioli", "cheese ravioli", "tortellini"
],
"soup-adj": [
"chicken", "chicken ginseng", "chicken noodle", "chickpea",
"cream of broccoli", "cream of mushroom", "cream of tomato", "egg drop",
"fisherman's", "french onion", "hot and sour", "leek", "lentil",
"tom yum", "yellow pea"
],
"soup-side": [
"a #salad# salad", "a side of crusty bread", "some garlic bread",
"a baguette"
],
"soup": [
"#soup-adj# soup", "avgolemono", "butajiru", "clam chowder",
"corn chowder", "gazpacho", "goulash", "haddock chowder", "lobster bisque",
"minestrone", "squash bisque", "potato and corn chowder", "pozole", "soto",
"stracciatella"
],
"stew-adj": [
"beef", "curry beef", "fish", "mixed vegetable", "venison"
],
"salad": [
"caesar", "couscous", "kale", "mango", "potato", "potato and egg"
],
"bento-main": [
"barbeque pork", "poached salmon", "shrimp tempura", "teriyaki beef",
"teriyaki chicken"
],
"bento-side1": [
"coleslaw", "pickled ginger", "pickled radishes", "tsukemono", "umeboshi"
],
"bento-side2": [
"a rolled egg", "làcháng", "gyoza", "shiitake mushrooms", "seaweed"
],
"wrap-adj": [
"bean", "grilled chicken", "shredded pork"
],
"wrap-side": [
"avocados", "grilled peppers", "ground beef", "roasted peppers",
"adobo sauce", "chili sauce", "grilled steak"
],
"wrap": [
"burritos", "pitas", "tacos"
],
"entree-part-meat": [
"#poultry-adj# #poultry#", "#meat-cut-adj# #meat-cut#",
"#mollusc-adj# #mollusc#"
],
"entree-part-veggie": [
"artichokes", "arugula", "asparagus", "baby bok choy", "broccoli",
"brussel sprouts", "baby corn", "cauliflower", "kale", "green peas",
"snowpeas", "spinach"
],
"entree-part-starch": [
"#potato-adj# potatoes", "#rice-adj# rice"
],
"origin": [
"#bento-main# bento with #bento-side1#, #bento-side2# and rice",
"#entree-part-meat# with #entree-part-veggie# and #entree-part-starch#",
"#pasta# with #pasta-meat# and #pasta-sauce#",
"#pasta-pair# and #pasta-sauce#",
"#stew-adj# stew with #rice-adj# rice",
"#soup# with #soup-side#",
"#wrap-adj# wrap with #wrap-side#"
]
}

29
thirdparty/.tracery/hello vendored 100644
View File

@ -0,0 +1,29 @@
{
"origin": [
"hello!",
"hi!",
"ahoy!",
"hai",
"EHLO",
"henlo",
"hallo (Dutch)",
"hola (Spanish)",
"salut! (French)",
"ciao! (Italian)",
"tag! (German)",
"hej (Swedish)",
"aloha! (Hawaiian)",
"saluton (Esperanto)",
"هلا (pron: hala)",
"مااس (Arabic, pron: salaam)",
"สวัสดี (Thai, pron: sawatdee)",
"你好 (Chinese, pron: ni hao)",
"こんにちわ (Japanese, pron: konnichiwa)",
"안녕 (Korean, pron: annyeong)",
"नमस्ते (Hindi, pron: namaste)",
"ᐊᐃ (Ai)",
"Привет (Russian, pron: preevyet)",
"שָׁלוֹם (Hebrew, pron: shalom)",
"kia ora (Māori)"
]
}

181
thirdparty/.tracery/pizza vendored 100644
View File

@ -0,0 +1,181 @@
{
"cheese": [
"asiago",
"bocconcini",
"catupiry",
"cheddar",
"feta",
"fontina",
"goat cheese",
"gorgonzola",
"Monterey Jack",
"mozzarella",
"Oaxaca cheese",
"paneer",
"parmesan",
"provolone",
"ricotta",
"stracchino"
],
"crust": [
"deep-dish",
"extra-thick crust",
"mochi crust",
"Neapolitan",
"Roman",
"square",
"thick crust",
"thin crust"
],
"herb": [
"basil",
"cilantro",
"dill",
"oregano",
"rosemary"
],
"meat": [
"bacon",
"bulgogi beef",
"rendang beef",
"roast beef",
"satay beef",
"achari chicken",
"balado chicken",
"dak galbi",
"grilled chicken",
"rendang chicken",
"rotisserie chicken",
"tandoori chicken",
"teriyaki chicken",
"chicken tikka masala",
"chorizo",
"crocodile",
"emu",
"ham",
"parma ham",
"döner kebab",
"meatballs",
"Peking duck",
"peperoncini",
"pepperoni",
"prosciutto",
"salami",
"sauage",
"German sausage",
"Maltese sausage",
"tripas"
],
"mushroom": [
"cremini mushrooms",
"enoki",
"portobello mushrooms",
"shiitake",
"truffle"
],
"seafood": [
"anchovies",
"canned tuna",
"clams",
"crab meat",
"crawfish",
"eel",
"lobster",
"mussels",
"oysters",
"poke",
"prawns",
"salmon",
"salmon teriyaki",
"scallops",
"scampi",
"scungilli",
"shrimp",
"tom yum and shrimp",
"smoked salmon",
"squid",
"tuna"
],
"vegetable": [
"artichokes",
"arugula",
"asparagus",
"avocado",
"broccoli",
"capers",
"chilis",
"jalapeños",
"serrano chilis",
"corn",
"cucumbers",
"pickled cucumbers",
"kale",
"kimchi",
"green olives",
"onions",
"red onions",
"green peppers",
"red peppers",
"red bell peppers",
"peas",
"potatoes",
"sweet potatoes",
"tomatoes",
"sun-dried tomatoes",
"spinach",
"zucchini"
],
"garnish": [
"ground black pepper",
"minced garlic",
"melted butter",
"curry",
"marinara",
"olive oil",
"extra virgin olive oil",
"balsamic vinegar",
"barbecue sauce",
"béarnaise sauce",
"brown sauce",
"donair sauce",
"honey barbecue sauce",
"sriracha",
"tabasco sauce",
"tomato sauce",
"white sauce"
],
"other": [
"hard-boiled eggs",
"labane",
"nachos",
"poutine",
"bananas",
"pineapples"
],
"topping": [
"#cheese#",
"#herb#",
"#meat#",
"#mushroom#",
"#other#",
"#seafood#",
"#vegetable#"
],
"forecast": [
"#crust#, #topping# and #topping# with a chance of #topping#",
"lots of #topping#, with #topping# all the way to the #crust#, and a greater chance of #topping# and #topping# throughout",
"a meaty mix of #meat#, #meat# and #meat#",
"a toss-up of #vegetable#, #vegetable# and #vegetable#",
"a dynamic system of #cheese# and #cheese# is moving in towards the #crust#, along with more #cheese#",
"some scattered #vegetable#, high on the #mushroom#, and just a touch of #topping# below",
"a pretty #crust# with lots of #mushroom#, #herb# and #garnish#",
"plenty of #topping# and #garnish#, followed by trace amounts of #herb#",
"#topping#, #topping# and a shower of #garnish#",
"#meat# and #vegetable# followed later by #cheese#",
"expect some #garnish# and #herb# over the #topping# and #topping#",
"starts with some #meat#, then moves into #vegetable# and #mushroom# over the #crust#"
],
"origin": [
"Pizza forecast: #forecast#"
]
}

98
thirdparty/.tracery/tot vendored 100644
View File

@ -0,0 +1,98 @@
{
"animal": [
"bat",
"black cat",
"crow",
"earthworm",
"newt",
"owl",
"scorpion",
"slug",
"snake",
"spider",
"worm"
],
"char": [
"frankenstein",
"ghost",
"grim reaper",
"headless horseman",
"jack 'o lantern",
"mummy",
"phantom",
"werewolf",
"witch",
"vampire",
"zombie"
],
"part": [
"arm",
"claw",
"eyeball",
"finger",
"hand",
"paw",
"thumb",
"toe"
],
"trick": [
"an invisible cauldron of ghoulash",
"a bottle of super-sour brain licker",
"a whoopie cushion filled with Paris air",
"a #animal# doodle on wet paper with invisible ink",
"a bag of fried devil's tongues coated in chocolate habaneros",
"a bag of bulls eyes",
"a bowl of instant intestines with flash-frozen #part#s, #part#s and #part#s",
"an inflatable #char# #part#",
"a jack 'o lantern full of #char# goo",
"a pack of used gum, with the faint outline of a #part# print visible"
],
"trick-special": [
"6 bags Barf Sinson's Everyawful Flavour Jellybeans",
"6 boxes of Weirdo Wonka's Newts",
"6 boxes of Weirdo Wonka's neverlasting gobstoppers",
"6 rolls of Lifedestroyer gummies",
"6 drums of Toxic Waste",
"a bag of OH! Horror! mini-bars (666 g)",
"a party-sized box of Dung-king Donuts",
"6 tiny bars of demonically insipid white chocolate",
"a 666 mL can of Chef Boilerdee's #char# heads in pig's blood soup. 5 character heads to collect!",
"a bottle of Eau de Skunk (666 mL)"
],
"treat": [
"a bag of whoppers",
"a pack of vegan chocolate mini-bars",
"a bottle of pancake syrup-flavoured supercharged energy drink",
"a large pack of bobblicious bobblegum",
"a bag of beet and sweet potato crisps",
"a bag of rich butterscotch",
"a bag of salty liquorice",
"a bag of pineapple lumps",
"a bag of chocolate fishes",
"a pack of white rabbit candy",
"a bag of king kong milk candy",
"a bag of #animal#-shaped gummies",
"a can of sweet 'n' sour #animal#",
"a bag of lollipops shaped like #char# heads"
],
"treat-special": [
"7 boxes of Willy Wonka's everlasting gobstoppers",
"7 rolls of Deathsaver gummies",
"7 large bars of premium angelic dark chocolate",
"7 boxes of dunkin' chocolate cookies 'n' creme donuts",
"7 bags of freshly-made dragon's beard",
"a box of handmade chocolate truffles",
"a large bag of artisan peanut butter brittles",
"a large jar of konpeito in rainbow colours",
"a big jar of fruit-flavoured gumballs",
"a humungous pumpkin full of candy corn"
],
"origin": [
"Happy Halloween! Have #trick#!",
"Happy Halloween! Have #treat#!",
"This Hallow's Eve, have #treat# before you leave!",
"All the world's spooky and frightful, here's something more delightful: #treat#",
"Happy Hallawoooooooooo ... A specia- mwuhahahahaha! Here's #trick-special#!",
"Happy Hallosweet! Time for a special treat! Here's #treat-special#!"
]
}

40
thirdparty/add-roles.txt vendored 100644
View File

@ -0,0 +1,40 @@
# 2018-06-19
# For ~von/public_html/user_story/add_roles.txt
major league foosball player
high-flying hula hooper
bottle cap anthropologist
bumper car bouncer
subzero marine adventurer
aeronautic ether diver
electronics embalmer
flamethrowing polka dancer
UFO mechanic
high-glucose jelly architect
steampunk shaman
heirloom tomato pomologist
tcp packet farmer
lolcode archmage
steam-powered rickshaw pilot
asteroid meteorologist
nuclear synergy engineer
rainbow-scented gummy bear
beatbox robot
reliquary reconstructor
yokai doctor
pyrlit bookworm
candy gusher
rangy brid
love3d virus
brush-conducting gimp
supersonic snail racer
tundra trailblazer
graveyard pan piper
prufrock punkroller
cheese tiramisu officiator
swan lake foxtrotter
giant pancake steamroll operator
sine wave spectrum rider
holistic cybrebenedictor
gothic metal bard

10
thirdparty/madlibs/balls.madlib vendored 100644
View File

@ -0,0 +1,10 @@
Down at an English {{place}}, one evening {{person name}} was there
When I heard a {{occupation}} {{verb -ing}} underneath the flair
I've got a lovely bunch of {{noun plural}}
There they are, all {{verb -ing}} in a row
{{adjective}} ones, {{adjective}} ones, some as {{adjective}} as your head
Give 'em a twist, a {{noun}} of the wrist
That's what the {{occupation}} said
---
(Adapted from I've Got A Lovely Bunch of Coconuts - Danny Kaye)

13
thirdparty/madlibs/chest.madlib vendored 100644
View File

@ -0,0 +1,13 @@
Fifteen {{noun plural}} on the {{adjective}} man's chest—
Drink and the {{person}} had done for the rest—
The {{person}} was {{verb -ed}} by the bos'n's pike.
The bos'n {{verb -ed}} with a marlinspike
And Cookey's {{noun}} was marked belike
It had been gripped
By {{noun plural}} ten;
And there they lay,
All {{adjective}} dead {{noun plural -en}},
Like {{noun}} in a boozing-ken—
Yo—ho—ho and a bottle of {{beverage}}!
---
(Adapted from The Dead Man's Chest - Robert Louis Stevenson)

13
thirdparty/madlibs/choka.madlib vendored 100644
View File

@ -0,0 +1,13 @@
What {{noun, 2 syllables}} above,
{{verb, 2 syllables}} earnestly this night?
What {{noun, 2 syllables}} below,
{{verb, 2 syllables}}, in morning languish?
In darkness, {{noun, 2 syllables}}
{{verb, 2 syllables}} the demons at bay
In light, more {{noun, 2 syllables}}
{{verb, 2 syllables}} the angels away
When swayed, the {{noun, 2 syllables}}
{{verb, 2 syllables}} to the children's plight
Whence laid the {{noun, 2 syllables}}
now cannot {{verb, 2 syllables}} nor see
{{verb, 2 syllables}} to dust, the adults wish.

17
thirdparty/madlibs/miles.madlib vendored 100644
View File

@ -0,0 +1,17 @@
My true {{noun}} wuz beautiful,
An' my true {{noun}} wuz gay,
But she's taken a trip on a {{adjective}} ship
{{verb -ed}} out to Botany Bay,
An' though she's far away,
I'll never {{verb}} me own true {{noun}},
Ten thousand miles away!
Oh, it wuz a summer's mornin',
When last I {{verb -ed}} my Meg
She'd a Government {{noun}} around each hand
An' another one round her leg
As the big {{noun}} left the bay,
"Adieu," she sez, "{{verb}} me,
Ten thousand miles away!"
---
(Adapted from Ten Thousand Miles Away)

View File

@ -0,0 +1,16 @@
Little Celery {{verb}} a myrtle,
who was named Tomato and lived in her garden.
Little Celery {{verb}} a basket with turtles,
and visited Tomato often like a {{adjective}} warden.
One day, Little Celery and Tomato went {{verb -ing}},
to watch the neighborhood {{noun}} play.
They stopped at a {{place}} for food amid their wandering
There were plantains and buttered consommé,
and cola that swirled in time to {{adjective}} fizzling,
but they didn't {{verb}} any blueberries, to Tomato's dismay.
"Don't worry!" Celery assured her companion {{adverb -ly}}
They got seeds from the next {{place}} across the fence,
and {{verb}} them in a big plot that bloomed heartily,
Now every year they shared the {{noun}} with all their friends.

10
thirdparty/madlibs/wake.madlib vendored 100644
View File

@ -0,0 +1,10 @@
{{person 1}} lived in {{name}} Street
A {{noun}} Irish, {{adverb}} odd;
They'd a beautiful {{noun}} so rich and sweet
And to {{verb}} in the world they {{verb -ed}} a hod.
Now they had a sort o' the {{adjective}} way
With a love of the {{noun}} poor {{person 1}} was born
And to {{verb}} them on with their work each day
They'd a drop of the {{beverage}} ev'ry morn.
---
(Adapted from Finnegan's Wake)

8
thirdparty/nicethings.txt vendored 100644
View File

@ -0,0 +1,8 @@
You are the peanut butter to the town's jelly. We fit so well together!
You add sweetness and spice to the communal kitchen.
The town cocktails are so much tastier with your heart's fruit punch.
Your jellybeans add colour and flavour to tilde.town life.
Your presence warms us like a hearty bowl of vegetable soup.
This peer, by any other name, would stay as sweet. /ShakesPear/
Your bright energy is as invigorating as the finest morning brew.
Your smile brightens mornings like a sweet, refreshing glass of orange juice.

BIN
thirdparty/our/coffee vendored 100755

Binary file not shown.

40
thirdparty/our/coffee.nim vendored 100644
View File

@ -0,0 +1,40 @@
import random
var
coffee = @[
# Hot coffee
"A café americano is an espresso-based drink made by adding hot water.",
"A café de olla is coffee made in earthen clay pots, flavoured with cinnamon and unrefined cane sugar.",
"A café miel is an espresso with steamed milk, cinnamon and honey.",
"A café Touba is a Senegalese ground coffee made by roasting coffee beans with grains of selim.",
"A caffè lungo is an espresso-based drink made with the liquid brewed rather than adding hot water to the espresso.",
"A caffè macchiato is an espresso marked with a small amount of milk.",
"A caffè Medici is a double shot espresso with chocolate syrup and orange peel, and finished with whipped cream.",
"A cappuccino is an espresso made with steamed milk.",
"A double-double is a drip coffee poured over two creams and two sugars.",
"A doppio is a double shot espresso.",
"An espresso is coffee brewed with a machine that extrudes a small amount of hot water or steam through finely ground coffee.",
"A latte is coffee made with espresso and steamed milk.",
"A manilo is made with espresso and a small amount of milk.",
"A melya is coffee with cocoa powder and honey.",
"A mocha or mocaccino is made with espresso, hot milk, and chocolate syrup, dark or milk chocolate.",
"A pedrocchi is a mint coffee made from espresso, fresh cream, mint syrup and a thin layer of cocoa powder.",
"A pocillo is a shot of unsweetened coffee popular in Latin America.",
"A ristretto is traditionally espresso made with half the amount of water.",
# Iced coffee
"A black tie is made by mixing two shots of espresso with Thai iced tea, which includes black tea, orange blossom water, tamarind, sugar, condensed milk or cream.",
"A frappé is an iced coffee covered with a layer of foam.",
"A guillermo is espresso poured over lime slices, sometimes served with ice or milk.",
"An ice shot is a shot of espresso poured over ice and often with ice cream added on top.",
"A palazzo consists of two shots of chilled espresso mixed with sweet cream.",
"A mazagran is an Algerian cold coffee with ice, and occasionally lemon, rum or sugar.",
"A shakerato is an iced coffee made from espresso shaken with ice cubes.",
# Liqueur coffee
"A caffè corretto is a shot of espresso with a shot of liquor, such as brandy or grappa.",
"An Irish coffee includes Irish whiskey and sugar and topped with cream.",
"A ponce is a shot of espresso with rum and occasionally, lemon zest."
]
randomize()
echo sample(coffee)

3
thirdparty/our/readme.md vendored 100644
View File

@ -0,0 +1,3 @@
# Scripts for the our bot
To rebuild a script: `nim c -d:release [script].nim`

3
thirdparty/readme.md vendored 100644
View File

@ -0,0 +1,3 @@
# Readme
Content for other townies' applications and bots.

17
thirdparty/shrimp.txt vendored 100644
View File

@ -0,0 +1,17 @@
________________
-------- _______---- -----___
_---- __---- __________ --,
- __-----` ````---__\\_
.` / _-' ```------------
.-|` | (@),..```\````
. | \ ___,-`===++' `.
. \_ _`--------`````== =====
. `--_===--' ``=== ==== ========
.--_____---=====--' ===== ======== ====
. .======-' ======== =========----.
--_____-'.====--' ===========----.
`. .===-' == ==__.
`--___-''`--___ _________ _-'
`-_ _,' \_________\_
```-----/`----______/
``--______/

78
twtxt/tw2txt 100755
View File

@ -0,0 +1,78 @@
#!/bin/bash
_name="tw2txt"
_author="mio"
_desc="download twitter user_timeline.json using twurl and convert to twtxt."
_version="0.1 (2017-03-06)"
_license="BSD-3"
twurl_src="/1.1/statuses/user_timeline.json"
input="/home/$(whoami)/twitter.json"
output="$(dirname $input)/tw2txt.txt"
convert() {
if [ -n "$1" ]; then output="$1"; fi
mkdir -p "$(dirname $input)"
twurl "$twurl_src" > $input
# Concat json, remove [] wrapper
# Split at date start and remove date label, split at date end
# Replace text label with placeholder
# Remove unneeded lines, remove extra newlines
# Replace placeholder
tdata=$(cat $input | tr -d "[]" | \
sed "s/{\"created_at\":\"/\n/g" | sed "s/\",\"/\n/g" | \
sed "s/text\":\"/_TWT_/g" | \
sed "s/.*\":\".*//g" | sed "/^$/d" | \
sed "N;s/\n_TWT_/\t/g")
rm -rf $input
# Convert timestamp
local idt
IFS=$old_ifs
IFS=$'\n'
for line in $tdata; do
idt=$(echo $line | cut -f 1)
# Remove any extra newlines from tweet body
# $()\t$() = [timestamp][tab][tweet]
echo -e "$(date -d"$idt" "+%FT%T%:z")\t\
$(echo -e $line | cut -f 2 | tr -d "\n")" >> $output
done
IFS=$old_ifs
# Remove escape backslashes from double quotes and urls
sed -i 's/\\"/\"/g' $output
sed -i "s|\\\/|/|g" $output
}
layout() {
# Convert timestamp
local idt is_date odt
IFS=$old_ifs
IFS=$'\n'
for line in $(cat "$output"); do
idt=$(echo $line | cut -f 1)
# Check if valid date or newline in tweet
# This step should be unneeded after removing \n in convert()
# and is an extra check to avoid date conversion error
is_date=`date -d $idt 2>: 1>:; echo $?`
if [ ! "$is_date" = "1" ]; then
odt=$(date -d"$idt" "+%B %d, %Y %H:%M %Z")
echo -e "$line\n" | sed "s/$idt\t/$odt\n/"
else
echo -e "$line\n"
fi
done
IFS=$old_ifs
}
case "$1" in
output|-o) convert "$2";;
parse|-p) convert "$output"; cat "$output"; rm -rf "$output";;
help|--help) echo -e "$_name — $_desc\n\n\
Options:\n\
output [file]\t\tOutput twtxt to file\n\
parse\t\t\tView timeline in a parse-friendly format\n\
--version\t\tShow the version";;
version|--version) echo -e "$_name $_version";;
*) convert "$output"; layout; rm -rf "$output";;
esac

537
twtxt/txtsh 100755
View File

@ -0,0 +1,537 @@
#!/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