Initial commit
This commit is contained in:
commit
dc2920aa8f
20
LICENSE
Normal file
20
LICENSE
Normal file
@ -0,0 +1,20 @@
|
||||
Copyright (c) 2015 rnhmjoj
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included
|
||||
in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
95
Main.hs
Executable file
95
Main.hs
Executable file
@ -0,0 +1,95 @@
|
||||
{-# LANGUAGE OverloadedStrings, RecordWildCards, ScopedTypeVariables #-}
|
||||
|
||||
import Control.Monad (when, forM_)
|
||||
import Control.Exception (try)
|
||||
import Data.Text (Text, unpack)
|
||||
import qualified Data.Text.IO as T
|
||||
import Data.Monoid
|
||||
import Data.ByteString.Lazy (hPut)
|
||||
import System.IO
|
||||
import System.IO.Temp (withSystemTempFile)
|
||||
import System.Environment (getEnv, lookupEnv)
|
||||
import Network.HTTP.Conduit (HttpException, simpleHttp)
|
||||
|
||||
import Telegram
|
||||
import Parser
|
||||
|
||||
url :: String
|
||||
url = "http://oglaf.com"
|
||||
|
||||
|
||||
commands :: String -> Comic -> [Command]
|
||||
commands path Comic{..} =
|
||||
[ if page == 1
|
||||
then message user title
|
||||
else nothing
|
||||
, sendFile user path
|
||||
, message user descr ]
|
||||
|
||||
|
||||
cache :: IO String
|
||||
cache = do
|
||||
path <- lookupEnv "XDG_CACHE_HOME"
|
||||
case path of
|
||||
Just x -> return (x ++ "/oglaf")
|
||||
Nothing -> (++ "/.cache/oglaf") <$> getEnv "HOME"
|
||||
|
||||
|
||||
lastComic :: IO Text
|
||||
lastComic = do
|
||||
res <- try (T.readFile =<< cache)
|
||||
case res of
|
||||
Left (err :: IOError) -> do
|
||||
putStrLn ("Can't read saved state " ++ show err)
|
||||
putStrLn "Send anyway"
|
||||
return ""
|
||||
Right title -> return title
|
||||
|
||||
|
||||
setLastComic :: Text -> IO ()
|
||||
setLastComic title = cache >>= flip T.writeFile title
|
||||
|
||||
|
||||
findPages :: Text -> IO [Comic]
|
||||
findPages = findPages' 1 where
|
||||
findPages' n link = do
|
||||
res <- try $ simpleHttp (url <> page)
|
||||
case res of
|
||||
Left (_ :: HttpException) -> return []
|
||||
Right cur -> (parseComic cur n :) <$> findPages' (succ n) link
|
||||
where
|
||||
page = unpack link <> show n
|
||||
|
||||
|
||||
main :: IO ()
|
||||
main = do
|
||||
comics <- parseArchive <$> simpleHttp (url <> "/archive")
|
||||
when (null comics) (fail "error parsing comics archive")
|
||||
|
||||
let latest = head comics
|
||||
last <- lastComic
|
||||
|
||||
if latest == last
|
||||
then T.putStrLn ("Already sent " <> last <> ", stopping")
|
||||
else do
|
||||
pages <- findPages latest
|
||||
process <- telegramProcess
|
||||
socket <- telegramSocket
|
||||
|
||||
send socket connect
|
||||
|
||||
forM_ pages $ \comic ->
|
||||
withSystemTempFile "oglaf.png" $ \path temp -> do
|
||||
simpleHttp (imgUrl comic) >>= hPut temp
|
||||
hClose temp
|
||||
result <- try $ mapM (send socket) (commands path comic)
|
||||
|
||||
case result of
|
||||
Left (err :: IOError) -> do
|
||||
putStrLn "failed" >> print err
|
||||
fail "Could not send the comic :("
|
||||
Right _ -> do
|
||||
setLastComic latest
|
||||
putStrLn ("Sent " <> show (page comic))
|
||||
|
||||
closeTelegram socket process
|
54
Parser.hs
Normal file
54
Parser.hs
Normal file
@ -0,0 +1,54 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
|
||||
module Parser where
|
||||
|
||||
import Data.Monoid
|
||||
import Data.ByteString.Lazy (ByteString)
|
||||
import qualified Data.Text as T
|
||||
import Data.Text (Text, unpack)
|
||||
import Text.XML.Cursor
|
||||
import Text.HTML.DOM (parseLBS)
|
||||
import Text.HTML.TagSoup.Entity (lookupEntity)
|
||||
|
||||
data Comic = Comic
|
||||
{ title :: String
|
||||
, descr :: String
|
||||
, imgUrl :: String
|
||||
, page :: Int
|
||||
}
|
||||
|
||||
|
||||
parseArchive :: ByteString -> [Text]
|
||||
parseArchive str = mkCursor str $// selectLinks &| extractUrl
|
||||
where
|
||||
selectLinks = element "a" &/ element "img" >=> parent
|
||||
extractUrl = T.concat . attribute "href"
|
||||
|
||||
|
||||
parseComic :: ByteString -> Int -> Comic
|
||||
parseComic str n = Comic
|
||||
{ title = decodeHtml $ unpack $ head $ cur $// selectTitle &| extractContent
|
||||
, descr = decodeHtml $ unpack $ head $ cur $// selectImg &| extractDescr
|
||||
, imgUrl = unpack $ head $ cur $// selectImg &| extractUrl
|
||||
, page = n
|
||||
} where
|
||||
selectTitle = element "title" >=> child
|
||||
selectImg = element "img" >=> attributeIs "id" "strip"
|
||||
extractUrl = T.concat . attribute "src"
|
||||
extractDescr = T.concat . attribute "title"
|
||||
extractContent = T.concat . content
|
||||
cur = mkCursor str
|
||||
|
||||
|
||||
mkCursor :: ByteString -> Cursor
|
||||
mkCursor = fromDocument . parseLBS
|
||||
|
||||
|
||||
decodeHtml :: String -> String
|
||||
decodeHtml [] = []
|
||||
decodeHtml ('&' : xs) =
|
||||
let (b, a) = break (== ';') xs in
|
||||
case (lookupEntity b, a) of
|
||||
(Just c, ';' : as) -> c <> decodeHtml as
|
||||
_ -> '&' : decodeHtml xs
|
||||
decodeHtml (x : xs) = x : decodeHtml xs
|
76
Telegram.hs
Normal file
76
Telegram.hs
Normal file
@ -0,0 +1,76 @@
|
||||
module Telegram where
|
||||
|
||||
import Network
|
||||
import System.IO
|
||||
import System.Process
|
||||
import System.Process.Internals
|
||||
import System.Posix.Signals (signalProcess, sigKILL)
|
||||
import Data.List.Utils (replace)
|
||||
import Control.Monad (when, replicateM_)
|
||||
import Control.Retry
|
||||
|
||||
-- Parameters
|
||||
user = "Oglaf"
|
||||
program = "telegram-cli"
|
||||
args = ["-d", "-P", show port]
|
||||
(host, port) = ("localhost", 2391)
|
||||
|
||||
|
||||
telegramProcess :: IO ProcessHandle
|
||||
telegramProcess = do
|
||||
(_,_,_, handle) <- createProcess (proc program args)
|
||||
return handle
|
||||
|
||||
|
||||
telegramSocket :: IO Handle
|
||||
telegramSocket = recoverAll timeout connect
|
||||
where connect = connectTo host (PortNumber port)
|
||||
timeout = fibonacciBackoff 100000 <> limitRetries 5
|
||||
|
||||
|
||||
closeTelegram :: Handle -> ProcessHandle -> IO ()
|
||||
closeTelegram socket handle =
|
||||
hClose socket >> getPid handle >>= signalProcess sigKILL
|
||||
where
|
||||
go (OpenHandle h) = h
|
||||
getPid h = withProcessHandle h (return . go)
|
||||
|
||||
|
||||
send :: Handle -> String -> IO ()
|
||||
send handle command = do
|
||||
hPutStrLn handle command
|
||||
putStr (command ++ " -> ")
|
||||
|
||||
-- wait for a reply
|
||||
reply <- hWaitForInput handle 10000
|
||||
when (not reply) (fail "No reply from telegram")
|
||||
|
||||
-- read the whole response
|
||||
size <- read . last . words <$> hGetLine handle
|
||||
replicateM_ (size+1) (hGetChar handle)
|
||||
putStrLn "ok"
|
||||
|
||||
|
||||
-- Commands
|
||||
type Name = String
|
||||
type Arg = String
|
||||
type Command = String
|
||||
|
||||
|
||||
mkCommand :: Name -> Arg -> Arg -> Command
|
||||
mkCommand name a b =
|
||||
name ++ " " ++ escape a ++ " '" ++ escape b ++ "'"
|
||||
where escape = replace "\'" "\\\'"
|
||||
|
||||
|
||||
connect :: Command
|
||||
connect = "dialog_list"
|
||||
|
||||
sendFile :: Arg -> Arg -> Command
|
||||
sendFile = mkCommand "send_file"
|
||||
|
||||
message :: Arg -> Arg -> Command
|
||||
message = mkCommand "msg"
|
||||
|
||||
nothing :: Command
|
||||
nothing = "help"
|
20
oglaf.cabal
Normal file
20
oglaf.cabal
Normal file
@ -0,0 +1,20 @@
|
||||
name: oglaf
|
||||
version: 0.1.0.0
|
||||
synopsis: Script that fetches the latest oglaf and sends it over to me via telegram
|
||||
license: MIT
|
||||
license-file: LICENSE
|
||||
author: rnhmjoj
|
||||
maintainer: micheleguerinirocco@me.com
|
||||
copyright: copyright (C) Michele Guerini Rocco
|
||||
category: Network
|
||||
build-type: Simple
|
||||
cabal-version: >=1.10
|
||||
|
||||
executable oglaf
|
||||
main-is: Main.hs
|
||||
build-depends: base >=4.8 && <4.9, network,
|
||||
bytestring, text, retry, tagsoup,
|
||||
MissingH, process, unix, temporary,
|
||||
http-conduit, xml-conduit, html-conduit
|
||||
|
||||
default-language: Haskell2010
|
Loading…
Reference in New Issue
Block a user