packages feed

clit (empty) → 0.1.0.0

raw patch · 6 files changed

+212/−0 lines, 6 filesdep +aesondep +authenticate-oauthdep +basesetup-changed

Dependencies added: aeson, authenticate-oauth, base, bytestring, clit, http-client, http-client-tls, http-types, optparse-applicative, split

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright Vanessa McHale (c) 2016++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions are met:++    * Redistributions of source code must retain the above copyright+      notice, this list of conditions and the following disclaimer.++    * Redistributions in binary form must reproduce the above+      copyright notice, this list of conditions and the following+      disclaimer in the documentation and/or other materials provided+      with the distribution.++    * Neither the name of Vanessa McHale nor the names of other+      contributors may be used to endorse or promote products derived+      from this software without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ app/Main.hs view
@@ -0,0 +1,6 @@+module Main where++import Web.Exec++main :: IO ()+main = exec
+ clit.cabal view
@@ -0,0 +1,40 @@+name:                clit+version:             0.1.0.0+synopsis:            Post tweets that you pipe to stdin+description:         Please see README.md+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+cabal-version:       >=1.10++library+  hs-source-dirs:      src+  exposed-modules:     Web.Tweet+                     , Web.Exec+  build-depends:       base >= 4.7 && < 5+                     , aeson+                     , http-client-tls+                     , http-client+                     , http-types+                     , authenticate-oauth+                     , bytestring+                     , split+                     , optparse-applicative+  default-language:    Haskell2010++executable tweet+  hs-source-dirs:      app+  main-is:             Main.hs+  ghc-options:         -threaded -rtsopts -with-rtsopts=-N -O2+  build-depends:       base+                     , clit+  default-language:    Haskell2010++source-repository head+  type:     git+  location: https://github.com/vmchale/command-line-tweeter
+ src/Web/Exec.hs view
@@ -0,0 +1,46 @@+-- | Provides IO action that parses command line options and tweets from stdin+module Web.Exec (exec) where++import Web.Tweet+import Options.Applicative+import qualified Data.ByteString.Char8 as BS+import Data.List.Split (chunksOf)++-- | Executes parser+exec :: IO ()+exec = execParser opts >>= select+    where +        opts = info (helper <*> program)+            (fullDesc+            <> progDesc "Tweet from stdin!"+            <> header "clit - a Command Line Interface Tweeter")++-- | query twitter to post stdin+fromStdIn :: Int -> FilePath -> IO ()+fromStdIn num filepath = do+    content <- fmap ((take num) . (map BS.pack) .  (chunksOf 140)) getContents+    sequence_ $ fmap (tweet filepath) content++-- | Data type for our program; just one optional argument for path to credential file.+data Program = Program { cred :: Maybe FilePath , tweets :: Maybe String }++-- | Executes program+select :: Program -> IO ()+select (Program Nothing (Just n)) = fromStdIn (read n) ".cred"+select (Program Nothing Nothing) = fromStdIn 4 ".cred"+select (Program (Just file) (Just n)) = fromStdIn (read n) file+select (Program (Just file) Nothing) = fromStdIn 4 file++-- | Parser to return a program datatype+program :: Parser Program+program = Program+    <$> (optional $ strOption+        (long "cred"+        <> short 'c'+        <> metavar "CREDENTIALS"+        <> help "path to credentials"))+    <*> (optional $ strOption+        (long "tweets"+        <> short 't'+        <> metavar "NUM"+        <> help "Number of tweets in a row, default 4"))
+ src/Web/Tweet.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE DeriveGeneric     #-}++-- | Various utilities to tweet using the twitter api+-- +-- Make sure you have a file credentials file (default `.cred`) with the following info in the correct order:+--+-- api-key: API_KEY+--+-- api-sec: API_SECRE+--+-- tok: OAUTH_TOKEN+--+-- tok-sec: TOKEN_SECRET++module Web.Tweet+    ( tweet+    , signRequest+    , urlString+    ) where++import Data.Aeson+import GHC.Generics+import Network.HTTP.Client+import Network.HTTP.Client.TLS+import Network.HTTP.Types.Status (statusCode)+import Web.Authenticate.OAuth+import qualified Data.ByteString.Char8 as BS+import qualified Data.ByteString.Lazy.Char8 as BSL+import Data.Char (toLower)++-- | Data type for our request+data Tweet = Tweet+    { status    :: String+    , trim_user :: Bool+    } deriving Generic++instance ToJSON Tweet where++-- | tweet a byteString, with credentials from a given file+tweet :: FilePath -> BS.ByteString -> IO ()+tweet filepath content = do+    requestString <- urlString content+    manager <- newManager tlsManagerSettings+    initialRequest <- parseRequest ("https://api.twitter.com/1.1/statuses/update.json" ++ requestString)+    request <- signRequest filepath $ initialRequest { method = "POST" }+    response request manager++-- | print output of a request+response :: Request -> Manager -> IO ()+response request manager = do+    response <- httpLbs request manager+    putStrLn $ "The status code was: " ++ show (statusCode $ responseStatus response)+    BSL.putStrLn $ responseBody response++-- | Sign a request using your OAuth dev token.+-- Uses the IO monad because signatures require a timestamp+signRequest :: FilePath -> Request -> IO Request+signRequest filepath req = do+    o <- oAuth filepath+    c <- credential filepath+    signOAuth o c req++-- | Create an OAuth token+oAuth :: FilePath -> IO OAuth+oAuth filepath = do+    secret <- (flip (!!) 1) <$> getConfigData filepath+    key <- (flip (!!) 0) <$> getConfigData filepath+    let url = "api.twitter.com"+    return newOAuth { oauthConsumerKey = key , oauthConsumerSecret = secret , oauthServerName = url }++-- | Create a new credential from a token and secret component of that token+credential :: FilePath -> IO Credential+credential filepath = newCredential <$> token <*> secretToken+    where token       = (flip (!!) 2) <$> getConfigData filepath+          secretToken = (flip (!!) 3) <$> getConfigData filepath++getConfigData :: FilePath -> IO [BS.ByteString]+getConfigData filepath = ((map (BS.pack . filterLine)) . lines) <$> readFile filepath++-- | Filter a line of a file for only the actual data and no descriptors+filterLine :: String -> String+filterLine = reverse . (takeWhile (not . (`elem` (" :" :: String)))) . reverse++-- | Convert a byteString to the percent-encoded version+urlString :: BS.ByteString -> IO String+urlString content = do+    return $ "?status=" ++ ((BS.unpack . paramEncode) content) ++ "&trim_user" ++ (map toLower $ show True)