aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJacques Comeaux <jacquesrcomeaux@gmail.com>2022-05-27 21:07:43 -0500
committerJacques Comeaux <jacquesrcomeaux@gmail.com>2022-05-27 21:07:43 -0500
commit3d3163a1028e99e5073fd39adc9aee5d76a5e5a2 (patch)
treed1cff9ed00418183d3ed8de412d0fdccd7666eaf /src
Initial Commit
Diffstat (limited to 'src')
-rw-r--r--src/Data/Message.hs78
-rw-r--r--src/Data/Tomato.hs70
-rw-r--r--src/Tomato/.Post.hs.swpbin0 -> 12288 bytes
-rw-r--r--src/Tomato/Post.hs65
-rw-r--r--src/Tomato/Validate.hs16
5 files changed, 229 insertions, 0 deletions
diff --git a/src/Data/Message.hs b/src/Data/Message.hs
new file mode 100644
index 0000000..dfbc20b
--- /dev/null
+++ b/src/Data/Message.hs
@@ -0,0 +1,78 @@
+module Data.Message
+ ( InMessage (..)
+ , GMIUrl (..)
+ , OutMessage (..)
+ ) where
+
+import RIO
+
+import Data.Aeson ((.:), (.=))
+import Network.HTTP.Req
+ ( Url
+ , Scheme (Https)
+ , useHttpsURI
+ , renderUrl
+ )
+
+import qualified Data.Aeson as Ae
+import qualified Data.Aeson.Types as Ae
+import qualified RIO.Vector as V
+import qualified Text.URI as URI
+
+data InMessage = InMessage
+ { hasAttach :: !Bool
+ , groupId :: !Word
+ , isUser :: !Bool
+ , text :: !Text
+ } deriving Show
+
+instance Ae.FromJSON InMessage where
+ parseJSON = Ae.withObject "InMessage" $ \o -> InMessage
+ <$> (o .: "attachments" >>= Ae.withArray "attachments" (pure . not . null))
+ <*> o .: "group-id"
+ <*> ((==) ("user" :: Text) <$> o .: "sender_type")
+ <*> o .: "text"
+
+
+newtype GMIUrl = GMIUrl
+ { unGMIUrl :: Url Https
+ } deriving Show
+
+instance Ae.FromJSON GMIUrl where
+ parseJSON = Ae.withObject "payload" $ \o -> do
+ p <- o .: "payload"
+ u <- p .: "url"
+ GMIUrl <$> toUrl u
+ where
+ toUrl :: Text -> Ae.Parser (Url Https)
+ toUrl t = maybe mzero pure $ fmap fst $ useHttpsURI =<< URI.mkURI t
+
+data OutMessage = OutMessage
+ { image :: !GMIUrl
+ } deriving Show
+
+instance Ae.ToJSON OutMessage where
+ toJSON om = Ae.object
+ [ "attachments" .=
+ ( Ae.Array $ V.singleton attach )
+ -- , "bot_id" .= botId
+ ]
+ where
+ attach :: Ae.Value
+ attach = Ae.object
+ [ "type" .= ("image" :: Text)
+ , "url" .= url
+ ]
+ url :: Text
+ url = renderUrl $ unGMIUrl $ image om
+
+ -- {
+ -- "bot_id" : "j5abcdefg",
+ -- "text" : "Hello world",
+ -- "attachments" : [
+ -- {
+ -- "type" : "image",
+ -- "url" : "https://i.groupme.com/somethingsomething.large"
+ -- }
+ -- ]
+-- }
diff --git a/src/Data/Tomato.hs b/src/Data/Tomato.hs
new file mode 100644
index 0000000..fc9b8b5
--- /dev/null
+++ b/src/Data/Tomato.hs
@@ -0,0 +1,70 @@
+module Data.Tomato
+ ( Links (..)
+ , Tomato (..)
+ , Urls (..)
+ ) where
+
+import RIO
+
+import Data.Aeson ((.:))
+import Network.HTTP.Req (Url, Scheme (Https), useHttpsURI)
+
+import qualified Data.Aeson as Ae
+import qualified Data.Aeson.Types as Ae
+import qualified Text.URI as URI
+
+
+data Tomato = Tomato
+ { id :: !Text
+ , width :: !Word
+ , height :: !Word
+ , color :: !Text
+ , blur_hash :: !Text
+ , description :: !Text
+ , urls :: !Urls
+ , links :: !Links
+ } deriving Show
+
+instance Ae.FromJSON Tomato where
+ parseJSON = Ae.withObject "Tomato" $ \o -> Tomato
+ <$> o .: "id"
+ <*> o .: "width"
+ <*> o .: "height"
+ <*> o .: "color"
+ <*> o .: "blur_hash"
+ <*> o .: "description"
+ <*> o .: "urls"
+ <*> o .: "links"
+
+data Links = Links
+ { self :: !(Url Https)
+ , html :: !(Url Https)
+ , download :: !(Url Https)
+ , download_location :: !(Url Https)
+ } deriving Show
+
+instance Ae.FromJSON Links where
+ parseJSON = Ae.withObject "Links" $ \o -> Links
+ <$> (o .: "self" >>= toUrl)
+ <*> (o .: "html" >>= toUrl)
+ <*> (o .: "download" >>= toUrl)
+ <*> (o .: "download_location" >>= toUrl)
+
+data Urls = Urls
+ { raw :: !(Url Https)
+ , full :: !(Url Https)
+ , regular :: !(Url Https)
+ , small :: !(Url Https)
+ , thumb :: !(Url Https)
+ } deriving Show
+
+instance Ae.FromJSON Urls where
+ parseJSON = Ae.withObject "Urls" $ \o -> Urls
+ <$> (o .: "raw" >>= toUrl)
+ <*> (o .: "full" >>= toUrl)
+ <*> (o .: "regular" >>= toUrl)
+ <*> (o .: "small" >>= toUrl)
+ <*> (o .: "thumb" >>= toUrl)
+
+toUrl :: Text -> Ae.Parser (Url Https)
+toUrl t = maybe mzero pure $ fmap fst $ useHttpsURI =<< URI.mkURI t
diff --git a/src/Tomato/.Post.hs.swp b/src/Tomato/.Post.hs.swp
new file mode 100644
index 0000000..f823f90
--- /dev/null
+++ b/src/Tomato/.Post.hs.swp
Binary files differ
diff --git a/src/Tomato/Post.hs b/src/Tomato/Post.hs
new file mode 100644
index 0000000..1e97782
--- /dev/null
+++ b/src/Tomato/Post.hs
@@ -0,0 +1,65 @@
+module Tomato.Post
+ ( postTomato
+ , uploadTomato
+ , postMessage
+ ) where
+
+import RIO
+
+import Network.HTTP.Req
+ ( jsonResponse
+ , ignoreResponse
+ , req
+ , ReqBodyFile (..)
+ , ReqBodyJson (..)
+ , (/:)
+ , POST (..)
+ , Req
+ , https
+ , header
+ , responseBody
+ )
+
+import Data.Message (GMIUrl (..), OutMessage (..))
+
+import qualified Data.Aeson as Ae
+import qualified Data.Aeson.KeyMap as Ae
+
+-- TODO global config reader monad
+tomatoFile :: FilePath
+tomatoFile = "tomato.png"
+
+accessToken :: ByteString
+accessToken = "nj8X8tB3TC0MoZdMLhIUA4G89r9IlQfVej97Mhg3"
+
+botId :: Text
+botId = "713bcb4a4604006c944804552c"
+
+-- | Post the current tomato
+postTomato :: Req ()
+postTomato = do
+ url <- uploadTomato
+ postMessage $ OutMessage url
+
+-- | Upload the current tomato to the GroupMe image server
+uploadTomato :: Req GMIUrl
+uploadTomato = do
+ let url = https "image.groupme.com" /: "pictures"
+ js <- req POST url (ReqBodyFile tomatoFile) jsonResponse $
+ header "X-Access-Token" accessToken <>
+ header "Content-Type" "image/png"
+ gmURL <- case Ae.fromJSON (responseBody js) of
+ Ae.Success r -> pure r
+ Ae.Error _s -> error "deal with this later" -- TODO error monad
+ return gmURL
+
+-- | Post a message to the group with image as attachment
+postMessage :: OutMessage -> Req ()
+postMessage outMes = do
+ let url = https "api.groupme.com" /: "v3" /: "bots" /: "post"
+ outMes' = case Ae.toJSON outMes of
+ Ae.Object o -> Ae.Object $ Ae.insert "bot_id" (Ae.toJSON botId) o
+ _ -> error "exceptional"
+ req POST url (ReqBodyJson outMes') ignoreResponse $
+ header "X-Access-Token" accessToken
+ return ()
diff --git a/src/Tomato/Validate.hs b/src/Tomato/Validate.hs
new file mode 100644
index 0000000..a3dd328
--- /dev/null
+++ b/src/Tomato/Validate.hs
@@ -0,0 +1,16 @@
+module Tomato.Validate
+ ( isTomato
+ ) where
+
+import RIO
+
+import Data.Message (InMessage (..))
+
+
+isTomato :: InMessage -> Bool
+isTomato m =
+ not (hasAttach m) &&
+ groupId m == 87220147 &&
+ isUser m &&
+ text m == "tomato"
+