diff --git a/LICENSE b/LICENSE
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright Vanessa McHale (c) 2017
+Copyright Vanessa McHale (c) 2017, 2020
 
 All rights reserved.
 
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -1,6 +1,8 @@
 # Command Line Interface Tweeter
 
+[![Windows build status](https://ci.appveyor.com/api/projects/status/github/vmchale/command-line-tweeter?svg=true)](https://ci.appveyor.com/project/vmchale/command-line-tweeter)
 [![Build Status](https://travis-ci.org/vmchale/command-line-tweeter.svg?branch=master)](https://travis-ci.org/vmchale/command-line-tweeter)
+[![Hackage](https://img.shields.io/hackage/v/tweet-hs.svg)](http://hackage.haskell.org/package/tweet-hs)
 
 ![Displaying a user timeline in a terminal.](https://raw.githubusercontent.com/vmchale/command-line-tweeter/master/screenshot.png)
 
@@ -8,7 +10,7 @@
 its [rust counterpart](https://github.com/vmchale/clit-rs) and it's a bit
 slower. 
 
-Reasons to use tweeth-s:
+Reasons to use tweeth-hs:
   - Faster than other tools ([t](https://github.com/sferik/t),
   [oysttyer](https://github.com/oysttyer/oysttyer))
   - Support for colored output. 
@@ -37,13 +39,14 @@
 ## Config
 Generate a token to authorize access to your twitter account by following the guide [here](https://dev.twitter.com/oauth/overview/application-owner-access-tokens)
 
-Then place your API keys and OAuth tokens in a file `~/.cred`, separated by a line break:
+Then place your API keys and OAuth tokens in a file `~/.cred.toml`, as in the
+following example:
 
 ```
-api-key: API_KEY_HERE
-api-sec: API_SECRET_HERE
-tok: OAUTH_TOKEN_HERE
-tok-sec: TOKEN_SECRET_HERE
+api-key = "API_KEY_HERE"
+api-sec = "API_SECRET_HERE"
+tok = "OAUTH_TOKEN_HERE"
+tok-sec = "TOKEN_SECRET_HERE"
 ```
 
 ## Installation
@@ -76,13 +79,21 @@
 ```
 
 ### Sending tweets
+
+To send a tweet:
+
+```
+tweet send "This is my tweet"
+```
+
+#### Input from stdin
 To tweet from stderr, run a command that pipes stderr to stdin, i.e.
 
 ```
-YOUR_BUILD_COMMAND 2>&1 >/dev/null | tweet input
+stack build &>/dev/null | tweet input
 ```
 
-The `tweet` executable reads from stdIn only, but you can view the options (replies, number of tweets to thread, etc.) with
+The `tweet` executable reads from stdin only, but you can view the options (replies, number of tweets to thread, etc.) with
 
 ```
 tweet --help
@@ -105,6 +116,14 @@
 ```
 
 to view your own timeline.
+
+### GHCi integration
+
+You can define the following in your `~/.ghci`
+
+```haskell
+:def tweet (\str -> pure $ ":! tweet send \"" ++ str ++ "\"")
+```
 
 ### Completions
 
diff --git a/Setup.hs b/Setup.hs
deleted file mode 100644
--- a/Setup.hs
+++ /dev/null
@@ -1,2 +0,0 @@
-import Distribution.Simple
-main = defaultMain
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -1,6 +1,343 @@
 module Main where
 
-import Web.Tweet.Exec
+import qualified Data.ByteString.Lazy.Char8 as BSL
+import           Data.Foldable              (traverse_)
+import           Data.Maybe
+import           Data.Version
+import           Options.Applicative
+import           Paths_tweet_hs
+import           System.Directory
+import           Web.Tweet
 
+-- | Data type for our program: one optional path to a credential file, (optionally) the number of tweetInputs to make, the id of the status you're replying to, and a list of users you wish to mention.
+data Program = Program { subcommand :: Command , cred :: Maybe FilePath , color :: Bool }
+
+-- | Data type for a command
+-- TODO add boolean option to show ids alongside tweets
+data Command = Timeline { count :: Maybe Int }
+    | SendInput { tweetInputs :: Maybe Int, replyId :: Maybe String, replyh :: Maybe [String] }
+    | Profile { count :: Maybe Int , screenName'' :: Maybe String, withReplies :: Bool, withRetweets :: Bool }
+    | Mentions { count :: Maybe Int }
+    | Markov { screenName' :: String }
+    | Send { tweets :: Maybe Int , replyId :: Maybe String , replyh :: Maybe [String] , userInput :: String }
+    | Sort { screenName' :: String , count :: Maybe Int , includeReplies :: Bool }
+    | Delete { twId :: Integer }
+    | Fav { twId :: Integer }
+    | Unfav { twId :: Integer }
+    | Retweet { twId :: Integer }
+    | Unretweet { twId :: Integer }
+    | List { count :: Maybe Int , screenName' :: String }
+    | Follow { screenName' :: String }
+    | Unfollow { screenName' :: String }
+    | Block { screenName' :: String }
+    | Unblock { screenName' :: String }
+    | Mute { screenName' :: String }
+    | Unmute { screenName' :: String }
+    | Dump { screenName' :: String }
+    | Replies { screenName' :: String, twId :: Integer }
+    | MuteReplies { screenName' :: String, twId :: Integer }
+    | MuteMentions { screenName' :: String }
+
+-- | query twitter to post stdin with no fancy options
+fromStdIn :: Int -> FilePath -> IO ()
+fromStdIn = threadStdIn [] Nothing
+
+-- | Tweet string given to us, as parsed from the command line
+fromCLI :: String -> Int -> FilePath -> IO ()
+fromCLI s = thread s [] Nothing
+
+-- | Threaded tweetInputs from stdIn
+threadStdIn :: [String] -> Maybe Int -> Int -> FilePath -> IO ()
+threadStdIn hs idNum num filepath = do
+    contents <- getContents
+    thread contents hs idNum num filepath
+
+-- | Executes parser
 main :: IO ()
-main = exec
+main = execParser opts >>= select
+    where
+        versionInfo = infoOption ("tweet-hs version: " ++ showVersion version) (short 'v' <> long "version" <> help "Show version")
+        opts        = info (helper <*> versionInfo <*> program)
+            (fullDesc
+            <> progDesc "Tweet and view tweets"
+            <> header "tweet - a Command Line Interface Tweeter")
+
+-- | Executes program given parsed `Program`
+select :: Program -> IO ()
+select (Program com maybeFile c) = case maybeFile of
+    (Just file) -> selectCommand com (not c) file
+    _ -> selectCommand com (not c) =<< (++ "/.cred.toml") <$> getHomeDirectory
+
+-- | Executes subcommand given subcommand + filepath to configuration file
+selectCommand :: Command -> Bool -> FilePath -> IO ()
+selectCommand (Send maybeNum Nothing Nothing input) _ file = fromCLI input (fromMaybe 15 maybeNum) file
+selectCommand (Send maybeNum (Just rId) Nothing input) _ file = thread input [] (pure . read $ rId) (fromMaybe 15 maybeNum) file
+selectCommand (Send maybeNum Nothing (Just h) input) _ file = thread input h Nothing (fromMaybe 15 maybeNum) file
+selectCommand (Send maybeNum (Just rId) (Just h) input) _ file = thread input h (pure . read $ rId) (fromMaybe 15 maybeNum) file
+selectCommand (SendInput maybeNum Nothing Nothing) _ file  = fromStdIn (fromMaybe 15 maybeNum) file
+selectCommand (SendInput maybeNum (Just rId) (Just h)) _ file = threadStdIn h (pure . read $ rId) (fromMaybe 15 maybeNum) file
+selectCommand (SendInput maybeNum (Just rId) Nothing) _ file = threadStdIn [] (pure . read $ rId) (fromMaybe 15 maybeNum) file
+selectCommand (SendInput maybeNum Nothing (Just h)) _ file = threadStdIn h Nothing (fromMaybe 15 maybeNum) file
+selectCommand (Replies uname twid) c file = putStrLn =<< showReplies uname (fromIntegral twid) c file
+selectCommand (MuteReplies uname twid) _ file = do
+    muted <- muteRepliers uname (fromIntegral twid) file
+    putStrLn "Muted:"
+    traverse_ putStrLn muted
+selectCommand (MuteMentions uname) _ file = muteMentions uname file
+selectCommand (Timeline maybeNum) c file = putStrLn =<< showTimeline (fromMaybe 11 maybeNum) c file
+selectCommand (Mentions maybeNum) c file = putStrLn =<< showTweets c <$> mentions (fromMaybe 11 maybeNum) file
+selectCommand (Profile maybeNum n False False) c file = putStrLn =<< showProfile (fromMaybe mempty n) (fromMaybe 11 maybeNum) c file
+selectCommand (Profile maybeNum n True False) c file = putStrLn =<< showFilteredTL [filterReplies] (fromMaybe mempty n) (fromMaybe 11 maybeNum) c file
+selectCommand (Profile maybeNum n False True) c file = putStrLn =<< showFilteredTL [filterRTs] (fromMaybe mempty n) (fromMaybe 11 maybeNum) c file
+selectCommand (Profile maybeNum n True True) c file = putStrLn =<< showFilteredTL [filterReplies, filterRTs] (fromMaybe mempty n) (fromMaybe 11 maybeNum) c file
+selectCommand (Sort n maybeNum False) c file = putStrLn =<< showBest' n (fromMaybe 11 maybeNum) c file
+selectCommand (Sort n maybeNum True) c file = putStrLn =<< showBest n (fromMaybe 11 maybeNum) c file
+selectCommand (List maybeNum n) c file = putStrLn =<< showFavorites (fromMaybe 11 maybeNum) n c file
+selectCommand (Markov n) _ file = do
+    raw <- getMarkov n Nothing file
+    appendFile (n ++ ".txt") (unlines raw)
+    putStrLn $ "Written output to: " ++ n ++ ".txt"
+selectCommand (Delete n) c file = do
+    putStrLn "Deleted:\n"
+    putStrLn =<< showTweets c <$> deleteTweetResponse n file
+selectCommand (Fav n) c file = do
+    putStrLn "Favorited:\n"
+    putStrLn =<< showTweets c <$> favoriteTweetResponse n file
+selectCommand (Unfav n) c file = do
+    putStrLn "Unfavorited:\n"
+    putStrLn =<< showTweets c <$> unfavoriteTweetResponse n file
+selectCommand (Retweet n) c file = do
+    putStrLn "Retweeted:\n"
+    putStrLn =<< showTweets c <$> retweetTweetResponse n file
+selectCommand (Unretweet n) c file = do
+    putStrLn "Unretweeted:\n"
+    putStrLn =<< showTweets c <$> unretweetTweetResponse n file
+selectCommand (Follow sn) _ file = do
+    follow sn file
+    putStrLn ("..." ++ sn ++ " followed successfully!")
+selectCommand (Unfollow sn) _ file = do
+    unfollow sn file
+    putStrLn ("..." ++ sn ++ " unfollowed successfully!")
+selectCommand (Block sn) _ file = do
+    block sn file
+    putStrLn ("..." ++ sn ++ " blocked successfully")
+selectCommand (Unblock sn) _ file = do
+    unblock sn file
+    putStrLn ("..." ++ sn ++ " unblocked successfully")
+selectCommand (Mute sn) _ file = do
+    mute sn file
+    putStrLn ("..." ++ sn ++ " muted successfully")
+selectCommand (Unmute sn) _ file = do
+    unmute sn file
+    putStrLn ("..." ++ sn ++ " unmuted successfully")
+selectCommand (Dump sn) _ file = BSL.putStrLn =<< getProfileRaw sn 3200 file Nothing
+
+-- | Parser to return a program datatype
+program :: Parser Program
+program = Program
+    <$> hsubparser
+        (command "send" (info tweet (progDesc "Send a tweet from the command-line"))
+        <> command "input" (info tweetInput (progDesc "Send a tweet from stdIn"))
+        <> command "view" (info timeline (progDesc "Get your timeline"))
+        <> command "user" (info profile (progDesc "Get a user's profile"))
+        <> command "markov" (info markov (progDesc "Grab tweets en masse."))
+        <> command "hits" (info best (progDesc "View a user's top tweets."))
+        <> command "del" (info delete (progDesc "Delete a tweet.")) -- TODO delete/favorite in bunches!
+        <> command "fav" (info fav (progDesc "Favorite a tweet"))
+        <> command "ufav" (info unfav (progDesc "Unfavorite a tweet"))
+        <> command "urt" (info unrt (progDesc "Un-retweet a tweet"))
+        <> command "rt" (info rt (progDesc "Retweet a tweet"))
+        <> command "follow" (info fol (progDesc "Follow a user"))
+        <> command "unfollow" (info unfol (progDesc "Unfollow a user"))
+        <> command "replies" (info repliesParser (progDesc "Show replies to a tweet"))
+        <> command "list" (info list (progDesc "List a user's favorites"))
+        <> command "dump" (info dump (progDesc "Dump tweets (for debugging)"))
+        <> command "block" (info blockParser (progDesc "Block a user"))
+        <> command "unblock" (info unblockParser (progDesc "Unblock a user"))
+        <> command "mute" (info muteParser (progDesc "Mute a user"))
+        <> command "unmute" (info unmuteParser (progDesc "Unmute a user"))
+        <> command "mute-replies" (info muteRepliesParser (progDesc "Mute everyone who replied to a particular tweet"))
+        <> command "mute-mentions" (info muteMentionsParser (progDesc "Mute everyone who mentioned a particular user"))
+        <> command "mentions" (info mentionsParser (progDesc "Fetch mentions")))
+    <*> optional (strOption
+        (long "cred"
+        <> short 'c'
+        <> metavar "CREDENTIALS"
+        <> completer (bashCompleter "file -X '!*.toml' -o plusdirs")
+        <> help "path to credentials"))
+    <*> switch
+        (long "color"
+        <> short 'l'
+        <> help "Turn off colorized terminal output.")
+
+-- | Parser for the view subcommand
+timeline :: Parser Command
+timeline = Timeline
+    <$> optional (read <$> strOption
+        (long "count"
+        <> short 'n'
+        <> metavar "NUM"
+        <> help "number of tweetInputs to fetch, default 5"))
+
+-- | Parser for the markov subcommand
+markov :: Parser Command
+markov = Markov <$> user
+
+-- | Parser for the follow subcommand
+fol :: Parser Command
+fol = Follow <$> user
+
+-- | Parser for the block subcommand
+blockParser :: Parser Command
+blockParser = Block <$> user
+
+-- | Parser for the unblock subcommand
+unblockParser :: Parser Command
+unblockParser = Unblock <$> user
+
+-- | Parser for the dump subcommand
+dump :: Parser Command
+dump = Dump <$> user
+
+-- | Parser for the unfollow subcommand
+unfol :: Parser Command
+unfol = Unfollow <$> user
+
+-- | Parser for the list subcommand
+list :: Parser Command
+list = List
+    <$> optional (read <$> strOption
+        (long "count"
+        <> short 'n'
+        <> metavar "NUM"
+        <> help "Number of tweetInputs to fetch, default 12"))
+    <*> user
+
+-- | Parser for the unfollow subcommand
+muteParser :: Parser Command
+muteParser = Mute <$> user
+
+-- | Parser for the unfollow subcommand
+unmuteParser :: Parser Command
+unmuteParser = Unmute <$> user
+
+-- | Parse a user screen name
+user :: Parser String
+user = argument str
+    (metavar "SCREEN_NAME"
+    <> help "Screen name of user.")
+
+-- | Parser for the del subcommand
+delete :: Parser Command
+delete = Delete <$> getInt
+
+-- | Parser for the fav subcommand
+fav :: Parser Command
+fav = Fav <$> getInt
+
+-- | Parser for the fav subcommand
+unfav :: Parser Command
+unfav = Unfav <$> getInt
+
+-- | Parser for the fav subcommand
+unrt :: Parser Command
+unrt = Unretweet <$> getInt
+
+-- | Parser for the fav subcommand
+rt :: Parser Command
+rt = Retweet <$> getInt
+
+-- | Parser for the del subcommand
+getInt :: Parser Integer
+getInt = read <$> argument str
+    (metavar "TWEET_ID"
+    <> help "ID of tweet")
+
+repliesParser :: Parser Command
+repliesParser = Replies <$> user <*> getInt
+
+muteRepliesParser :: Parser Command
+muteRepliesParser = MuteReplies <$> user <*> getInt
+
+muteMentionsParser :: Parser Command
+muteMentionsParser = MuteMentions <$> user
+
+-- | Parser for the user subcommand
+profile :: Parser Command
+profile = Profile
+    <$> optional (read <$> strOption
+        (long "count"
+        <> short 'n'
+        <> metavar "NUM"
+        <> help "Number of tweetInputs to fetch, default 12"))
+    <*> optional user
+    <*> switch (
+           long "no-replies"
+        <> short 'r'
+        <> help "Don't display replies.")
+    <*> switch (
+           long "no-retweets"
+        <> short 't'
+        <> help "Don't display retweets.")
+
+-- | Parser for the mention subcommand
+mentionsParser :: Parser Command
+mentionsParser = Mentions
+    <$> optional (read <$> strOption
+        (long "count"
+        <> short 'n'
+        <> metavar "NUM"
+        <> help "Number of tweetInputs to fetch, default 12"))
+
+-- | Parse best tweets
+best :: Parser Command
+best = Sort
+    <$> argument str
+        (metavar "SCREEN_NAME"
+        <> help "Screen name of user you want to view.")
+    <*> optional (read <$> strOption
+        (long "count"
+        <> short 'n'
+        <> metavar "NUM"
+        <> help "Number of tweetInputs to fetch, default 12"))
+    <*> switch
+        (long "replies"
+        <> short 'r'
+        <> help "Include replies in your all-time hits")
+
+-- | Parser for the send subcommand
+tweet :: Parser Command
+tweet = Send
+    <$> optional (read <$> strOption
+        (long "tweets"
+        <> short 't'
+        <> metavar "NUM"
+        <> help "Number of tweetInputs in a row, default 15"))
+    <*> optional (strOption
+        (long "reply"
+        <> short 'r'
+        <> help "id of status to reply to - be sure to include their handle, e.g. @my_build_errors"))
+    <*> optional (some $ strOption
+        (long "handle"
+        <> short 'h'
+        <> metavar "HANDLE1"
+        <> help "h to include in replies"))
+    <*> (unwords <$> some (argument str
+        (metavar "TEXT"
+        <> help "text of tweet to be sent")))
+
+-- | Parser for the input command
+tweetInput :: Parser Command
+tweetInput = SendInput
+    <$> optional (read <$> strOption
+        (long "tweets"
+        <> short 't'
+        <> metavar "NUM"
+        <> help "Number of tweets in a row, default 15"))
+    <*> optional (strOption
+        (long "reply"
+        <> short 'r'
+        <> help "id of status to reply to - be sure to include their handle, e.g. @my_build_errors"))
+    <*> optional (some $ argument str
+        (metavar "HANDLE1"
+        <> help "h to include in replies"))
diff --git a/bench/Bench.hs b/bench/Bench.hs
--- a/bench/Bench.hs
+++ b/bench/Bench.hs
@@ -1,19 +1,20 @@
 module Main where
 
-import Criterion.Main
-import Text.Megaparsec
-import Web.Tweet.Parser
-import Web.Tweet.Parser.FastParser
-import qualified Data.ByteString as BS
+import           Criterion.Main
+import qualified Data.ByteString  as BS
+import           Text.Megaparsec
+import           Web.Tweet.Parser
 
-fun = parse parseTweet ""
+setupEnv :: IO BS.ByteString
+setupEnv = BS.readFile "test/data"
 
-fast = fastParse
+setupEnv' :: IO String
+setupEnv' = readFile "test/data"
 
-main = do
-    file <- BS.readFile "test/data"
-    defaultMain [ bgroup "parseTweet"
-                      [ bench "226" $ whnf fun file ]
-                , bgroup "fastParser"
-                      [ bench "226" $ whnf fast file ]
-                ]
+main :: IO ()
+main =
+    defaultMain [
+              env setupEnv' $ \ file ->
+              bgroup "handrolled parser"
+                  [ bench "226" $ whnf (parse parseTweet) file ]
+            ]
diff --git a/cabal.project.local b/cabal.project.local
new file mode 100644
--- /dev/null
+++ b/cabal.project.local
@@ -0,0 +1,3 @@
+constraints: tweet-hs +development
+optimization: 2
+max-backjumps: 40000
diff --git a/src/Web/Tweet.hs b/src/Web/Tweet.hs
--- a/src/Web/Tweet.hs
+++ b/src/Web/Tweet.hs
@@ -1,47 +1,58 @@
 -- | Various utilities to tweet using the twitter api
--- 
--- Make sure you have a file credentials file (default the executable looks for is `.cred`) with the following info:
 --
+-- Make sure you have a file credentials file (default the executable looks for is @$HOME/.cred.toml@) with the following info:
+--
 -- @
 --
--- api-key: API_KEY
+-- api-key = "API_KEY"
 --
--- api-sec: API_SECRE
+-- api-sec = "API_SECRET"
 --
--- tok: OAUTH_TOKEN
+-- tok = "OAUTH_TOKEN"
 --
--- tok-sec: TOKEN_SECRET
+-- tok-sec = "TOKEN_SECRET"
 --
 -- @
 
 module Web.Tweet
     (
     -- * Functions to tweet
-    basicTweet
+      basicTweet
     , thread
+    , reply
     -- * Data type for a tweet
     , module Web.Tweet.Types
     -- * Various API calls
     , module Web.Tweet.API
+    , module Web.Tweet.API.Internal
     -- * Functions to sign API requests
     , signRequest
+    , oAuthMem
+    , credentialMem
     -- * Functions to generate a URL string from a `Tweet`
     , urlString
+    -- * Timeline filters
+    , filterReplies
+    , filterRTs
+    , filterQuotes
+    -- * Helper function to print a bird
+    , bird
     ) where
-    
-import Web.Tweet.Sign
-import Web.Tweet.API
-import Web.Tweet.Utils.API
-import Web.Tweet.Types
-import Data.List.Split (chunksOf)
-import Control.Monad
-import Control.Lens
-import Data.Maybe
-import Data.Default
 
+import           Control.Monad
+import           Data.List.Split        (chunksOf)
+import           Data.Maybe
+import           Lens.Micro
+import           Web.Tweet.API
+import           Web.Tweet.API.Internal
+import           Web.Tweet.Sign
+import           Web.Tweet.Types
+import           Web.Tweet.Utils
+import           Web.Tweet.Utils.API
+
 -- | Tweet a string given a path to credentials; return the id of the status.
 --
--- > basicTweet "On the airplane." ".cred"
+-- > basicTweet "On the airplane." ".cred.toml"
 basicTweet :: String -> FilePath -> IO Int
 basicTweet contents = tweetData (mkTweet contents)
 
@@ -51,22 +62,22 @@
 -- > thread "Hi I'm back in New York!" ["friend1","friend2"] Nothing 1 ".cred"
 thread :: String -> [String] -> Maybe Int -> Int -> FilePath -> IO ()
 thread contents hs idNum num filepath = do
-    let handleStr = concatMap (((++) " ") . ((++) "@")) hs
-    let content = (take num) . (chunksOf (140-(length handleStr))) $ contents
+    let handleStr = concatMap ((++) " " . (++) "@") hs
+    let content = take num . chunksOf (280-length handleStr) $ contents
     case idNum of
-        (Just i) -> thread' content hs idNum num filepath
+        (Just _) -> thread' content hs idNum filepath
         Nothing -> case content of
-            [] -> pure ()
-            [x] -> void $ basicTweet x filepath
-            y@(x:xs) -> thread' y hs (Just 0) num filepath
+            []      -> pure ()
+            [x]     -> void $ basicTweet x filepath
+            y@(_:_) -> thread' y hs (Just 0) filepath
 
--- | Helper function to make `thread` easier to write. 
-thread' :: [String] -> [String] -> Maybe Int -> Int -> FilePath -> IO ()
-thread' content hs idNum num filepath = do
-    let f = \str i -> tweetData (Tweet { _status = str, _handles = hs, _replyID = if i == 0 then Nothing else Just i }) filepath
+-- | Helper function to make `thread` easier to write.
+thread' :: [String] -> [String] -> Maybe Int -> FilePath -> IO ()
+thread' content hs idNum filepath = do
+    let f str i = tweetData Tweet { _status = str, _handles = hs, _replyID = if i == 0 then Nothing else Just i } filepath
     let initial = f (head content)
-    last <- foldr ((>=>) . f) initial (content) $ fromMaybe 0 idNum
-    deleteTweet (fromIntegral last) filepath
+    lastTweet <- foldr ((>=>) . f) initial content $ fromMaybe 0 idNum
+    deleteTweet (fromIntegral lastTweet) filepath
 
 -- | Reply with a single tweet. Works the same as `thread` but doesn't take the fourth argument.
 --
@@ -76,4 +87,4 @@
 
 -- | Make a `Tweet` with only the contents.
 mkTweet :: String -> Tweet
-mkTweet contents = over (status) (const (contents)) def 
+mkTweet contents = over status (pure contents) pricklyTweet
diff --git a/src/Web/Tweet/API.hs b/src/Web/Tweet/API.hs
--- a/src/Web/Tweet/API.hs
+++ b/src/Web/Tweet/API.hs
@@ -1,192 +1,447 @@
 {-# LANGUAGE OverloadedStrings #-}
 
--- | Module containing the functions directly dealing with twitter's API
+-- | Module containing the functions directly dealing with twitter's API. Most functions in this module have two versions - one which takes a path to a TOML file containing api keys/secrets and tokens/secrets, the other takes api keys/secrets and tokens/secrets as an argument.
 module Web.Tweet.API where
 
+import           Control.Composition
+import           Control.Monad
 import qualified Data.ByteString.Lazy.Char8 as BSL
-import Web.Tweet.Types
-import Web.Tweet.Utils
-import Control.Monad
-import Control.Lens
-import Text.Megaparsec.Error
-import Web.Tweet.Utils.API
-import Data.Composition
+import           Data.Containers.ListUtils  (nubOrd)
+import           Data.Foldable              (traverse_)
+import           Data.Functor               (($>))
+import           Data.Maybe                 (isJust)
+import qualified Data.Set                   as S
+import           Data.Void
+import           Lens.Micro
+import           Lens.Micro.Extras
+import           Text.Megaparsec.Error
+import           Web.Tweet.Types
+import           Web.Tweet.Utils
+import           Web.Tweet.Utils.API
 
 -- | Get tweets (text only) for some user
 getMarkov :: String -> Maybe Int -> FilePath -> IO [String]
-getMarkov = (fmap (map (view text))) .** getAll 
+getMarkov = fmap (map (view text)) .** getAll
 
 -- | Get all tweets by some user
 getAll :: String -> Maybe Int -> FilePath -> IO Timeline
-getAll screenName maxId filepath = do
-    tweets <- either (error "Parse tweets failed") id <$> getProfileMax screenName 200 filepath maxId
-    let lastId = _tweetId . last $ tweets
-    if (Just lastId) == maxId then 
+getAll sn maxId filepath = do
+    tweets <- either (error "Parse tweets failed") id <$> getProfileMax sn 200 filepath maxId
+    let lastId = _tweetId <$> tweets ^? _last
+    if lastId == maxId then
         pure []
     else
         do
-            putStrLn $ "fetching tweets since " ++ show lastId ++ "..."
-            next <- getAll screenName (Just lastId) filepath
+            if isJust lastId
+                then putStrLn $ "fetching tweets since " ++ show (lastId ^?! _Just) ++ "..."
+                else pure ()
+            next <- getAll sn lastId filepath
             pure (tweets ++ next)
 
+-- | tweet, given a `Tweet` and a `Config` containing necessary data to sign the request.
+tweetDataMem :: Tweet -> Config -> IO Int
+tweetDataMem tweet config = do
+    let requestString = urlString tweet
+    bytes <- postRequestMem ("https://api.twitter.com/1.1/statuses/update.json" ++ requestString) config
+    putStrLn $ displayTimelineColor . either (error "failed to parse tweet") id . getTweets . BSL.toStrict $ bytes
+    pure . view tweetId . head . either (error "failed to parse tweet") id . getTweets . BSL.toStrict $ bytes
+
 -- | tweet, given a `Tweet` and path to credentials. Return id of posted tweet.
 tweetData :: Tweet -> FilePath -> IO Int
 tweetData tweet filepath = do
     let requestString = urlString tweet
     bytes <- postRequest ("https://api.twitter.com/1.1/statuses/update.json" ++ requestString) filepath -- FIXME fix the coloration
     putStrLn $ displayTimelineColor . either (error "failed to parse tweet") id . getTweets . BSL.toStrict $ bytes
-    pure . (view tweetId) . head . either (error "failed to parse tweet") id . getTweets . BSL.toStrict $ bytes
+    pure . view tweetId . head . either (error "failed to parse tweet") id . getTweets . BSL.toStrict $ bytes
 
 -- | Gets user profile with max_id set.
-getProfileMax :: String -> Int -> FilePath -> Maybe Int -> IO (Either (ParseError Char Dec) Timeline)
+getProfileMax :: String -> Int -> FilePath -> Maybe Int -> IO (Either (ParseErrorBundle String Void) Timeline)
 getProfileMax = fmap (getTweets . BSL.toStrict) .*** getProfileRaw
 
 -- | Gets user profile with max_id set.
+getProfileMaxMem :: String -> Int -> Config -> Maybe Int -> IO (Either (ParseErrorBundle String Void) Timeline)
+getProfileMaxMem = fmap (getTweets . BSL.toStrict) .*** getProfileRawMem
+
+-- | Gets user profile with max_id set.
 getProfileRaw :: String -> Int -> FilePath -> Maybe Int -> IO BSL.ByteString
-getProfileRaw screenName count filepath maxId = getRequest ("https://api.twitter.com/1.1/statuses/user_timeline.json" ++ requestString) filepath
+getProfileRaw sn count filepath maxId = getRequest ("https://api.twitter.com/1.1/statuses/user_timeline.json" ++ requestString) filepath
     where requestString = case maxId of {
-        (Just id) -> "?screen_name=" ++ screenName ++ "&count=" ++ (show count) ++ "&max_id=" ++ (show id) ;
-        Nothing -> "?screen_name=" ++ screenName ++ "&count=" ++ (show count) }
+        (Just i) -> "?screen_name=" ++ sn ++ "&count=" ++ show count ++ "&max_id=" ++ show i ;
+        Nothing -> "?screen_name=" ++ sn ++ "&count=" ++ show count }
 
+-- | Gets user profile with max_id set
+getProfileRawMem :: String -> Int -> Config -> Maybe Int -> IO BSL.ByteString
+getProfileRawMem sn count config maxId = getRequestMem ("https://api.twitter.com/1.1/statuses/user_timeline.json" ++ requestString) config
+    where requestString = case maxId of {
+        (Just i) -> "?screen_name=" ++ sn ++ "&count=" ++ show count ++ "&max_id=" ++ show i ;
+        Nothing -> "?screen_name=" ++ sn ++ "&count=" ++ show count }
+
 -- | Get mentions and parse response as a list of tweets
-mentions :: Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+mentions :: Int -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
 mentions = fmap (getTweets . BSL.toStrict) .* mentionsRaw
 
+-- | Get mentions and parse response as a list of tweets
+mentionsMem :: Int -> Config -> IO (Either (ParseErrorBundle String Void) Timeline)
+mentionsMem = fmap (getTweets . BSL.toStrict) .* mentionsRawMem
+
+searchRaw :: String -> FilePath -> IO BSL.ByteString
+searchRaw str = getRequest ("https://api.twitter.com/1.1/search/tweets.json" ++ str)
+
+searchMentionsRaw :: Maybe Int -- ^ Max ID
+                  -> String -- ^ Username
+                  -> FilePath
+                  -> IO BSL.ByteString
+searchMentionsRaw maxid uname = searchRaw ("?q=to%3A" ++ uname ++ maxidUrl)
+    where maxidUrl = case maxid of
+            Just id' -> "&max_id=" ++ show id'
+            Nothing  -> ""
+
+searchRepliesRaw :: Maybe Int -- ^ Max ID
+                 -> String -- ^ Username
+                 -> Int -- ^ Tweet ID
+                 -> FilePath
+                 -> IO BSL.ByteString
+searchRepliesRaw maxid uname twid = searchRaw ("?q=to%3A" ++ uname ++ "&since_id=" ++ show twid ++ maxidUrl)
+    where maxidUrl = case maxid of
+            Just id' -> "&max_id=" ++ show id'
+            Nothing  -> ""
+
+loopMentions :: Maybe Int -> Maybe Int -> S.Set String -> String -> FilePath -> IO ()
+loopMentions pastMax maxid alreadyMuted uname fp =
+    if maxid == pastMax
+        then pure ()
+        else do
+            next <- searchMentions maxid uname fp
+            case next of
+                Right [] -> pure ()
+                Left{} -> pure ()
+                Right tws -> do
+                    let newMax = minimum (_tweetId <$> tws)
+                    let toMute = [ tw | tw <- nubOrd (_screenName <$> tws), tw `S.notMember` alreadyMuted ]
+                    let modAlready = thread [ S.insert k | k <- toMute ]
+                    putStrLn "Muted:"
+                    traverse_ (\u -> mute u fp *> putStrLn u) toMute
+                    loopMentions maxid (Just newMax) (modAlready alreadyMuted) uname fp
+
+muteMentions :: String -- ^ Screen name
+             -> FilePath
+             -> IO ()
+muteMentions = loopMentions (Just 0) Nothing mempty
+
+loopReplies :: Maybe Int -> Maybe Int -> String -> Int -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
+loopReplies pastMax maxid uname twid fp =
+    if maxid == pastMax
+        then pure (Right [])
+        else do
+            next <- searchReplies maxid uname twid fp
+            case next of
+                Right [] -> pure (Right [])
+                Left x -> pure (Left x)
+                Right tws -> let newMax = minimum (_tweetId <$> tws)
+                        in fmap (tws ++) <$> loopReplies maxid (Just newMax) uname twid fp
+
+searchMentions :: Maybe Int -> String -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
+searchMentions = fmap (getTweets . BSL.toStrict) .** searchMentionsRaw
+
+searchReplies :: Maybe Int -> String -> Int -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
+searchReplies = fmap (getTweets . BSL.toStrict) .*** searchRepliesRaw
+
+getReplies :: String -- ^ Screen name
+           -> Int -- ^ Tweet
+           -> FilePath
+           -> IO (Either (ParseErrorBundle String Void) Timeline)
+getReplies str twid = fmap (fmap (filter p)) . loopReplies (Just 0) Nothing str twid
+    where p entity = _replyTo entity == Just twid
+
+muteRepliers :: String
+             -> Int
+             -> FilePath
+             -> IO [String]
+muteRepliers str twid fp = do
+    us <- getReplies str twid fp
+    case us of
+        Left{} -> pure []
+        -- TODO: add a delay so I don't get rate limited?
+        Right xs -> let toMute = nubOrd (_screenName <$> xs) in traverse (\u -> mute u fp $> u) toMute
+
 -- | Gets mentions
 mentionsRaw :: Int -> FilePath -> IO BSL.ByteString
 mentionsRaw count = getRequest ("https://api.twitter.com/1.1/statuses/mentions_timeline.json" ++ requestString)
-    where requestString = "?count=" ++ (show count)
+    where requestString = "?count=" ++ show count
 
+-- | Gets mentions
+mentionsRawMem :: Int -> Config -> IO BSL.ByteString
+mentionsRawMem count = getRequestMem ("https://api.twitter.com/1.1/statuses/mentions_timeline.json" ++ requestString)
+    where requestString = "?count=" ++ show count
+
 -- | Get user profile given screen name and how many tweets to return
-getProfile :: String -> Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
-getProfile screenName count filepath = getProfileMax screenName count filepath Nothing
+getProfile :: String -> Int -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
+getProfile sn count filepath = getProfileMax sn count filepath Nothing
 
--- | Show a user profile given screen name, how many tweets to return, 
--- and whether to print them in color.
-showProfile :: String -> Int -> Bool -> FilePath -> IO String
-showProfile screenName count color = fmap (showTweets color) . getProfile screenName count
+-- | Mute a user given their screen name
+mute :: String -> FilePath -> IO ()
+mute = fmap void . muteUserRaw
 
--- | Show the most successful tweets by a given user, given their screen name. 
-showBest :: String -> Int -> Bool -> FilePath -> IO String
-showBest screenName n color = fmap (showTweets color . pure . (take n . hits)) . getAll screenName Nothing
+-- | Mute a user given their screen name
+muteMem :: String -> Config -> IO ()
+muteMem = fmap void . muteUserRawMem
 
--- | Display user timeline
-showTimeline :: Int -> Bool -> FilePath -> IO String
-showTimeline count color = (fmap (showTweets color)) . getTimeline count 
+-- | Unmute a user given their screen name
+unmute :: String -> FilePath -> IO ()
+unmute = fmap void . unmuteUserRaw
 
--- | Display user timeline in color, as appropriate
-showTweets :: Bool -> Either (ParseError Char Dec) Timeline -> String
-showTweets color = (either show id) . (fmap (if color then displayTimelineColor else displayTimeline))
+-- | Unmute a user given their screen name
+unmuteMem :: String -> Config -> IO ()
+unmuteMem = fmap void . unmuteUserRawMem
 
+-- | Mute a user given their screen name
+muteUserRaw :: String -> FilePath -> IO BSL.ByteString
+muteUserRaw sn = postRequest ("https://api.twitter.com/1.1/mutes/users/create.json?screen_name=" ++ sn)
+
+-- | Mute a user given their screen name
+muteUserRawMem :: String -> Config -> IO BSL.ByteString
+muteUserRawMem sn = postRequestMem ("https://api.twitter.com/1.1/mutes/users/create.json?screen_name=" ++ sn)
+
+-- | Unmute a user given their screen name
+unmuteUserRaw :: String -> FilePath -> IO BSL.ByteString
+unmuteUserRaw sn = postRequest ("https://api.twitter.com/1.1/mutes/users/destroy.json?screen_name=" ++ sn)
+
+-- | Unmute a user given their screen name
+unmuteUserRawMem :: String -> Config -> IO BSL.ByteString
+unmuteUserRawMem sn = postRequestMem ("https://api.twitter.com/1.1/mutes/users/destroy.json?screen_name=" ++ sn)
+
 -- | Get user's DMs.
+getDMsRaw :: Show p => p -> FilePath -> IO BSL.ByteString
 getDMsRaw count = getRequest ("https://api.twitter.com/1.1/direct_messages.json" ++ requestString)
-    where requestString = "?count=" ++ (show count)
+    where requestString = "?count=" ++ show count
 
+-- | Get a user's favorites
+getFavorites :: Int -> String -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
+getFavorites count = fmap (fmap (take count) . getTweets . BSL.toStrict) .* favoriteTweetListRaw
+
 -- | Get a timeline
-getTimeline :: Int -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
-getTimeline = (fmap (getTweets . BSL.toStrict)) .* getTimelineRaw
+getTimeline :: Int -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
+getTimeline = fmap (getTweets . BSL.toStrict) .* getTimelineRaw
 
+-- | Get a timeline
+getTimelineMem :: Int -> Config -> IO (Either (ParseErrorBundle String Void) Timeline)
+getTimelineMem = fmap (getTweets . BSL.toStrict) .* getTimelineRawMem
+
 -- | Get a user's timeline and return response as a bytestring
 getTimelineRaw :: Int -> FilePath -> IO BSL.ByteString
 getTimelineRaw count = getRequest ("https://api.twitter.com/1.1/statuses/home_timeline.json" ++ requestString)
-    where requestString = "?count=" ++ (show count)
+    where requestString = "?count=" ++ show count
 
+-- | Get a user's timeline and return response as a bytestring
+getTimelineRawMem :: Int -> Config -> IO BSL.ByteString
+getTimelineRawMem count = getRequestMem ("https://api.twitter.com/1.1/statuses/home_timeline.json" ++ requestString)
+    where requestString = "?count=" ++ show count
+
 -- | Delete a tweet given its id
 deleteTweet :: Integer -> FilePath -> IO ()
-deleteTweet = (fmap void) . deleteTweetRaw
+deleteTweet = fmap void . deleteTweetRaw
 
+-- | Delete a tweet given its id
+deleteTweetMem :: Integer -> Config -> IO ()
+deleteTweetMem = fmap void . deleteTweetRawMem
+
 -- | Get response, i.e. the tweet deleted
-deleteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+deleteTweetResponse :: Integer -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
 deleteTweetResponse = fmap (getTweets . BSL.toStrict) .* deleteTweetRaw
 
+-- | Get response, i.e. the tweet deleted
+deleteTweetResponseMem :: Integer -> Config -> IO (Either (ParseErrorBundle String Void) Timeline)
+deleteTweetResponseMem = fmap (getTweets . BSL.toStrict) .* deleteTweetRawMem
+
 -- | Favorite a tweet given its id
 favoriteTweet :: Integer -> FilePath -> IO ()
-favoriteTweet = (fmap void) . favoriteTweetRaw
+favoriteTweet = fmap void . favoriteTweetRaw
 
+-- | Favorite a tweet given its id
+favoriteTweetMem :: Integer -> Config -> IO ()
+favoriteTweetMem = fmap void . favoriteTweetRawMem
+
 -- | Favorite a tweet and returned the (parsed) response
-favoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+favoriteTweetList :: String -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
+favoriteTweetList = fmap (getTweets . BSL.toStrict) .* favoriteTweetListRaw
+
+-- | Favorite a tweet and returned the (parsed) response
+favoriteTweetListMem :: String -> Config -> IO (Either (ParseErrorBundle String Void) Timeline)
+favoriteTweetListMem = fmap (getTweets . BSL.toStrict) .* favoriteTweetListRawMem
+
+-- | Favorite a tweet and returned the (parsed) response
+favoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
 favoriteTweetResponse = fmap (getTweets . BSL.toStrict) .* favoriteTweetRaw
 
 -- | Unfavorite a tweet given its id
 unfavoriteTweet :: Integer -> FilePath -> IO ()
-unfavoriteTweet = (fmap void) . unfavoriteTweetRaw
+unfavoriteTweet = fmap void . unfavoriteTweetRaw
 
+-- | Unfavorite a tweet given its id
+unfavoriteTweetMem :: Integer -> Config -> IO ()
+unfavoriteTweetMem = fmap void . unfavoriteTweetRawMem
+
 -- | Unfavorite a tweet and returned the (parsed) response
-unfavoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+unfavoriteTweetResponse :: Integer -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
 unfavoriteTweetResponse = fmap (getTweets . BSL.toStrict) .* unfavoriteTweetRaw
 
+-- | Unfavorite a tweet and returned the (parsed) response
+unfavoriteTweetResponseMem :: Integer -> Config -> IO (Either (ParseErrorBundle String Void) Timeline)
+unfavoriteTweetResponseMem = fmap (getTweets . BSL.toStrict) .* unfavoriteTweetRawMem
+
 -- | Unretweet a tweet given its id
 unretweetTweet :: Integer -> FilePath -> IO ()
-unretweetTweet = (fmap void) . unretweetTweetRaw
+unretweetTweet = fmap void . unretweetTweetRaw
 
+-- | Unretweet a tweet given its id
+unretweetTweetMem :: Integer -> Config -> IO ()
+unretweetTweetMem = fmap void . unretweetTweetRawMem
+
 -- | Unretweet a tweet and returned the (parsed) response
-unretweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+unretweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
 unretweetTweetResponse = fmap (getTweets . BSL.toStrict) .* unretweetTweetRaw
 
+-- | Unretweet a tweet and returned the (parsed) response
+unretweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseErrorBundle String Void) Timeline)
+unretweetTweetResponseMem = fmap (getTweets . BSL.toStrict) .* unretweetTweetRawMem
+
 -- | Unfollow a user given their screen name
 unfollow :: String -> FilePath -> IO ()
-unfollow = (fmap void) . unfollowUserRaw
+unfollow = fmap void . unfollowUserRaw
 
+-- | Unfollow a user given their screen name
+unfollowMem :: String -> Config -> IO ()
+unfollowMem = fmap void . unfollowUserRawMem
+
 -- | Follow a user given their screen name
 follow :: String -> FilePath -> IO ()
-follow = (fmap void) . followUserRaw
+follow = fmap void . followUserRaw
 
+-- | Follow a user given their screen name
+followMem :: String -> Config -> IO ()
+followMem = fmap void . followUserRawMem
+
 -- | Block a user given their screen name
 block :: String -> FilePath -> IO ()
-block = (fmap void) . blockUserRaw
+block = fmap void . blockUserRaw
 
+-- | Block a user given their screen name
+blockMem :: String -> Config -> IO ()
+blockMem = fmap void . blockUserRawMem
+
 -- | Unblock a user given their screen name
 unblock :: String -> FilePath -> IO ()
-unblock = (fmap void) . unblockUserRaw
+unblock = fmap void . unblockUserRaw
 
+-- | Unblock a user given their screen name
+unblockMem :: String -> Config -> IO ()
+unblockMem = fmap void . unblockUserRawMem
+
 -- | Retweet a tweet given its id
 retweetTweet :: Integer -> FilePath -> IO ()
-retweetTweet = (fmap void) . retweetTweetRaw
+retweetTweet = fmap void . retweetTweetRaw
 
+-- | Retweet a tweet given its id
+retweetTweetMem :: Integer -> Config -> IO ()
+retweetTweetMem = fmap void . retweetTweetRawMem
+
 -- | Retweet a tweet and returned the (parsed) response
-retweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseError Char Dec) Timeline)
+retweetTweetResponse :: Integer -> FilePath -> IO (Either (ParseErrorBundle String Void) Timeline)
 retweetTweetResponse = fmap (getTweets . BSL.toStrict) .* retweetTweetRaw
 
+-- | Retweet a tweet and returned the (parsed) response
+retweetTweetResponseMem :: Integer -> Config -> IO (Either (ParseErrorBundle String Void) Timeline)
+retweetTweetResponseMem = fmap (getTweets . BSL.toStrict) .* retweetTweetRawMem
+
+-- | Get a lisr of favorited tweets by screen name; return bytestring response
+favoriteTweetListRaw :: String -> FilePath -> IO BSL.ByteString
+favoriteTweetListRaw sn = getRequest ("https://api.twitter.com/1.1/favorites/list.json?screen_name=" ++ sn)
+
+-- | Get a lisr of favorited tweets by screen name; return bytestring response
+favoriteTweetListRawMem :: String -> Config -> IO BSL.ByteString
+favoriteTweetListRawMem sn = getRequestMem ("https://api.twitter.com/1.1/favorites/list.json?screen_name=" ++ sn)
+
 -- | Favorite a tweet given its id; return bytestring response
 favoriteTweetRaw :: Integer -> FilePath -> IO BSL.ByteString
-favoriteTweetRaw id = postRequest ("https://api.twitter.com/1.1/favorites/create.json?id=" ++ (show id))
+favoriteTweetRaw idNum = postRequest ("https://api.twitter.com/1.1/favorites/create.json?id=" ++ show idNum)
 
--- | Retweet a tweet given its id; return bytestring response
+-- | Favorite a tweet given its idNum; return bytestring response
+favoriteTweetRawMem :: Integer -> Config -> IO BSL.ByteString
+favoriteTweetRawMem idNum = postRequestMem ("https://api.twitter.com/1.1/favorites/create.json?id=" ++ show idNum)
+
+-- | Retweet a tweet given its idNum; return bytestring response
 retweetTweetRaw :: Integer -> FilePath -> IO BSL.ByteString
-retweetTweetRaw id = postRequest ("https://api.twitter.com/1.1/statuses/retweet/" ++ (show id) ++ ".json")
+retweetTweetRaw idNum = postRequest ("https://api.twitter.com/1.1/statuses/retweet/" ++ show idNum ++ ".json")
 
+-- | Retweet a tweet given its idNum; return bytestring response
+retweetTweetRawMem :: Integer -> Config -> IO BSL.ByteString
+retweetTweetRawMem idNum = postRequestMem ("https://api.twitter.com/1.1/statuses/retweet/" ++ show idNum ++ ".json")
+
 -- | Send a DM given text, screen name of recipient.
-sendDMRaw txt screenName = postRequest ("https://api.twitter.com/1.1/direct_messages/new.json?text=" ++ encoded ++ "&screen_name" ++ screenName ++ ".json")
+sendDMRaw :: String -> String -> FilePath -> IO BSL.ByteString
+sendDMRaw txt sn = postRequest ("https://api.twitter.com/1.1/direct_messages/new.json?text=" ++ encoded ++ "&screen_name" ++ sn ++ ".json")
     where encoded = strEncode txt
 
 -- | Get DMs, return bytestring of response
 getDMs :: Int -> FilePath -> IO BSL.ByteString
-getDMs count = getRequest ("https://dev.twitter.com/rest/reference/get/direct_messages.json?count=" ++ (show count))
+getDMs count = getRequest ("https://dev.twitter.com/rest/reference/get/direct_messages.json?count=" ++ show count)
 
+-- | Get DMs, return bytestring of response
+getDMMem :: Int -> Config -> IO BSL.ByteString
+getDMMem count = getRequestMem ("https://dev.twitter.com/rest/reference/get/direct_messages.json?count=" ++ show count)
+
 -- | Follow a user given their screen name
 followUserRaw :: String -> FilePath -> IO BSL.ByteString
-followUserRaw screenName = postRequest ("https://api.twitter.com/1.1/friendships/create.json?screen_name=" ++ screenName)
+followUserRaw sn = postRequest ("https://api.twitter.com/1.1/friendships/create.json?screen_name=" ++ sn)
 
+-- | Follow a user given their screen name
+followUserRawMem :: String -> Config -> IO BSL.ByteString
+followUserRawMem sn = postRequestMem ("https://api.twitter.com/1.1/friendships/create.json?screen_name=" ++ sn)
+
 -- | Block a user given their screen name
 blockUserRaw :: String -> FilePath -> IO BSL.ByteString
-blockUserRaw screenName = postRequest ("https://api.twitter.com/1.1/blocks/create.json?screen_name=" ++ screenName)
+blockUserRaw sn = postRequest ("https://api.twitter.com/1.1/blocks/create.json?screen_name=" ++ sn)
 
+-- | Block a user given their screen name
+blockUserRawMem :: String -> Config -> IO BSL.ByteString
+blockUserRawMem sn = postRequestMem ("https://api.twitter.com/1.1/blocks/create.json?screen_name=" ++ sn)
+
 -- | Unblock a user given their screen name
 unblockUserRaw :: String -> FilePath -> IO BSL.ByteString
-unblockUserRaw screenName = postRequest ("https://api.twitter.com/1.1/blocks/destroy.json?screen_name=" ++ screenName)
+unblockUserRaw sn = postRequest ("https://api.twitter.com/1.1/blocks/destroy.json?screen_name=" ++ sn)
 
+-- | Unblock a user given their screen name
+unblockUserRawMem :: String -> Config -> IO BSL.ByteString
+unblockUserRawMem sn = postRequestMem ("https://api.twitter.com/1.1/blocks/destroy.json?screen_name=" ++ sn)
+
 -- | Follow a user given their screen name
 unfollowUserRaw :: String -> FilePath -> IO BSL.ByteString
-unfollowUserRaw screenName = postRequest ("https://api.twitter.com/1.1/friendships/destroy.json?screen_name=" ++ screenName)
+unfollowUserRaw sn = postRequest ("https://api.twitter.com/1.1/friendships/destroy.json?screen_name=" ++ sn)
 
+-- | Follow a user given their screen name
+unfollowUserRawMem :: String -> Config -> IO BSL.ByteString
+unfollowUserRawMem sn = postRequestMem ("https://api.twitter.com/1.1/friendships/destroy.json?screen_name=" ++ sn)
+
 -- | Unretweet a tweet given its id; return bytestring response
 unretweetTweetRaw :: Integer -> FilePath -> IO BSL.ByteString
-unretweetTweetRaw id = postRequest ("https://api.twitter.com/1.1/statuses/unretweet/" ++ (show id) ++ ".json")
+unretweetTweetRaw idNum = postRequest ("https://api.twitter.com/1.1/statuses/unretweet/" ++ show idNum ++ ".json")
 
--- | Favorite a tweet given its id; return bytestring response
+-- | Unretweet a tweet given its idNum; return bytestring response
+unretweetTweetRawMem :: Integer -> Config -> IO BSL.ByteString
+unretweetTweetRawMem idNum = postRequestMem ("https://api.twitter.com/1.1/statuses/unretweet/" ++ show idNum ++ ".json")
+
+-- | Unfavorite a tweet given its idNum; return bytestring response
 unfavoriteTweetRaw :: Integer -> FilePath -> IO BSL.ByteString
-unfavoriteTweetRaw id = postRequest ("https://api.twitter.com/1.1/favorites/destroy.json?id=" ++ (show id))
+unfavoriteTweetRaw idNum = postRequest ("https://api.twitter.com/1.1/favorites/destroy.json?id=" ++ show idNum)
 
--- | Delete a tweet given its id; return bytestring response
+-- | Unfavorite a tweet given its idNum; return bytestring response
+unfavoriteTweetRawMem :: Integer -> Config -> IO BSL.ByteString
+unfavoriteTweetRawMem idNum = postRequestMem ("https://api.twitter.com/1.1/favorites/destroy.json?id=" ++ show idNum)
+
+-- | Delete a tweet given its idNum; return bytestring response
 deleteTweetRaw :: Integer -> FilePath -> IO BSL.ByteString
-deleteTweetRaw id = postRequest ("https://api.twitter.com/1.1/statuses/destroy/" ++ (show id) ++ ".json")
+deleteTweetRaw idNum = postRequest ("https://api.twitter.com/1.1/statuses/destroy/" ++ show idNum ++ ".json")
+
+-- | Delete a tweet given its idNum; return bytestring response
+deleteTweetRawMem :: Integer -> Config -> IO BSL.ByteString
+deleteTweetRawMem idNum = postRequestMem ("https://api.twitter.com/1.1/statuses/destroy/" ++ show idNum ++ ".json")
diff --git a/src/Web/Tweet/API/Internal.hs b/src/Web/Tweet/API/Internal.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Tweet/API/Internal.hs
@@ -0,0 +1,41 @@
+-- | Helper functions for the command line tool.
+module Web.Tweet.API.Internal where
+
+import           Data.Void
+import           Text.Megaparsec.Error
+import           Web.Tweet.API
+import           Web.Tweet.Types
+import           Web.Tweet.Utils
+
+type Filter = Timeline -> Timeline
+
+-- | Show a user profile given screen name, how many tweets to return,
+-- and whether to print them in color.
+showProfile :: String -> Int -> Bool -> FilePath -> IO String
+showProfile sn count color = fmap (showTweets color) . getProfile sn count
+
+-- | Show the most successful tweets by a given user, given their screen name.
+showBest :: String -> Int -> Bool -> FilePath -> IO String
+showBest sn n color = fmap (showTweets color . pure . take n . hits) . getAll sn Nothing
+
+-- | Show the most successful tweets by a given user, given their screen name. Additionally filter out replies.
+showBest' :: String -> Int -> Bool -> FilePath -> IO String
+showBest' sn n color = fmap (showTweets color . pure . take n . hits') . getAll sn Nothing
+
+showReplies :: String -> Int -> Bool -> FilePath -> IO String
+showReplies uname twid color = fmap (showTweets color) . getReplies uname twid
+
+-- | Display user timeline
+showTimeline :: Int -> Bool -> FilePath -> IO String
+showTimeline count color = fmap (showTweets color) . getTimeline count
+
+showFilteredTL :: [Filter] -> String -> Int -> Bool -> FilePath -> IO String
+showFilteredTL filters sn count color = fmap (showTweets color . fmap (foldr (.) id filters)) . getProfile sn count
+
+-- | Display user timeline in color, as appropriate
+showTweets :: Bool -> Either (ParseErrorBundle String Void) Timeline -> String
+showTweets color = either show id . fmap (if color then displayTimelineColor else displayTimeline)
+
+-- | Display a user's favorites
+showFavorites :: Int -> String -> Bool -> FilePath -> IO String
+showFavorites count sn color = fmap (showTweets color) . getFavorites count sn
diff --git a/src/Web/Tweet/Exec.hs b/src/Web/Tweet/Exec.hs
deleted file mode 100644
--- a/src/Web/Tweet/Exec.hs
+++ /dev/null
@@ -1,276 +0,0 @@
--- | Provides IO action that parses command line options and tweetInputs from stdin
-module Web.Tweet.Exec ( exec
-                      , Program (..)
-                      , Command (..)) where
-
-import Web.Tweet
-import Options.Applicative
-import qualified Data.ByteString.Lazy.Char8 as BSL
-import Data.Monoid hiding (getAll)
-import System.Directory
-import Data.Maybe
-import Paths_tweet_hs
-import Data.Version
-
--- | Data type for our program: one optional path to a credential file, (optionally) the number of tweetInputs to make, the id of the status you're replying to, and a list of users you wish to mention.
-data Program = Program { subcommand :: Command , cred :: Maybe FilePath , color :: Bool }
-
--- | Data type for a command
--- TODO add boolean option to show ids alongside tweets
-data Command = Timeline { count :: Maybe Int }
-    | SendInput { tweetInputs :: Maybe Int, replyId :: Maybe String, replyHandles :: Maybe [String] }
-    | Profile { count :: Maybe Int , screenNameMaybe :: Maybe String }
-    | Mentions { count :: Maybe Int }
-    | Markov { screenName :: String }
-    | Send { tweets :: Maybe Int , replyId :: Maybe String , replyHandles :: Maybe [String] , userInput :: String }
-    | Sort { screenName :: String , count :: Maybe Int }
-    | Delete { twId :: Integer }
-    | Fav { twId :: Integer }
-    | Unfav { twId :: Integer }
-    | Retweet { twId :: Integer }
-    | Unretweet { twId :: Integer }
-    | Follow { screenName :: String }
-    | Unfollow { screenName :: String }
-    | Block { screenName :: String }
-    | Unblock { screenName :: String }
-    | Dump { screenName :: String }
-
--- | query twitter to post stdin with no fancy options
-fromStdIn :: Int -> FilePath -> IO ()
-fromStdIn = threadStdIn [] Nothing
-
--- | Tweet string given to us, as parsed from the command line
-fromCLI :: String -> Int -> FilePath -> IO ()
-fromCLI str = thread str [] Nothing
-
--- | Threaded tweetInputs from stdIn
-threadStdIn :: [String] -> Maybe Int -> Int -> FilePath -> IO ()
-threadStdIn hs idNum num filepath = do
-    contents <- getContents
-    thread contents hs idNum num filepath
-
--- | Executes parser
-exec :: IO ()
-exec = execParser opts >>= select
-    where
-        versionInfo = infoOption ("tweet-hs version: " ++ showVersion version) (short 'v' <> long "version" <> help "Show version")
-        opts        = info (helper <*> versionInfo <*> program)
-            (fullDesc
-            <> progDesc "Tweet and view tweets"
-            <> header "clit - a Command Line Interface Tweeter")
-
--- | Executes program given parsed `Program`
-select :: Program -> IO ()
-select (Program com maybeFile color) = case maybeFile of
-    (Just file) -> selectCommand com (not color) file
-    _ -> selectCommand com (not color) =<< (++ "/.cred") <$> getHomeDirectory
-
--- | Executes subcommand given subcommand + filepath to configuration file
-selectCommand :: Command -> Bool -> FilePath -> IO ()
-selectCommand (Send maybeNum Nothing Nothing input) _ file = fromCLI input (fromMaybe 15 maybeNum) file
-selectCommand (Send maybeNum (Just replyId) Nothing input) _ file = thread input [] (pure . read $ replyId) (fromMaybe 15 maybeNum) file
-selectCommand (Send maybeNum Nothing (Just handles) input) _ file = thread input handles Nothing (fromMaybe 15 maybeNum) file
-selectCommand (SendInput maybeNum Nothing Nothing) _ file  = fromStdIn (fromMaybe 15 maybeNum) file
-selectCommand (SendInput maybeNum (Just replyId) (Just handles)) _ file = threadStdIn handles (pure . read $ replyId) (fromMaybe 15 maybeNum) file
-selectCommand (SendInput maybeNum (Just replyId) Nothing) _ file = threadStdIn [] (pure . read $ replyId) (fromMaybe 15 maybeNum) file
-selectCommand (SendInput maybeNum Nothing (Just handles)) _ file = threadStdIn handles Nothing (fromMaybe 15 maybeNum) file
-selectCommand (Timeline maybeNum) color file = putStrLn =<< showTimeline (fromMaybe 11 maybeNum) color file
-selectCommand (Mentions maybeNum) color file = putStrLn =<< showTweets color <$> mentions (fromMaybe 11 maybeNum) file
-selectCommand (Profile maybeNum name) color file = putStrLn =<< showProfile (fromMaybe mempty name) (fromMaybe 11 maybeNum) color file
-selectCommand (Sort name maybeNum) color file = putStrLn =<< showBest name (fromMaybe 11 maybeNum) color file
-selectCommand (Markov name) _ file = do
-    raw <- getMarkov name Nothing file
-    appendFile (name ++ ".txt") (unlines raw)
-    putStrLn $ "Written output to: " ++ name ++ ".txt"
-selectCommand (Delete n) color file = do
-    putStrLn "Deleted:\n"
-    putStrLn =<< showTweets color <$> deleteTweetResponse n file
-selectCommand (Fav n) color file = do
-    putStrLn "Favorited:\n"
-    putStrLn =<< showTweets color <$> favoriteTweetResponse n file
-selectCommand (Unfav n) color file = do
-    putStrLn "Unfavorited:\n"
-    putStrLn =<< showTweets color <$> unfavoriteTweetResponse n file
-selectCommand (Retweet n) color file = do
-    putStrLn "Retweeted:\n"
-    putStrLn =<< showTweets color <$> retweetTweetResponse n file
-selectCommand (Unretweet n) color file = do
-    putStrLn "Unretweeted:\n"
-    putStrLn =<< showTweets color <$> unretweetTweetResponse n file
-selectCommand (Follow screenName) _ file = do
-    follow screenName file
-    putStrLn ("..." ++ screenName ++ " followed successfully!")
-selectCommand (Unfollow screenName) _ file = do
-    unfollow screenName file
-    putStrLn ("..." ++ screenName ++ " unfollowed successfully!")
-selectCommand (Block screenName) color file = do
-    block screenName file
-    putStrLn ("..." ++ screenName ++ " blocked successfully")
-selectCommand (Unblock screenName) color file = do
-    unblock screenName file
-    putStrLn ("..." ++ screenName ++ " unblocked successfully")
-selectCommand (Dump screenName) color file = BSL.putStrLn =<< (getProfileRaw screenName 3200 file Nothing)
-
--- | Parser to return a program datatype
-program :: Parser Program
-program = Program
-    <$> (hsubparser
-        (command "send" (info tweet (progDesc "Send a tweet from the command-line"))
-        <> command "input" (info tweetInput (progDesc "Send a tweet from stdIn"))
-        <> command "view" (info timeline (progDesc "Get your timeline"))
-        <> command "user" (info profile (progDesc "Get a user's profile"))
-        <> command "markov" (info markov (progDesc "Grab tweets en masse."))
-        <> command "hits" (info best (progDesc "View a user's top tweets."))
-        <> command "del" (info delete (progDesc "Delete a tweet.")) -- TODO delete/favorite in bunches!
-        <> command "fav" (info fav (progDesc "Favorite a tweet"))
-        <> command "ufav" (info unfav (progDesc "Unfavorite a tweet"))
-        <> command "urt" (info unrt (progDesc "Un-retweet a tweet"))
-        <> command "rt" (info rt (progDesc "Retweet a tweet"))
-        <> command "follow" (info fol (progDesc "Follow a user"))
-        <> command "unfollow" (info unfol (progDesc "Unfollow a user"))
-        <> command "dump" (info dump (progDesc "Dump tweets (for debugging)"))
-        <> command "block" (info blockParser (progDesc "Block a user"))
-        <> command "unblock" (info unblockParser (progDesc "Unblock a user"))
-        <> command "mentions" (info mentionsParser (progDesc "Fetch mentions"))))
-    <*> (optional $ strOption
-        (long "cred"
-        <> short 'c'
-        <> metavar "CREDENTIALS"
-        <> help "path to credentials"))
-    <*> switch
-        (long "color"
-        <> short 'l'
-        <> help "Turn off colorized terminal output.")
-
--- | Parser for the view subcommand
-timeline :: Parser Command
-timeline = Timeline
-    <$> (optional $ read <$> strOption
-        (long "count"
-        <> short 'n'
-        <> metavar "NUM"
-        <> help "number of tweetInputs to fetch, default 5"))
-
--- | Parser for the markov subcommand
-markov :: Parser Command
-markov = Markov <$> user
-
--- | Parser for the follow subcommand
-fol :: Parser Command
-fol = Follow <$> user
-
--- | Parser for the block subcommand
-blockParser :: Parser Command
-blockParser = Block <$> user
-
--- | Parser for the unblock subcommand
-unblockParser :: Parser Command
-unblockParser = Unblock <$> user
-
--- | Parser for the dump subcommand
-dump :: Parser Command
-dump = Dump <$> user
-
--- | Parser for the unfollow subcommand
-unfol :: Parser Command
-unfol = Unfollow <$> user
-
--- | Parse a user screen name
-user :: Parser String
-user = argument str
-    (metavar "SCREEN_NAME"
-    <> help "Screen name of user.")
-
--- | Parser for the del subcommand
-delete :: Parser Command
-delete = Delete <$> getInt
-
--- | Parser for the fav subcommand
-fav :: Parser Command
-fav = Fav <$> getInt
-
--- | Parser for the fav subcommand
-unfav :: Parser Command
-unfav = Unfav <$> getInt
-
--- | Parser for the fav subcommand
-unrt :: Parser Command
-unrt = Unretweet <$> getInt
-
--- | Parser for the fav subcommand
-rt :: Parser Command
-rt = Retweet <$> getInt
-
--- | Parser for the del subcommand
-getInt :: Parser Integer
-getInt = read <$> (argument str
-    (metavar "TWEET_ID"
-    <> help "ID of tweet"))
-
--- | Parser for the user subcommand
-profile :: Parser Command
-profile = Profile
-    <$> (optional $ read <$> strOption
-        (long "count"
-        <> short 'n'
-        <> metavar "NUM"
-        <> help "Number of tweetInputs to fetch, default 12"))
-    <*> optional user 
-
--- | Parser for the mention subcommand
-mentionsParser :: Parser Command
-mentionsParser = Mentions
-    <$> (optional $ read <$> strOption
-        (long "count"
-        <> short 'n'
-        <> metavar "NUM"
-        <> help "Number of tweetInputs to fetch, default 12"))
-
--- | Parse best tweets
-best :: Parser Command
-best = Sort
-    <$> argument str
-        (metavar "SCREEN_NAME"
-        <> help "Screen name of user you want to view.")
-    <*> (optional $ read <$> strOption
-        (long "count"
-        <> short 'n'
-        <> metavar "NUM"
-        <> help "Number of tweetInputs to fetch, default 12"))
-
--- | Parser for the send subcommand
-tweet :: Parser Command
-tweet = Send
-    <$> (optional $ read <$> strOption
-        (long "tweets"
-        <> short 't'
-        <> metavar "NUM"
-        <> help "Number of tweetInputs in a row, default 15"))
-    <*> (optional $ strOption
-        (long "reply"
-        <> short 'r'
-        <> help "id of status to reply to - be sure to include their handle, e.g. @my_build_errors"))
-    <*> (optional (some $ strOption
-        (long "handle"
-        <> short 'h'
-        <> metavar "HANDLE1"
-        <> help "Handles to include in replies")))
-    <*> (unwords <$> (some $ argument str
-        (metavar "TEXT"
-        <> help "text of tweet to be sent")))
-
--- | Parser for the input command
-tweetInput :: Parser Command
-tweetInput = SendInput
-    <$> (optional $ read <$> strOption
-        (long "tweets"
-        <> short 't'
-        <> metavar "NUM"
-        <> help "Number of tweets in a row, default 15"))
-    <*> (optional $ strOption
-        (long "reply"
-        <> short 'r'
-        <> help "id of status to reply to - be sure to include their handle, e.g. @my_build_errors"))
-    <*> (optional (some $ argument str
-        (metavar "HANDLE1"
-        <> help "handles to include in replies")))
diff --git a/src/Web/Tweet/Parser.hs b/src/Web/Tweet/Parser.hs
--- a/src/Web/Tweet/Parser.hs
+++ b/src/Web/Tweet/Parser.hs
@@ -1,42 +1,61 @@
+{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
+{-# LANGUAGE LambdaCase        #-}
 {-# LANGUAGE OverloadedStrings #-}
--- FIXME make this module available under cabal file
+
 -- | Module containing parsers for tweet and response data.
 module Web.Tweet.Parser ( parseTweet
                         , getData ) where
 
-import qualified Data.ByteString as BS
-import Text.Megaparsec.ByteString
-import Text.Megaparsec.Lexer as L
-import Text.Megaparsec
-import Web.Tweet.Types
-import Data.Monoid
-import qualified Data.Map as M
-import Data.Maybe
-import Control.Monad
+import           Control.Composition        ((.*))
+import           Control.Monad
+import qualified Data.ByteString            as BS
+import           Data.List                  (isInfixOf)
+import qualified Data.Map                   as M
+import           Data.Maybe
+import qualified Data.Text                  as T
+import qualified Data.Text.Encoding         as TE
+import           Data.Void
+import           Text.Megaparsec
+import           Text.Megaparsec.Byte
+import           Text.Megaparsec.Byte.Lexer as L
+import           Web.Tweet.Types
 
+type Parser = Parsec Void String
+
 -- | Parse some number of tweets
 parseTweet :: Parser Timeline
-parseTweet = many (try getData <|> (const (TweetEntity "" "" "" 0 Nothing 0 0) <$> eof))
+parseTweet = many (try getData <|> (TweetEntity "" Nothing "" "" 0 mempty Nothing 0 0 <$ eof))
 
--- | Parse a single tweet's: name, text, fave count, retweet count
+inReplyTo :: String -> Maybe Int
+inReplyTo str =
+    if "null" `isInfixOf` str
+        then Nothing
+        else pure (read str)
+
+-- | Parse a single tweet's: n, text, fave count, retweet count
 getData :: Parser TweetEntity
 getData = do
-    id <- read <$> filterStr "id" 
-    text <- filterStr "text"
+    idNum <- read <$> filterStr "id"
+    t <- filterStr "text"
     skipMentions
-    name <- filterStr "name"
-    screenName' <- filterStr "screen_name"
+    irt <- inReplyTo <$> filterStr "in_reply_to_status_id"
+    n <- filterStr "name"
+    screenn' <- filterStr "screen_name"
+    --withheldCountries <- (catMaybes . sequence) <$> optional filterList
+    let withheldCountries = mempty
+    --let toBlock = "DE" `elem` (catMaybes (sequence bannedList))
     isQuote <- filterStr "is_quote_status"
     case isQuote of
         "false" -> do
             rts <- read <$> filterStr "retweet_count"
             faves <- read <$> filterStr "favorite_count"
-            pure (TweetEntity text name screenName' id Nothing rts faves)
+            pure (TweetEntity t irt n screenn' idNum withheldCountries Nothing rts faves)
         "true" -> do
-            quoted <- parseQuoted
+            q <- parseQuoted
             rts <- read <$> filterStr "retweet_count"
             faves <- read <$> filterStr "favorite_count"
-            pure $ TweetEntity text name screenName' id quoted rts faves
+            pure $ TweetEntity t irt n screenn' idNum withheldCountries q rts faves
+        _ -> error "is_quote_status must have a value of \"true\" or \"false\""
 
 -- | Parse a the quoted tweet
 parseQuoted :: Parser (Maybe TweetEntity)
@@ -44,73 +63,78 @@
     optional (string ",\"quoted_status_id" >> filterStr "quoted_status_id_str") -- FIXME it's skipping too many? prob is when two are deleted in a row twitter just dives in to RTs
     contents <- optional $ string "\",\"quoted_status"
     case contents of
-        (Just contents) -> pure <$> getData
-        _ -> pure Nothing
-    
+        (Just _) -> pure <$> getData
+        _        -> pure Nothing
 
 -- | Skip a set of square brackets []
 skipInsideBrackets :: Parser ()
-skipInsideBrackets = void (between (char '[') (char ']') $ many (skipInsideBrackets <|> void (noneOf ("[]" :: String))))
+skipInsideBrackets = void (between (single '[') (single ']') $ many (skipInsideBrackets <|> void (noneOf ("[]" :: String))))
 
--- | Skip user mentions field to avoid parsing the wrong name
+-- | Skip user mentions field to avoid parsing the wrong n
 skipMentions :: Parser ()
 skipMentions = do
-    many $ try $ anyChar >> notFollowedBy (string "\"user_mentions\":")
-    char ','
-    string "\"user_mentions\":"
+    many $ try $ anySingle >> notFollowedBy (string "\"user_mentions\":")
+    string ",\"user_mentions\":"
     skipInsideBrackets
-    pure ()
 
 -- | Throw out input until we get to a relevant tag.
 filterStr :: String -> Parser String
 filterStr str = do
-    many $ try $ anyChar >> notFollowedBy (string ("\"" <> str <> "\":"))
-    char ','
+    many $ try $ anySingle >> notFollowedBy (string ("\"" <> str <> "\":"))
+    single ','
     filterTag str
 
 -- | Parse a field given its tag
 filterTag :: String -> Parser String
 filterTag str = do
     string $ "\"" <> str <> "\":"
-    open <- optional $ char '\"'
+    open <- optional $ single '\"'
     let forbidden = if isJust open then ("\\\"" :: String) else ("\\\"," :: String)
-    want <- many $ parseHTMLChar <|> noneOf forbidden <|> specialChar '\"' <|> specialChar '/' <|> newlineChar <|> unicodeChar -- TODO modify parsec to make this parallel?
-    pure want
+    many $ parseHTMLChar <|> noneOf forbidden <|> specialChar '\"' <|> specialChar '/' <|> newlineChar <|> emojiChar <|> unicodeChar -- TODO modify parsec to make this parallel?
 
 -- | Parse a newline
 newlineChar :: Parser Char
-newlineChar = do
-    string "\\n"
-    pure '\n'
+newlineChar = string "\\n" >> pure '\n'
 
 -- | Parser for unicode; twitter will give us something like "/u320a"
 unicodeChar :: Parser Char
-unicodeChar = do
-    string "\\u"
-    num <- fromHex . filterEmoji . BS.pack . map (fromIntegral . fromEnum) <$> count 4 anyChar
-    pure . toEnum . fromIntegral $ num
+unicodeChar = toEnum . fromIntegral . f <$> go
+    where go = string "\\u" >> count 4 anySingle
+          f = fromHex . filterEmoji . BS.pack . fmap (fromIntegral . fromEnum)
 
+emojiChar :: Parser Char
+emojiChar = go a
+    where a = string "\\ud" >> count 3 anySingle
+          go = (<*>) =<< (((T.head . decodeUtf16) .* ((<>) . (<> "d") . ("d" <>))) <$>)
+
+decodeUtf16 :: String -> T.Text
+decodeUtf16 = TE.decodeUtf16BE . BS.concat . go
+    where
+        go []             = []
+        go (a:b:c:d:rest) = let sym = convert16 [a,b] [c,d] in sym : go rest
+        go _              = error "Internal error: decodeUtf16 failed."
+        convert16 x y = BS.pack [(read . ("0x"<>)) x, (read . ("0x"<>)) y]
+
 -- | helper function to ignore emoji
+filterEmoji :: BS.ByteString -> BS.ByteString
 filterEmoji str = if BS.head str == (fromIntegral . fromEnum $ 'd') then "FFFD" else str
 
 -- | Parse HTML chars
 parseHTMLChar :: Parser Char
 parseHTMLChar = do
-    char '&'
-    innards <- many $ noneOf (";" :: String)
-    char ';'
-    pure . (\case 
-        (Just a) -> a 
+    single '&'
+    innards <- many $ anySingleBut ';'
+    single ';'
+    pure . (\case
+        (Just a) -> a
         Nothing -> '?') $ M.lookup innards (M.fromList [("amp",'&'),("gt",'>'),("lt",'<'),("quot",'"'),("euro",'€'),("ndash",'–'),("mdash",'—')])
 
-
 -- | Parse escaped characters
 specialChar :: Char -> Parser Char
-specialChar c = do
-    string $ "\\" ++ pure c
-    pure c
+specialChar c = string ("\\" <> pure c) >> pure c
 
 -- | Convert a string of four hexadecimal digits to an integer.
 fromHex :: BS.ByteString -> Integer
-fromHex = fromRight . (parse (L.hexadecimal :: Parser Integer) "")
-    where fromRight (Right a) = a
+fromHex = fromRight . parseMaybe (L.hexadecimal :: Parsec Void BS.ByteString Integer)
+    where fromRight (Just a) = a
+          fromRight _        = error "failed to parse hex"
diff --git a/src/Web/Tweet/Parser/FastParser.hs b/src/Web/Tweet/Parser/FastParser.hs
deleted file mode 100644
--- a/src/Web/Tweet/Parser/FastParser.hs
+++ /dev/null
@@ -1,32 +0,0 @@
-{-# LANGUAGE DeriveGeneric     #-}
-
-module Web.Tweet.Parser.FastParser ( fastParse
-                                   , FastTweet (..)
-                                   ) where
-
-import GHC.Generics
-import Data.Aeson
-import qualified Data.Text as T
-import qualified Data.ByteString.Lazy as BSL
-import qualified Data.ByteString as BS
---import Data.Vector
-
-data FastTweet = FastTweet
-    { id :: !Int
-    , text :: !T.Text
-    , user :: User
-    , quoted_status :: Maybe FastTweet
-    , retweet_count :: !Int
-    , favorite_count :: !Int
-    } deriving (Generic, Eq, Show)
-
-data User = User { name        :: !T.Text
-                 , screen_name :: !T.Text } 
-                 deriving (Generic, Eq, Show)
-
-instance FromJSON FastTweet
-
-instance FromJSON User
-
-fastParse :: BS.ByteString -> Either String [FastTweet] -- (Vector FastTweet)
-fastParse = eitherDecode . BSL.fromStrict
diff --git a/src/Web/Tweet/Sign.hs b/src/Web/Tweet/Sign.hs
--- a/src/Web/Tweet/Sign.hs
+++ b/src/Web/Tweet/Sign.hs
@@ -3,12 +3,23 @@
 -- | Functions to sign HTTP requests with oAuth
 module Web.Tweet.Sign ( signRequest
                       , signRequestMem
-                      , mkConfig ) where
+                      , mkConfig
+                      , mkConfigToml
+                      , oAuthMem
+                      , credentialMem ) where
 
-import Web.Tweet.Utils
-import Web.Authenticate.OAuth
-import Network.HTTP.Client
-import Web.Tweet.Types
+import           Data.ByteString        as BS
+import           Data.HashMap.Lazy
+import           Data.Monoid
+import qualified Data.Text              as T
+import           Data.Text.Encoding     (encodeUtf8)
+import qualified Data.Text.IO           as TIO
+import           Network.HTTP.Client
+import           Prelude                hiding (lookup)
+import           Text.Toml
+import           Web.Authenticate.OAuth
+import           Web.Tweet.Types
+import           Web.Tweet.Utils
 
 -- | Sign a request using your OAuth dev token, as stored in a config file.
 -- Uses the IO monad because signatures require a timestamp
@@ -19,14 +30,47 @@
 signRequestMem :: Config -> Request -> IO Request
 signRequestMem = uncurry signOAuth
 
+-- | Create an OAuth api key from two ByteStrings.
+oAuthMem :: BS.ByteString -- ^ API key
+         -> BS.ByteString -- ^ API secret
+         -> OAuth
+oAuthMem key secret = newOAuth { oauthConsumerKey = key, oauthConsumerSecret = secret, oauthServerName = "api.twitter.com" }
+
+credentialMem :: BS.ByteString -- ^ Token
+              -> BS.ByteString -- ^ Token secret
+              -> Credential
+credentialMem = newCredential
+
 -- | Create an OAuth api key from config data in a file
 oAuth :: FilePath -> IO OAuth
 oAuth filepath = do
-    secret <- (lineByKey "api-sec") <$> getConfigData filepath
-    key <- (lineByKey "api-key") <$> getConfigData filepath
+    secret <- lineByKey "api-sec" <$> getConfigData filepath
+    key <- lineByKey "api-key" <$> getConfigData filepath
     let url = "api.twitter.com"
-    return newOAuth { oauthConsumerKey = key , oauthConsumerSecret = secret , oauthServerName = url }
+    pure newOAuth { oauthConsumerKey = key , oauthConsumerSecret = secret , oauthServerName = url }
 
+getKey :: HashMap T.Text Node -> T.Text -> BS.ByteString
+getKey hm key = case lookup key hm of
+    (Just (VString k)) -> encodeUtf8 k
+    (Just _) -> error $ "Key: " <> T.unpack key <> " found in the config file, but it is not a string."
+    Nothing -> error $ "Key: " <> T.unpack key <> " not found in config file."
+
+mkConfigToml :: FilePath -> IO Config
+mkConfigToml filepath = do
+    t <- TIO.readFile filepath
+    let hm = case parseTomlDoc ("failed to read .toml at: " <> filepath) t of
+            Right tab -> tab
+            Left e    -> error (show e)
+        secret = getKey hm "api-sec"
+        key = getKey hm "api-key"
+        tok = getKey hm "tok"
+        tokSecret = getKey hm "tok-sec"
+        url = "api.twitter.com"
+        o = newOAuth { oauthConsumerKey = key , oauthConsumerSecret = secret , oauthServerName = url }
+        c = newCredential tok tokSecret
+    pure (o, c)
+
+-- | Given a filepath, parse the contents of the file and return a configuration.
 mkConfig :: FilePath -> IO Config
 mkConfig filepath = do
     o <- oAuth filepath
@@ -36,5 +80,5 @@
 -- | Create a new credential from a token and token secret
 credential :: FilePath -> IO Credential
 credential filepath = newCredential <$> token <*> secretToken
-    where token       = (lineByKey "tok") <$> getConfigData filepath
-          secretToken = (lineByKey "tok-sec") <$> getConfigData filepath
+    where token       = lineByKey "tok" <$> getConfigData filepath
+          secretToken = lineByKey "tok-sec" <$> getConfigData filepath
diff --git a/src/Web/Tweet/Types.hs b/src/Web/Tweet/Types.hs
--- a/src/Web/Tweet/Types.hs
+++ b/src/Web/Tweet/Types.hs
@@ -1,32 +1,32 @@
-{-# LANGUAGE DeriveGeneric   #-}
-{-# LANGUAGE DeriveAnyClass  #-}
-
 -- | Exports the `Tweet` type, a datatype for building tweets easily
 module Web.Tweet.Types where
 
-import GHC.Generics
-import Control.Lens
-import Data.Default
-import Web.Authenticate.OAuth
+import           Lens.Micro
+import           Web.Authenticate.OAuth
 
--- | Data type for our request: consists of the status text, whether to trium user information in the response, the handles to mention, and optionally the id of the status to reply to.
+-- | Data type for our request: consists of the status text, whether to trium u information in the response, the handles to mention, and optionally the id of the status to reply to.
 data Tweet = Tweet
-    { _status   :: String
-    , _handles  :: [String]
-    , _replyID  :: Maybe Int
-    } deriving (Generic, Default)
+    { _status  :: String
+    , _handles :: [String]
+    , _replyID :: Maybe Int
+    }
 
 -- | Data type for tweets as they are returned
 data TweetEntity = TweetEntity
-    { _text :: String
-    , _name :: String
+    { _text       :: String
+    , _replyTo    :: Maybe Int
+    , _name       :: String
     , _screenName :: String
-    , _tweetId :: Int
-    , _quoted :: Maybe TweetEntity
-    , _retweets :: Int
-    , _favorites :: Int
-    } deriving (Generic, Default, Eq, Show)
+    , _tweetId    :: Int
+    , _withheld   :: [String]
+    , _quoted     :: Maybe TweetEntity
+    , _retweets   :: Int
+    , _favorites  :: Int
+    } deriving (Eq)
 
+pricklyTweet :: Tweet
+pricklyTweet = Tweet undefined [] Nothing
+
 -- | Stores data like (name, text, favoriteCount, retweetCount)
 type Timeline = [TweetEntity]
 
@@ -58,17 +58,17 @@
 screenName f tweet@TweetEntity { _screenName = scr } = fmap (\scr' -> tweet { _screenName = scr'}) (f scr)
 
 -- | Lens for `TweetEntity` accessing the `_tweetId` field.
-tweetId :: Lens' TweetEntity Int 
+tweetId :: Lens' TweetEntity Int
 tweetId f tweet@TweetEntity { _tweetId = tw } = fmap (\tw' -> tweet { _tweetId = tw'}) (f tw)
 
 -- | Lens for `TweetEntity` accessing the `_quoted` field.
-quoted :: Lens' TweetEntity (Maybe TweetEntity) 
-quoted f tweet@TweetEntity { _quoted = quot } = fmap (\quot' -> tweet { _quoted = quot'}) (f quot)
+quoted :: Lens' TweetEntity (Maybe TweetEntity)
+quoted f tweet@TweetEntity { _quoted = q } = fmap (\q' -> tweet { _quoted = q'}) (f q)
 
 -- | Lens for `TweetEntity` accessing the `_retweets` field.
-retweets :: Lens' TweetEntity Int 
+retweets :: Lens' TweetEntity Int
 retweets f tweet@TweetEntity { _retweets = rts } = fmap (\rts' -> tweet { _retweets = rts'}) (f rts)
 
 -- | Lens for `TweetEntity` accessing the `_favorites` field.
-favorites :: Lens' TweetEntity Int 
+favorites :: Lens' TweetEntity Int
 favorites f tweet@TweetEntity { _favorites = fav } = fmap (\fav' -> tweet { _favorites = fav'}) (f fav)
diff --git a/src/Web/Tweet/Utils.hs b/src/Web/Tweet/Utils.hs
--- a/src/Web/Tweet/Utils.hs
+++ b/src/Web/Tweet/Utils.hs
@@ -1,119 +1,140 @@
 -- | Miscellaneous functions that don't fit the project directly
 module Web.Tweet.Utils (
     hits
+  , hits'
   , getTweets
   , displayTimeline
   , displayTimelineColor
   , lineByKey
-  , getConfigData ) where
+  , bird
+  , getConfigData
+  , filterQuotes
+  , filterReplies
+  , filterRTs
+  ) where
 
-import qualified Data.ByteString.Char8 as BS
-import qualified Data.ByteString as BS2
-import Data.List
-import Web.Tweet.Types
-import Control.Lens hiding (noneOf)
-import Web.Tweet.Utils.Colors
-import Data.List.Extra
-import Web.Tweet.Parser
-import Text.Megaparsec
+import           Control.Composition
+import qualified Data.ByteString        as BS2
+import qualified Data.ByteString.Char8  as BS
+import           Data.List
+import           Data.List.Extra
+import           Data.Void
+import           Lens.Micro.Extras
+import           Text.Megaparsec
+import           Web.Tweet.Parser
+import           Web.Tweet.Types
+import           Web.Tweet.Utils.Colors
 
 -- | filter out retweets, and sort by most successful.
 hits :: Timeline -> Timeline
-hits = sortTweets . filterRTs 
+hits = sortTweets . filterRTs
 
+-- | Filter out retweets and replies, and sort by most sucessful.
+hits' :: Timeline -> Timeline
+hits' = hits . filterReplies
+
 -- | Filter out retweets
 filterRTs :: Timeline -> Timeline
-filterRTs = filter ((/="RT @") . take 4 . (view text))
+filterRTs = filter ((/="RT @") . take 4 . view text)
 
+-- | Filter out replies
+filterReplies :: Timeline -> Timeline
+filterReplies = filter ((/="@") . take 1 . view text)
+
 -- | Filter out quotes
 filterQuotes :: Timeline -> Timeline
-filterQuotes = filter ((==Nothing) . (view quoted))
+filterQuotes = filter ((==Nothing) . view quoted)
 
--- | Get a list of tweets from a response, returning author, favorites, retweets, and content. 
-getTweets :: BS2.ByteString -> Either (ParseError Char Dec) Timeline
-getTweets = parse parseTweet "" 
+-- | Get a list of tweets from a response, returning author, favorites, retweets, and content.
+getTweets :: BS2.ByteString -> Either (ParseErrorBundle String Void) Timeline
+getTweets = parse parseTweet "" . BS.unpack
 
 -- | Display Timeline without color
 displayTimeline :: Timeline -> String
-displayTimeline ((TweetEntity content user screenName idTweet Nothing rts fave):rest) = concat [user
+displayTimeline (TweetEntity content _ u sn idTweet _ Nothing rts fave:rest) = concat [u
     , " ("
-    , screenName
+    , sn
     , ")"
-    ,":\n    " 
-    ,fixNewline content 
-    ,"\n    " 
-    ,"♥ " 
-    ,show fave 
-    ," ♺ " 
-    ,show rts 
+    ,":\n    "
+    ,fixNewline content
+    ,"\n    "
+    , "💜"
+    -- , "♥ "
+    ,show fave
+    , " \61561  "
+    -- ," ♺ "
+    ,show rts
     , "  "
     , show idTweet
-    ,"\n\n" 
+    ,"\n\n"
     ,displayTimeline rest]
-displayTimeline ((TweetEntity content user screenName idTweet (Just quoted) rts fave):rest) = concat [user 
+displayTimeline (TweetEntity content _ u sn idTweet _ (Just q) rts fave:rest) = concat [u
     , " ("
-    , screenName
+    , sn
     , ")"
-    , ":\n    " 
-    , fixNewline content 
-    , "\n    " 
-    , "♥ " 
-    , show fave 
-    , " ♺ " 
-    , show rts 
+    , ":\n    "
+    , fixNewline content
+    , "\n    "
+    , "💜"
+    , show fave
+    , " \61561  "
+    , show rts
     , "  "
     , show idTweet
-    , "\n    " 
-    , _name quoted 
+    , "\n    "
+    , _name q
     , " ("
-    , _screenName quoted
+    , _screenName q
     , ")"
-    , ": " 
-    , _text quoted 
-    , "\n\n" 
+    , ": "
+    , _text q
+    , "\n\n"
     , displayTimeline rest]
 displayTimeline [] = []
 
+bird :: String
+bird = toPlainBlue "🐦\n"
+
 -- | Display Timeline in color
 displayTimelineColor :: Timeline -> String
-displayTimelineColor ((TweetEntity content user screenName idTweet Nothing rts fave):rest) = concat [toYellow user 
+displayTimelineColor (TweetEntity content _ u sn idTweet _ Nothing rts fave:rest) = concat [toYellow u
     , " ("
-    , screenName
+    , sn
     , ")"
-    , ":\n    " 
+    , ":\n    "
     , fixNewline content
-    , "\n    " 
-    , toRed "♥"
+    , "\n    "
+    , toRed "💜"
     , " "
-    , show fave 
-    , toGreen " ♺ " 
-    , show rts 
+    , show fave
+    , toGreen " \61561  " -- ♺ "
+    , show rts
     , "  "
     , toBlue (show idTweet)
-    , "\n\n" 
+    , "\n\n"
     , displayTimelineColor rest]
-displayTimelineColor ((TweetEntity content user screenName  idTweet (Just quoted) rts fave):rest) = concat [toYellow user 
+displayTimelineColor (TweetEntity content _ u sn idTweet _ (Just q) rts fave:rest) = concat [toYellow u
     , " ("
-    , screenName
+    , sn
     , ")"
-    , ":\n    " 
-    , fixNewline content 
-    , "\n    " 
-    , toRed "♥"
+    , ":\n    "
+    , fixNewline content
+    , "\n    "
+    , toRed "💜"
     , " "
-    , show fave 
-    , toGreen " ♺ " 
-    , show rts 
+    , show fave
+    , toGreen " \61561  "
+    , show rts
     , "  "
     , toBlue (show idTweet)
-    , "\n    " 
-    , toYellow $ _name quoted 
+    , "\n    "
+    , toYellow $ _name q
     , " ("
-    , _screenName quoted
+    , _screenName q
     , ")"
-    , ": " 
-    , _text quoted 
-    , "\n\n" 
+    , ": "
+    , _text q
+    , "\n\n"
     , displayTimelineColor rest]
 displayTimelineColor [] = []
 
@@ -124,7 +145,7 @@
 -- | sort tweets by most successful
 sortTweets :: Timeline -> Timeline
 sortTweets = sortBy compareTweet
-    where compareTweet (TweetEntity _ _ _ _ _ r1 f1) (TweetEntity _ _ _ _ _ r2 f2) = compare (2*r2 + f2) (2*r1 + f1)
+    where compareTweet (TweetEntity _ _ _ _ _ _ _ r1 f1) (TweetEntity _ _ _ _ _ _ _ r2 f2) = compare (2*r2 + f2) (2*r1 + f1)
 
 -- | helper function to get the key as read from a file
 keyLinePie :: String -> String
@@ -132,15 +153,15 @@
 
 -- | Pick out a key value from a key
 lineByKey :: BS.ByteString -> [(BS.ByteString, BS.ByteString)] -> BS.ByteString
-lineByKey key = snd . head . (filter (\i -> fst i == key))
+lineByKey = snd .* head .* (filter . (fst .@ (==)))
 
 -- | Filter a line of a file for only the actual data and no descriptors
 filterLine :: String -> String
-filterLine = reverse . (takeWhile (not . (`elem` (" :" :: String)))) . reverse
+filterLine = reverse . takeWhile (not . (`elem` (" :" :: String))) . reverse
 
 -- | Get pairs of "key" to search for and actual values
 getConfigData :: FilePath -> IO [(BS.ByteString, BS.ByteString)]
 getConfigData filepath = zip <$> keys <*> content
-    where content = (map (BS.pack . filterLine)) . lines <$> file
-          keys    = (map (BS.pack . keyLinePie)) . lines <$> file
+    where content = map (BS.pack . filterLine) . lines <$> file
+          keys    = map (BS.pack . keyLinePie) . lines <$> file
           file    = readFile filepath
diff --git a/src/Web/Tweet/Utils/API.hs b/src/Web/Tweet/Utils/API.hs
--- a/src/Web/Tweet/Utils/API.hs
+++ b/src/Web/Tweet/Utils/API.hs
@@ -4,21 +4,24 @@
 module Web.Tweet.Utils.API (
     getRequest
   , postRequest
+  , getRequestMem
+  , postRequestMem
   , urlString
   , strEncode ) where
 
-import Network.HTTP.Client
-import Network.HTTP.Client.TLS
-import Network.HTTP.Types.Status (statusCode)
+import           Control.Monad              ((<=<))
+import qualified Data.ByteString.Char8      as BS
 import qualified Data.ByteString.Lazy.Char8 as BSL
-import qualified Data.ByteString.Char8 as BS
-import qualified Data.Text as T
-import Web.Tweet.Types
-import Data.Char
-import Data.Maybe
-import Web.Authenticate.OAuth
-import Data.Text.Encoding
-import Web.Tweet.Sign
+import           Data.Char
+import           Data.Maybe
+import qualified Data.Text                  as T
+import           Data.Text.Encoding
+import           Network.HTTP.Client
+import           Network.HTTP.Client.TLS
+import           Network.HTTP.Types.Status  (statusCode)
+import           Web.Authenticate.OAuth
+import           Web.Tweet.Sign
+import           Web.Tweet.Types
 
 -- | Make a GET request to twitter given a request string
 getRequestMem :: String -> Config -> IO BSL.ByteString
@@ -30,11 +33,11 @@
 
 -- | Make a GET request to twitter given a request string
 getRequest :: String -> FilePath -> IO BSL.ByteString
-getRequest = flip ((. getRequestMem) . (>>=) . mkConfig)
+getRequest str = getRequestMem str <=< mkConfigToml
 
 -- | Make a POST request to twitter given a request string
 postRequest :: String -> FilePath -> IO BSL.ByteString
-postRequest = flip ((. postRequestMem) . (>>=) . mkConfig)
+postRequest str = postRequestMem str <=< mkConfigToml
 
 -- | Make a POST request to twitter given a request string
 postRequestMem :: String -> Config -> IO BSL.ByteString
@@ -49,35 +52,27 @@
 responseBS request manager = do
     response <- httpLbs request manager
     let code = statusCode $ responseStatus response
-    putStr $ if (code == 200) then "" else "failed :(\n error code: " ++ (show code) ++ "\n"
+    putStr $ if code == 200 then "" else "failed :(\n error code: " ++ show code ++ "\n"
     pure . responseBody $ response
 
--- | print output of a request and return status id as an `Int`. 
-responseInt :: Request -> Manager -> IO Int
-responseInt request manager = do
-    response <- httpLbs request manager
-    let code = statusCode $ responseStatus response
-    putStrLn $ if (code == 200) then "POST succesful!" else "failed :(\n error code: " ++ (show code)
-    return $ (read . (takeWhile (/=',')) . (drop 52)) (BSL.unpack $ responseBody response) -- TODO use the more general parser
-
 -- | Convert a tweet to a percent-encoded url for querying an API
 urlString :: Tweet -> String
 urlString tweet = concat [ "?status="
                          , BS.unpack (tweetEncode tweet)
                          , "&trim_user="
                          , map toLower (show trim)
-                         , (if isJust (_replyID tweet) then "&in_reply_to_status_id=" else "")
+                         , if isJust (_replyID tweet) then "&in_reply_to_status_id=" else ""
                          , reply ]
     where trim  = False
-          reply = fromMaybe "" (show <$> _replyID tweet)
+          reply = maybe "" show (_replyID tweet)
 
 -- | Percent-encode a string
 strEncode :: String -> String
 strEncode = BS.unpack . paramEncode . encodeUtf8 . T.pack
 
--- | Percent-encode the status update so it's fit for a URL and UTF-encode it as well. 
+-- | Percent-encode the status update so it's fit for a URL.
 tweetEncode :: Tweet -> BS.ByteString
 tweetEncode tweet = paramEncode . encodeUtf8 $ handleStr `T.append` content
     where content   = T.pack . _status $ tweet
-          handleStr = T.pack $ concatMap ((++ " ") . ((++) "@")) hs
+          handleStr = T.pack $ concatMap ((++ " ") . (++) "@") hs
           hs        = _handles tweet
diff --git a/src/Web/Tweet/Utils/Colors.hs b/src/Web/Tweet/Utils/Colors.hs
--- a/src/Web/Tweet/Utils/Colors.hs
+++ b/src/Web/Tweet/Utils/Colors.hs
@@ -1,8 +1,11 @@
 -- | Helper functions to color strings
 module Web.Tweet.Utils.Colors where
 
-import Text.PrettyPrint.ANSI.Leijen
+import           Text.PrettyPrint.ANSI.Leijen
 
+--  😎
+--  😐
+
 -- | Make a string red
 toRed :: String -> String
 toRed = show . dullred . text
@@ -18,3 +21,7 @@
 -- | Make a string blue
 toBlue :: String -> String
 toBlue = show . underline . dullblue . text
+
+-- | Make a string blue; no underlining.
+toPlainBlue :: String -> String
+toPlainBlue = show . dullblue . text
diff --git a/stack.yaml b/stack.yaml
deleted file mode 100644
--- a/stack.yaml
+++ /dev/null
@@ -1,66 +0,0 @@
-# This file was automatically generated by 'stack init'
-#
-# Some commonly used options have been documented as comments in this file.
-# For advanced use and comprehensive documentation of the format, please see:
-# http://docs.haskellstack.org/en/stable/yaml_configuration/
-
-# Resolver to choose a 'specific' stackage snapshot or a compiler version.
-# A snapshot resolver dictates the compiler version and the set of packages
-# to be used for project dependencies. For example:
-#
-# resolver: lts-3.5
-# resolver: nightly-2015-09-21
-# resolver: ghc-7.10.2
-# resolver: ghcjs-0.1.0_ghc-7.10.2
-# resolver:
-#  name: custom-snapshot
-#  location: "./custom-snapshot.yaml"
-resolver: lts-8.17
-
-# User packages to be built.
-# Various formats can be used as shown in the example below.
-#
-# packages:
-# - some-directory
-# - https://example.com/foo/bar/baz-0.0.2.tar.gz
-# - location:
-#    git: https://github.com/commercialhaskell/stack.git
-#    commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a
-# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a
-#   extra-dep: true
-#  subdirs:
-#  - auto-update
-#  - wai
-#
-# A package marked 'extra-dep: true' will only be built if demanded by a
-# non-dependency (i.e. a user package), and its test suites and benchmarks
-# will not be run. This is useful for tweaking upstream packages.
-packages:
-- '.'
-# Dependency packages to be pulled from upstream that are not in the resolver
-# (e.g., acme-missiles-0.3)
-extra-deps: []
-
-# Override default flag values for local packages and extra-deps
-flags: {}
-
-# Extra package databases containing global packages
-extra-package-dbs: []
-
-# Control whether we use the GHC we find on the path
-# system-ghc: true
-#
-# Require a specific version of stack, using version ranges
-# require-stack-version: -any # Default
-# require-stack-version: ">=1.4"
-#
-# Override the architecture used by stack, especially useful on Windows
-# arch: i386
-# arch: x86_64
-#
-# Extra directories used by stack for building
-# extra-include-dirs: [/path/to/dir]
-# extra-lib-dirs: [/path/to/dir]
-#
-# Allow a newer minor version of GHC than the snapshot specifies
-# compiler-check: newer-minor
diff --git a/test/Spec.hs b/test/Spec.hs
deleted file mode 100644
--- a/test/Spec.hs
+++ /dev/null
@@ -1,23 +0,0 @@
-{-# LANGUAGE OverloadedStrings #-}
-
-import Test.Hspec
-import Test.Hspec.Megaparsec
-import Text.Megaparsec
-import Web.Tweet.Parser
-import qualified Data.ByteString as BS
---
-import Web.Tweet.Parser.FastParser
-
--- TODO make sure it's the right number of tweets as well
-main :: IO ()
-main = hspec $ do
-    describe "parseTweet" $ do
-        file <- runIO $ BS.readFile "test/data"
-        parallel $ it "parses sample tweets" $ do
-            parse parseTweet "" `shouldSucceedOn` file
-            {--
-    describe "fastParse" $ do
-        file <- runIO $ BS.readFile "test/data"
-        parallel $ it "parses sample tweets wrong" $ do
-            fastParse "" `shouldBe` Left "some error idk"
-            --}
diff --git a/tweet-hs.cabal b/tweet-hs.cabal
--- a/tweet-hs.cabal
+++ b/tweet-hs.cabal
@@ -1,129 +1,120 @@
-name:                tweet-hs
-version:             0.5.3.13
-synopsis:            Command-line tool for twitter
-description:         a Command Line Interface Tweeter
-homepage:            https://github.com/vmchale/command-line-tweeter#readme
-license:             BSD3
-license-file:        LICENSE
-author:              Vanessa McHale
-maintainer:          tmchale@wisc.edu
-copyright:           2016 Vanessa McHale
-category:            Web
-build-type:          Simple
-stability:           stable
-extra-source-files:  README.md, stack.yaml, bash/mkCompletions, test/data
-cabal-version:       >=1.10
+cabal-version:      >=1.10
+name:               tweet-hs
+version:            1.0.2.3
+license:            BSD3
+license-file:       LICENSE
+copyright:          2016-2020 Vanessa McHale
+maintainer:         vamchale@gmail.com
+author:             Vanessa McHale
+stability:          stable
+synopsis:           Command-line tool for twitter
+description:        a Command Line Interface Tweeter
+category:           Web
+build-type:         Simple
+extra-source-files:
+    README.md
+    cabal.project.local
+    bash/mkCompletions
+    test/data
 
-Flag llvm-fast {
-  Description: Enable build with llvm backend
-  Default: False
-}
+source-repository head
+    type:     git
+    location: https://hub.darcs.net/vmchale/tweet-hs
 
-Flag library {
-  Description: Don't build an executable
-  Default:     False
-}
+flag llvm-fast
+    description: Enable build with llvm backend
+    default:     False
 
-Flag gold {
-  Description: Use the gold linker
-  Default:     True
-}
+flag development
+    description: Enable -Werror
+    default:     False
+    manual:      True
 
-Flag parallel-gc {
-  Description: Use parallel garbage collector
-  Default:     False
-}
+flag parallel-gc
+    description: Use parallel garbage collector
+    default:     False
 
 library
-  hs-source-dirs:      src
-  exposed-modules:     Web.Tweet
-                     , Web.Tweet.Exec
-                     , Web.Tweet.Parser
-                     , Web.Tweet.Parser.FastParser
-  other-modules:       Web.Tweet.Types
-                     , Web.Tweet.Utils
-                     , Web.Tweet.Utils.Colors
-                     , Web.Tweet.Sign
-                     , Web.Tweet.API
-                     , Web.Tweet.Utils.API
-                     , Paths_tweet_hs
-  build-depends:       base >= 4.7 && < 5
-                     , http-client-tls
-                     , http-client
-                     , http-types
-                     , authenticate-oauth
-                     , bytestring
-                     , split
-                     , optparse-applicative 
-                     , lens
-                     , data-default
-                     , text
-                     , megaparsec
-                     , containers
-                     , ansi-wl-pprint
-                     , directory
-                     , extra
-                     , composition
-                     , aeson
-  default-language:    Haskell2010
-  default-extensions:  LambdaCase
-  if flag(gold) 
-    ghc-options:       -optl-fuse-ld=gold
-    ld-options:        -fuse-ld=gold
-  ghc-options:      -fwarn-unused-imports
+    exposed-modules:
+        Web.Tweet
+        Web.Tweet.Parser
+        Web.Tweet.Sign
+        Web.Tweet.API
+        Web.Tweet.Utils
 
+    hs-source-dirs:   src
+    other-modules:
+        Web.Tweet.Types
+        Web.Tweet.Utils.Colors
+        Web.Tweet.API.Internal
+        Web.Tweet.Utils.API
+
+    default-language: Haskell2010
+    ghc-options:
+        -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates
+
+    build-depends:
+        base >=4.11 && <5,
+        http-client-tls,
+        http-client,
+        http-types,
+        authenticate-oauth,
+        megaparsec >=7.0,
+        bytestring,
+        split,
+        microlens,
+        unordered-containers,
+        htoml-megaparsec >=2.1.0.0,
+        text,
+        containers >=0.6.0.0,
+        ansi-wl-pprint,
+        composition-prelude >=3.0.0.0,
+        extra
+
+    if flag(development)
+        ghc-options: -Werror
+
 executable tweet
-  if flag(library)
-    Buildable: False
-  else
-    Buildable: True
-  hs-source-dirs:      app
-  main-is:             Main.hs
-  if flag(llvm-fast)
-    ghc-options:       -fllvm -optlo-O3 -O3
-  if flag(gold) 
-    ghc-options:       -optl-fuse-ld=gold
-    ld-options:        -fuse-ld=gold
-  if flag(parallel-gc)
-    ghc-options:       -rtsopts -with-rtsopts=-N
-  ghc-options:         -threaded
-  build-depends:       base
-                     , tweet-hs 
-  default-language:    Haskell2010
+    main-is:          Main.hs
+    hs-source-dirs:   app
+    other-modules:    Paths_tweet_hs
+    default-language: Haskell2010
+    ghc-options:
+        -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates
 
+    build-depends:
+        base,
+        tweet-hs,
+        optparse-applicative,
+        directory,
+        bytestring
+
+    if flag(llvm-fast)
+        ghc-options: -fllvm -optlo-O3 -O3
+
+    if flag(parallel-gc)
+        ghc-options: -rtsopts -with-rtsopts=-N
+
+    if flag(development)
+        ghc-options: -Werror
+
 benchmark tweeths-bench
-  type:             exitcode-stdio-1.0
-  hs-source-dirs:   bench
-  main-is:          Bench.hs
-  build-depends:    base
-                  , criterion
-                  , tweet-hs
-                  , megaparsec
-                  , bytestring
-  if flag(llvm-fast)
-    ghc-options:       -fllvm -optlo-O3 -O3
-  if flag(gold) 
-    ghc-options:       -optl-fuse-ld=gold
-    ld-options:        -fuse-ld=gold
-  ghc-options:       -threaded -rtsopts -with-rtsopts=-N -O3
-  default-language: Haskell2010
+    type:             exitcode-stdio-1.0
+    main-is:          Bench.hs
+    hs-source-dirs:   bench
+    default-language: Haskell2010
+    ghc-options:
+        -Wall -Wincomplete-uni-patterns -Wincomplete-record-updates
 
-test-suite tweeths-test
-  type:                exitcode-stdio-1.0
-  hs-source-dirs:      test
-  main-is:             Spec.hs
-  build-depends:       base
-                     , tweet-hs
-                     , hspec
-                     , hspec-megaparsec
-                     , megaparsec
-                     , bytestring
-  if flag(gold) 
-    ghc-options:       -optl-fuse-ld=gold
-    ld-options:        -fuse-ld=gold
-  ghc-options:         -threaded -rtsopts -with-rtsopts=-N 
-  default-language:    Haskell2010
+    build-depends:
+        base,
+        criterion,
+        tweet-hs,
+        bytestring,
+        megaparsec
 
-source-repository head
-  type:     git
-  location: https://github.com/vmchale/command-line-tweeter
+    if flag(llvm-fast)
+        ghc-options: -fllvm -optlo-O3 -O3
+
+    if flag(development)
+        ghc-options: -Werror
