From f93f1ffa84d32b9d0c3c7271fcee190eb9538d3e Mon Sep 17 00:00:00 2001 From: mio Date: Fri, 21 Apr 2023 02:31:52 +0000 Subject: [PATCH] Initial commit --- .gitignore | 3 + dotfiles/muttrc | 75 +++ dotfiles/tmux.conf | 110 +++++ dotfiles/vimrc | 397 +++++++++++++++ ftg/config.sample.yml | 27 ++ ftg/formatter.py | 272 +++++++++++ ftg/hashi.py | 49 ++ ftg/main.py | 225 +++++++++ ftg/readme.md | 9 + readme.md | 10 + saiba80/readme.md | 6 + saiba80/saiba80 | 4 + saiba80/saiba80.py | 22 + saiba80/saiba80_game_titles.py | 31 ++ saiba80/sources/combined.txt | 804 +++++++++++++++++++++++++++++++ saiba80/sources/fashion.txt | 89 ++++ saiba80/sources/films.txt | 214 ++++++++ saiba80/sources/games.txt | 168 +++++++ saiba80/sources/readme.md | 8 + saiba80/sources/toys.txt | 225 +++++++++ saiba80/sources/tv.txt | 108 +++++ thirdparty/.cadastre/home.txt | 13 + thirdparty/.choochoo | 10 + thirdparty/.tracery/8ball | 24 + thirdparty/.tracery/bean | 98 ++++ thirdparty/.tracery/dinner | 107 ++++ thirdparty/.tracery/hello | 29 ++ thirdparty/.tracery/pizza | 181 +++++++ thirdparty/.tracery/tot | 98 ++++ thirdparty/add-roles.txt | 40 ++ thirdparty/madlibs/balls.madlib | 10 + thirdparty/madlibs/chest.madlib | 13 + thirdparty/madlibs/choka.madlib | 13 + thirdparty/madlibs/miles.madlib | 17 + thirdparty/madlibs/plants.madlib | 16 + thirdparty/madlibs/wake.madlib | 10 + thirdparty/nicethings.txt | 8 + thirdparty/our/coffee | Bin 0 -> 101392 bytes thirdparty/our/coffee.nim | 40 ++ thirdparty/our/readme.md | 3 + thirdparty/readme.md | 3 + thirdparty/shrimp.txt | 17 + twtxt/tw2txt | 78 +++ twtxt/txtsh | 537 +++++++++++++++++++++ 44 files changed, 4221 insertions(+) create mode 100644 .gitignore create mode 100644 dotfiles/muttrc create mode 100644 dotfiles/tmux.conf create mode 100644 dotfiles/vimrc create mode 100644 ftg/config.sample.yml create mode 100644 ftg/formatter.py create mode 100644 ftg/hashi.py create mode 100644 ftg/main.py create mode 100644 ftg/readme.md create mode 100644 readme.md create mode 100644 saiba80/readme.md create mode 100755 saiba80/saiba80 create mode 100644 saiba80/saiba80.py create mode 100644 saiba80/saiba80_game_titles.py create mode 100644 saiba80/sources/combined.txt create mode 100644 saiba80/sources/fashion.txt create mode 100644 saiba80/sources/films.txt create mode 100644 saiba80/sources/games.txt create mode 100644 saiba80/sources/readme.md create mode 100644 saiba80/sources/toys.txt create mode 100644 saiba80/sources/tv.txt create mode 100644 thirdparty/.cadastre/home.txt create mode 100644 thirdparty/.choochoo create mode 100644 thirdparty/.tracery/8ball create mode 100644 thirdparty/.tracery/bean create mode 100644 thirdparty/.tracery/dinner create mode 100644 thirdparty/.tracery/hello create mode 100644 thirdparty/.tracery/pizza create mode 100644 thirdparty/.tracery/tot create mode 100644 thirdparty/add-roles.txt create mode 100644 thirdparty/madlibs/balls.madlib create mode 100644 thirdparty/madlibs/chest.madlib create mode 100644 thirdparty/madlibs/choka.madlib create mode 100644 thirdparty/madlibs/miles.madlib create mode 100644 thirdparty/madlibs/plants.madlib create mode 100644 thirdparty/madlibs/wake.madlib create mode 100644 thirdparty/nicethings.txt create mode 100755 thirdparty/our/coffee create mode 100644 thirdparty/our/coffee.nim create mode 100644 thirdparty/our/readme.md create mode 100644 thirdparty/readme.md create mode 100644 thirdparty/shrimp.txt create mode 100755 twtxt/tw2txt create mode 100755 twtxt/txtsh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2425c5b --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +__pycache__ +*.swp +ftg/config.yml diff --git a/dotfiles/muttrc b/dotfiles/muttrc new file mode 100644 index 0000000..7556651 --- /dev/null +++ b/dotfiles/muttrc @@ -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 \ + "~NN." \ + "mark all read" + +# Go to folders +macro index,pager gd ~/Mail/drafts "drafts" +macro index,pager gs ~/Mail/sent "sent" +macro index,pager gt ~/Mail/trash "trash" + +# Save messages to folders +bind index,pager f noop +macro index fl "~/Mail/list" +macro index ft "~/Mail/town" + + +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 next-line # Scroll down in message +bind pager previous-line # Scroll up in message +# For threaded view oldest to newest. Reverse if sorting by newest first. +bind pager previous-undeleted # Previous message +bind pager 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 diff --git a/dotfiles/tmux.conf b/dotfiles/tmux.conf new file mode 100644 index 0000000..6e58fbb --- /dev/null +++ b/dotfiles/tmux.conf @@ -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" diff --git a/dotfiles/vimrc b/dotfiles/vimrc new file mode 100644 index 0000000..b9a8a20 --- /dev/null +++ b/dotfiles/vimrc @@ -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 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\+\%#\@ 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 + +" Remap ctrl+d to toggle between shell and vim +nmap :sh + +" Disable ctrl+z to avoid accidentally stopping vim +nmap + +" Move page down/up +nmap +nmap + +" Buffer navigation — go to N, prev, next, left, right +nmap bg :ls:b +nmap bh :bprev +nmap bl :bnext +nmap [ h +nmap ] l + +" Buffer width resize — decrease, increase +nmap b- :vertical res -5 +nmap b= :vertical res +5 + +" Buffer loading — close, new (vsplit), reload, new (same split) +nmap bc :bw +nmap bn :enew +nmap br :e +nmap bv v:enew + +" Backup file in current buffer +nmap bk :call BackupFile() + +" Map delete to a black hole register (separate from cut/paste register) +map d "_d + +" Map expression register (used to evaluate expressions) +imap ee = + +" Code folding — collapse all, expand all, toggle current fold +nmap fc zM +nmap fe zR +nmap ft za + +" Git commands +nmap ga :!git add . +nmap gb :!git branch -b +nmap gc :!git commit -m +nmap gca :!git commit +nmap gco :!git checkout +nmap gd :!git diff +nmap gf :!git fetch +nmap gg :!git grep +nmap gl :!git log +nmap gm :!git merge +nmap gph :!git push +nmap gpl :!git pull +nmap gs :!git status + +" New markdown note +nmap md :call AddNewFile('$HOME/', '', 'md') + +" Toggle netrw browser +nmap nt :call ToggleNetrw() + +" Map OmniComplete +imap o + +" Insert paste into file from cat input +" https://stackoverflow.com/a/2545242 +nmap pp :r! cat + +" Search for selection, prompt for replacement, replace all in file +" https://stackoverflow.com/a/31172452 +vnoremap sa "0y:%s/0//g +" Prompt for search/replace text, replace all in selection +vnoremap sr :s///g + +" Toggle spell check +nmap sc :setl spell! + +" Search in current directory +nmap sd :!grep -R + +" Toggle search term highlighting +nmap sh :set nohlsearch! + +" Sessions — load (waits for file input), save +nmap sl :source $HOME/.vim/sessions/ +nmap ss :mksession! $HOME/.vim/sessions/ + +" Tab navigation — close, prev, next, new +" To go directly to tab n: [n]gt +nmap tc :tabc +nmap th :tabp +nmap tl :tabn +nmap tn :tabe + +" Edit/refresh to apply vimrc changes +nmap ve :tabe $MYVIMRC +nmap vr :source $MYVIMRC + +" Show word count +" https://unix.stackexchange.com/a/145293 +nmap wc g +vmap wc :s/\S\+//gn + +" Trim leading whitespace +" https://unix.stackexchange.com/a/29619 +" To reset cursor at first selected line: vmap wsl :%le +vmap wl :normal 0dw + +" Trim trailing whitespace +" http://oualline.com/vim-cook.html#trim +" https://vim.fandom.com/wiki/Remove_unwanted_spaces +nmap wst :1,$s/[ ]*$// +vmap wst :s/\s\+$// + + +" 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 . gh + " Files — copy, delete, move, rename, select + nmap fc mc + nmap fd D + nmap fm mm + nmap fr R + nmap v mf + " Go back in history + nmap h u + " Close file preview buffer + nmap P 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 ' . a:prefix . c . ' viwa' . + \ g:WrapWordChars[c] . 'bi' . c . 'ea' + exe 'vnoremap ' . a:prefix . c . ' xi' . c . + \ g:WrapWordChars[c] . 'P' + endfor + " Restore user settings + if exists('b:autopairs_enabled') + let b:autopairs_enabled = l:isapenabled + endif +endfun +call g:WrapWord('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 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 em (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 ld :call ale#cursor#ShowCursorDetail() +nmap li :ALELint +nmap lj (ale_next_wrap) +nmap lk (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 = '' +let g:AutoPairsShortcutJump = 'aj' +let g:AutoPairsShortcutToggle = 'ap' + +" commentary.vim — code commenting +imap / :Commentary +vmap / :Commentary + +" snippet.vim — code/template expansion +" Key mappings in snipmate.vim/after/plugin/snipMate.vim +let g:snippets_dir = '~/.vim/snippets' +imap . =TriggerSnippet() diff --git a/ftg/config.sample.yml b/ftg/config.sample.yml new file mode 100644 index 0000000..e95c2d3 --- /dev/null +++ b/ftg/config.sample.yml @@ -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 diff --git a/ftg/formatter.py b/ftg/formatter.py new file mode 100644 index 0000000..3c87fa7 --- /dev/null +++ b/ftg/formatter.py @@ -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 + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + # Remove non-functional empty links after stripping classes/ids + "", + "": "", + # Remove the tags themselves but not the inner html + "", "", + "", "", + "", "", + "", "", + "", "", + "", "", + "", "", + "", "", + "", "", + "", "", + # Remove currently unsupported tags + "", "", + "", "", + "", "", + # "", "", + # "", "", + # "", "", + } + + strip_ws = ["\n\n", "\t", " "] + + format_tags1 = { + ">\n<": "><", + "
": "\n>", + } + + format_tags2 = { + "
": "[[address]]\n", "
": "\n", + "": "*", "": "*", + "": "*", "": "*", + "

": "\n# ", "

": "\n", + "

": "\n## ", "

": "\n", + "

": "\n### ", "

": "\n", + "

": "\n#### ", "

": "\n", + "
": "\n##### ", "
": "\n", + "
": "\n###### ", "
": "\n", + "
": "\n---\n", "
": "\n---\n", "
": "\n---\n", + "
": "\n", "
": "\n", "
": "\n", + "
": "\n>", "
": "\n", + "": "**", "": "**", + "": "`", "": "`", + "": "~~", "": "~~", + "": "**", "": "**", + "
": "\n", "
": "", + "
": "", "
": ": ", + "
": "", "
": "\n", + "
": "*", "
": "*", + "
": "Fig. ", "
": "", + "": "***", "": "***", + "

": "\n", "

": "\n", + "
": "\n```\n", "
": "\n```\n", + "": "«", "": "»", + "": "", "": "", + "": " (", "": ")", + "": "[[~~", "": "~~]]", + "": "**", "": "**", + "": "⏝", "": "⏝", + "": "^", "": "^", + "": "**", "": "**", + "": "__", "": "__", + " ": "\n\n", + "'": "'", + "’": "'", "’": "'", + "“": "\"", "”": "\"", + "–": "—", + "©": "©", + } + + 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("", 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("", 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("") + elif " href='" in l: + attrs["href"] = l.split(" href='")[1].split("'")[0] + attrs["title"] = l.split(">")[1].strip("") + elif " href=" in l: + attrs["href"] = l.split(" href=")[1].split(" ")[0] + attrs["title"] = l.split(">")[1].strip("") + 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("", 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. `HTML` -> `HTML [[abbr: Hypertext Markup Language]]`""" + txt = html + abbrs = re.findall("", 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("") + elif " title='" in a: + attrs["title"] = a.split(" title='")[1].split("'")[0] + attrs["abbr"] = a.split(">")[1].strip("") + elif " title=" in a: + attrs["title"] = a.split(" title=")[1].split(" ")[0] + attrs["abbr"] = a.split(">")[1].strip("") + 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. `` -> + `Today (1970-01-01)`.""" + txt = html + timestamps = re.findall("", txt) + attrs = {"title": "", "datetime": ""} + for t in timestamps: + attrs["title"] = t.split(">")[1].strip("") + 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("
    .*?
", txt, flags=re.DOTALL) + for o in ol: + li = o.replace("", "").replace("", "").split("
  • ") + 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 ("
      " in txt) or ("
    • " in txt): + txt = txt.replace("
    • ","") + txt = txt.replace("
        ", "\n").replace("
      ", "") + txt = txt.replace("
    • ", "- ").replace("
    • ", "\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 diff --git a/ftg/hashi.py b/ftg/hashi.py new file mode 100644 index 0000000..79c21e0 --- /dev/null +++ b/ftg/hashi.py @@ -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 diff --git a/ftg/main.py b/ftg/main.py new file mode 100644 index 0000000..9c28c99 --- /dev/null +++ b/ftg/main.py @@ -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() diff --git a/ftg/readme.md b/ftg/readme.md new file mode 100644 index 0000000..19a98c4 --- /dev/null +++ b/ftg/readme.md @@ -0,0 +1,9 @@ +# feed the gopher + +A RSS feed service to browse headlines from gopher. + + +## Requirements + +- python 3 +- modules: feedparser pyyaml urllib3 diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..28a05e8 --- /dev/null +++ b/readme.md @@ -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. diff --git a/saiba80/readme.md b/saiba80/readme.md new file mode 100644 index 0000000..d3c71e0 --- /dev/null +++ b/saiba80/readme.md @@ -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` diff --git a/saiba80/saiba80 b/saiba80/saiba80 new file mode 100755 index 0000000..0129cd1 --- /dev/null +++ b/saiba80/saiba80 @@ -0,0 +1,4 @@ +#!/bin/sh + +cd ~mio/bin/saiba80 +python3 ./saiba80.py diff --git a/saiba80/saiba80.py b/saiba80/saiba80.py new file mode 100644 index 0000000..0b06067 --- /dev/null +++ b/saiba80/saiba80.py @@ -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) diff --git a/saiba80/saiba80_game_titles.py b/saiba80/saiba80_game_titles.py new file mode 100644 index 0000000..0fb5897 --- /dev/null +++ b/saiba80/saiba80_game_titles.py @@ -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(":", "")) diff --git a/saiba80/sources/combined.txt b/saiba80/sources/combined.txt new file mode 100644 index 0000000..1edf863 --- /dev/null +++ b/saiba80/sources/combined.txt @@ -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 + diff --git a/saiba80/sources/fashion.txt b/saiba80/sources/fashion.txt new file mode 100644 index 0000000..0cefb29 --- /dev/null +++ b/saiba80/sources/fashion.txt @@ -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 + diff --git a/saiba80/sources/films.txt b/saiba80/sources/films.txt new file mode 100644 index 0000000..1d926ce --- /dev/null +++ b/saiba80/sources/films.txt @@ -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 diff --git a/saiba80/sources/games.txt b/saiba80/sources/games.txt new file mode 100644 index 0000000..2f439dc --- /dev/null +++ b/saiba80/sources/games.txt @@ -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!! diff --git a/saiba80/sources/readme.md b/saiba80/sources/readme.md new file mode 100644 index 0000000..b4a005d --- /dev/null +++ b/saiba80/sources/readme.md @@ -0,0 +1,8 @@ +## Sources + +- +- +- +- +- +- diff --git a/saiba80/sources/toys.txt b/saiba80/sources/toys.txt new file mode 100644 index 0000000..b270bfb --- /dev/null +++ b/saiba80/sources/toys.txt @@ -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 diff --git a/saiba80/sources/tv.txt b/saiba80/sources/tv.txt new file mode 100644 index 0000000..c051baa --- /dev/null +++ b/saiba80/sources/tv.txt @@ -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 diff --git a/thirdparty/.cadastre/home.txt b/thirdparty/.cadastre/home.txt new file mode 100644 index 0000000..83eaea0 --- /dev/null +++ b/thirdparty/.cadastre/home.txt @@ -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] diff --git a/thirdparty/.choochoo b/thirdparty/.choochoo new file mode 100644 index 0000000..90c4db2 --- /dev/null +++ b/thirdparty/.choochoo @@ -0,0 +1,10 @@ + +_____.=-[]-===-[]-===-[]-=._____ +^\.__o__,,_____,,_____,,__o__./^ + " -uu- -uu- -uu- " + 0 *!* . 8 @ + ~|^ !/* ^.%. |8 |* + _ .,|. *\!. _V.. ;|, .^\_ + |=-----------mmmm-----------=| +==- /==\-/==\-mmmm-/==\-/==\ -== + \__/ \__/ \__/ \__/ diff --git a/thirdparty/.tracery/8ball b/thirdparty/.tracery/8ball new file mode 100644 index 0000000..61e359e --- /dev/null +++ b/thirdparty/.tracery/8ball @@ -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" + ] +} diff --git a/thirdparty/.tracery/bean b/thirdparty/.tracery/bean new file mode 100644 index 0000000..10776f2 --- /dev/null +++ b/thirdparty/.tracery/bean @@ -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#" + ] +} diff --git a/thirdparty/.tracery/dinner b/thirdparty/.tracery/dinner new file mode 100644 index 0000000..dc37114 --- /dev/null +++ b/thirdparty/.tracery/dinner @@ -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#" + ] +} diff --git a/thirdparty/.tracery/hello b/thirdparty/.tracery/hello new file mode 100644 index 0000000..0d3ca19 --- /dev/null +++ b/thirdparty/.tracery/hello @@ -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)" + ] +} diff --git a/thirdparty/.tracery/pizza b/thirdparty/.tracery/pizza new file mode 100644 index 0000000..80683fc --- /dev/null +++ b/thirdparty/.tracery/pizza @@ -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#" + ] +} diff --git a/thirdparty/.tracery/tot b/thirdparty/.tracery/tot new file mode 100644 index 0000000..6f17e59 --- /dev/null +++ b/thirdparty/.tracery/tot @@ -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#!" + ] +} diff --git a/thirdparty/add-roles.txt b/thirdparty/add-roles.txt new file mode 100644 index 0000000..993c280 --- /dev/null +++ b/thirdparty/add-roles.txt @@ -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 diff --git a/thirdparty/madlibs/balls.madlib b/thirdparty/madlibs/balls.madlib new file mode 100644 index 0000000..10722e8 --- /dev/null +++ b/thirdparty/madlibs/balls.madlib @@ -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) diff --git a/thirdparty/madlibs/chest.madlib b/thirdparty/madlibs/chest.madlib new file mode 100644 index 0000000..4d46149 --- /dev/null +++ b/thirdparty/madlibs/chest.madlib @@ -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) diff --git a/thirdparty/madlibs/choka.madlib b/thirdparty/madlibs/choka.madlib new file mode 100644 index 0000000..8d1c131 --- /dev/null +++ b/thirdparty/madlibs/choka.madlib @@ -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. diff --git a/thirdparty/madlibs/miles.madlib b/thirdparty/madlibs/miles.madlib new file mode 100644 index 0000000..ef3d1e6 --- /dev/null +++ b/thirdparty/madlibs/miles.madlib @@ -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) diff --git a/thirdparty/madlibs/plants.madlib b/thirdparty/madlibs/plants.madlib new file mode 100644 index 0000000..aabde85 --- /dev/null +++ b/thirdparty/madlibs/plants.madlib @@ -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. diff --git a/thirdparty/madlibs/wake.madlib b/thirdparty/madlibs/wake.madlib new file mode 100644 index 0000000..29a1739 --- /dev/null +++ b/thirdparty/madlibs/wake.madlib @@ -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) diff --git a/thirdparty/nicethings.txt b/thirdparty/nicethings.txt new file mode 100644 index 0000000..40f6c94 --- /dev/null +++ b/thirdparty/nicethings.txt @@ -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. diff --git a/thirdparty/our/coffee b/thirdparty/our/coffee new file mode 100755 index 0000000000000000000000000000000000000000..c06373c38b1f192d9d3ec090d0a548ecdc90c121 GIT binary patch literal 101392 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

      tWNf7-YQdINo}=N^(wLHh+4hF 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 $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 diff --git a/twtxt/txtsh b/twtxt/txtsh new file mode 100755 index 0000000..47bad37 --- /dev/null +++ b/twtxt/txtsh @@ -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