├── LICENSE ├── Setup.hs ├── .gitignore ├── etc └── jpgto.conf ├── stack.yaml ├── test.py ├── jpgtobot.cabal ├── README.md └── Main.hs /LICENSE: -------------------------------------------------------------------------------- 1 | Public domain 2 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .cabal-sandbox/ 2 | cabal.sandbox.config 3 | dist/ 4 | token 5 | start 6 | -------------------------------------------------------------------------------- /etc/jpgto.conf: -------------------------------------------------------------------------------- 1 | description 'jpgtobot' 2 | 3 | start on startup 4 | stop on shutdown 5 | 6 | respawn 7 | respawn limit 2 10 8 | 9 | script 10 | chdir /data/jpgtobot 11 | exec sudo /usr/bin/cabal run 12 | end script 13 | -------------------------------------------------------------------------------- /stack.yaml: -------------------------------------------------------------------------------- 1 | flags: {} 2 | packages: 3 | - '.' 4 | - location: 5 | git: git@github.com:hlian/linklater 6 | commit: d4d2f2863840bd3a6a514f798d5e1fcaab273c8a 7 | - location: 8 | git: git@github.com:hlian/jpg-cli 9 | commit: bbf4 10 | extra-deps: [] 11 | resolver: nightly-2015-07-10 12 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import subprocess 4 | import sys 5 | import urllib 6 | 7 | def curl(s): 8 | subprocess.call(['curl', s]) 9 | 10 | _, port, user, text = map(lambda s: urllib.quote(s.decode('utf8').encode('utf8')), sys.argv) 11 | curl('http://localhost:%s/?channel_name=slacktest&user_id=U2147483697&user_name=%s&command=/jpgto&text=%s' % (port, user, text)) 12 | -------------------------------------------------------------------------------- /jpgtobot.cabal: -------------------------------------------------------------------------------- 1 | -- Initial jpgtobot.cabal generated by cabal init. For further 2 | -- documentation, see http://haskell.org/cabal/users-guide/ 3 | 4 | name: jpgtobot 5 | version: 0.1.0.0 6 | license-file: LICENSE 7 | author: Hao Lian 8 | build-type: Simple 9 | cabal-version: >=1.10 10 | 11 | executable jpgtobot 12 | main-is: Main.hs 13 | build-depends: HTTP 14 | , aeson 15 | , attoparsec 16 | , base 17 | , base-prelude 18 | , bytestring 19 | , jpg-to 20 | , lens 21 | , linklater 22 | , text 23 | , transformers 24 | , utf8-string 25 | , warp 26 | , word8 27 | , wreq 28 | default-language: Haskell2010 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **For centuries, people have wondered**: "What's the fastest way to get a JPEG into a Slack channel?" Scientists now believe that **jpgtobot** is the answer. To verify these findings, please 2 | 3 | ## Up and running 4 | 5 | * Install Haskell ([Windows](http://www.haskell.org/platform/), [Mac](http://ghcformacosx.github.io/), and [Linux](https://gist.githubusercontent.com/hlian/b5a975252997cb3e0020/raw/e4ecab3042225d321a88ee74e804c38ead38ed52/gistfile1.txt)); 6 | * Install [Stack](https://github.com/commercialhaskell/stack); 7 | * `git clone https://github.com/hlian/jpegbot/ && cd jpegbot && stack build`; 8 | * Create a Slack incoming token and save it to a file named `hook`; 9 | * Create a Google Custom Search API key and save it to a file named `google-server-key` ([see this](https://github.com/dpatti/jpg-to/blob/master/README.md)); 10 | * Create a Google Search Engine and save the ID to a file named `google-search-engine-id` ([see this again](https://github.com/dpatti/jpg-to/blob/master/README.md)); 11 | * `stack exec jpgtobot`. 12 | 13 | ## And talking to Slack 14 | 15 | * Use [ngrok](https://ngrok.com/) to open a tunnel to port 3000 (`ngrok 3000`); 16 | * Copy down the ngrok URL; 17 | * Create a Slack custom command `/jpeg` POSTing to that URL; 18 | * In Slack, type `/jpeg corgi`. 19 | 20 | If all goes well, you'll see something like this: 21 | 22 | ![/jpgto Corgi](https://raw.githubusercontent.com/hlian/linklater/master/corgi.jpg) 23 | 24 | ## What is this, really? 25 | 26 | A demo app for [`Network.Linklater`](https://github.com/hlian/linklater), a library I wrote! It lets you write Slack bots very easily. Please check it out, whenever you have free time. 27 | -------------------------------------------------------------------------------- /Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE OverloadedStrings #-} 2 | {-# LANGUAGE NoImplicitPrelude #-} 3 | 4 | -- If writing Slack bots intrigues you, check out: https://github.com/hlian/linklater 5 | 6 | import qualified Data.Text as T 7 | import qualified Network.Images.Search as Search 8 | 9 | import Control.Monad.IO.Class (liftIO) 10 | import Control.Monad.Trans.Maybe (MaybeT, runMaybeT) 11 | import Data.Aeson (encode) 12 | import Data.Text (Text) 13 | import Network.Wai.Handler.Warp (run) 14 | 15 | -- Naked imports. 16 | import BasePrelude hiding (words, intercalate) 17 | import Network.Linklater 18 | 19 | cleverlyReadFile :: FilePath -> IO Text 20 | cleverlyReadFile filename = 21 | T.filter (/= '\n') . T.pack <$> readFile filename 22 | 23 | configIO :: IO Config 24 | configIO = 25 | Config <$> (cleverlyReadFile "hook") 26 | 27 | googleConfigIO :: IO Search.Gapi 28 | googleConfigIO = 29 | Search.config <$> (cleverlyReadFile "google-server-key") <*> (cleverlyReadFile "google-search-engine-id") 30 | 31 | parseText :: Text -> Maybe Text 32 | parseText text = case T.strip text of 33 | "" -> Nothing 34 | x -> Just x 35 | 36 | liftMaybe :: Maybe a -> MaybeT IO a 37 | liftMaybe = maybe mzero return 38 | 39 | messageOfCommand :: Command -> MaybeT IO Message 40 | 41 | messageOfCommand (Command "jpeg" user channel (Just text)) = do 42 | gapi <- liftIO googleConfigIO 43 | query <- liftMaybe (parseText text) 44 | urls <- liftIO (Search.linksOfQuery gapi query) 45 | url <- liftMaybe (listToMaybe urls) 46 | return (messageOf [FormatAt user, FormatLink url url]) 47 | where 48 | messageOf = 49 | FormattedMessage (EmojiIcon "gift") "jpgtobot" channel 50 | messageOfCommand _ = 51 | mzero 52 | 53 | jpgto :: Maybe Command -> IO Text 54 | jpgto Nothing = 55 | return "Unrecognized Slack request!" 56 | 57 | jpgto (Just command) = do 58 | putStrLn ("+ Incoming command: " <> show command) 59 | config <- configIO 60 | message <- (runMaybeT . messageOfCommand) command 61 | putStrLn ("+ Outgoing messsage: " <> show (encode <$> message)) 62 | case (debug, message) of 63 | (False, Just m) -> do 64 | _ <- say m config 65 | return "" 66 | (False, Nothing) -> 67 | return "*FRIZZLE* ERROR PROCESSING INPUT; BEGIN SELF-DETONATION; PLEASE FILE ISSUE AT " 68 | _ -> 69 | return "" 70 | where 71 | debug = False 72 | 73 | main :: IO () 74 | main = do 75 | putStrLn ("+ Listening on port " <> show port) 76 | run port (slashSimple jpgto) 77 | where 78 | port = 3333 79 | --------------------------------------------------------------------------------