version 3.3:

* added reCaptcha again (got too much spam)
This commit is contained in:
Vincent Ambo 2012-04-04 02:20:56 +02:00
parent 5b80f528c7
commit 3e16a443e6
5 changed files with 59 additions and 15 deletions

View file

@ -118,6 +118,15 @@ input, textarea, select {
margin-left: 15px; margin-left: 15px;
} }
.cCaptcha {
padding: 5px;
border: 1px solid #555;
-webkit-border-radius: 0.5em;
margin-left: 15px;
width: 555px;
background: #F9F9F9;
}
.tt { .tt {
font-family: "courier new",courier,monospace; font-family: "courier new",courier,monospace;
font-size: 13px; font-size: 13px;

View file

@ -1 +1 @@
@charset UTF-8;@import url(http://fonts.googleapis.com/css?family=Droid+Sans+Mono);@import url(http://fonts.googleapis.com/css?family=PT+Sans);html,body{margin:0;padding:0;}body{font-family:'PT Sans', sans-serif;min-height:850px;color:#EEE;}a{color:#000;}input,textarea,select{border:1px solid #555;font-size:15px;line-height:1.2em;width:550px;background:#F9F9F9;-webkit-border-radius:.5em;padding:.5em;}.header{background:url(/static/hbg.jpg);z-index:4;padding-left:20px;padding-bottom:70px;padding-top:30px;position:relative;box-shadow:0 6px 5px 1px #343537;}.link{color:#EEE;}.middle{position:relative;z-index:2;display:block;width:100%;padding-top:40px;background:url(/static/bg.gif);color:#000;}.footer{background:url(/static/hbg.jpg);z-index:4;position:relative;background-color:#4A525A;margin-top:30px;padding-top:20px;box-shadow:0 -6px 5px 1px #343537;color:#EEE;}.btitle{text-decoration:none;color:#EEE;font-size:x-large;font-weight:700;margin-top:15px;margin-bottom:10px;}.contacts{float:left;font-weight:bolder;}.righttext{float:right;padding-right:20px;}.rightbox{text-align:right;padding-right:14px;}.innerTitle{margin-left:10px;font-weight:700;}.innerBoxComments{margin-left:10px;}.innerContainer{padding-right:20px;}.centerbox{text-align:center;min-height:45px;}.cInput{margin-left:15px;}.tt{font-family:"courier new",courier,monospace;font-size:13px;}.cl{text-decoration:none;color:#000;}.cHead{font-size:large;font-weight:700;}.notFoundFace{text-align:center;font-size:100px;}.notFoundText{text-align:center;font-size:24px;font-weight:700;}.code{box-shadow:3px 3px 5px 1px #888;border-radius:10px;font-size:11pt;width:60em;color:#FFF;line-height:1.2em;font-family:'Droid Sans Mono', sans-serif;background:#000;background-image:url(/static/cbg.jpg);background-repeat:no-repeat;padding:.75em;}.code pre{font-family:'Droid Sans Mono', sans-serif;}kbd{font-family:'Droid Sans Mono', sans-serif;color:#333;font-size:.8em;}.wide{width:90em;}code{line-height:1.5em;border:1px;}.source-code{font-size:.75em;color:#666;}.warning{color:red;}.hs-keyword{color:#87CEEB;}.hs-comment,.hs-comment a{color:#5F9EA0;}.hs-str{color:#FF8C00;}.hs-chr{color:#BC8F8F;}.hs-conid{color:#ADFF2F;}.hs-sel{color:#B22222;}.hs-cpp{color:#FF0;}.hs-definition{color:#FFD700;}.hs-keyglyph,.hs-varop,.hs-conop{color:#B8860B;}.hs-layout,.hs-varid,.hs-num{color:#FFF;} @charset UTF-8;@import url(http://fonts.googleapis.com/css?family=Droid+Sans+Mono);@import url(http://fonts.googleapis.com/css?family=PT+Sans);html,body{margin:0;padding:0;}body{font-family:'PT Sans', sans-serif;min-height:850px;color:#EEE;}a{color:#000;}input,textarea,select{border:1px solid #555;font-size:15px;line-height:1.2em;width:550px;background:#F9F9F9;-webkit-border-radius:.5em;padding:.5em;}.header{background:url(/static/hbg.jpg);z-index:4;padding-left:20px;padding-bottom:70px;padding-top:30px;position:relative;box-shadow:0 6px 5px 1px #343537;}.link{color:#EEE;}.middle{position:relative;z-index:2;display:block;width:100%;padding-top:40px;background:url(/static/bg.gif);color:#000;}.footer{background:url(/static/hbg.jpg);z-index:4;position:relative;background-color:#4A525A;margin-top:30px;padding-top:20px;box-shadow:0 -6px 5px 1px #343537;color:#EEE;}.btitle{text-decoration:none;color:#EEE;font-size:x-large;font-weight:700;margin-top:15px;margin-bottom:10px;}.contacts{float:left;font-weight:bolder;}.righttext{float:right;padding-right:20px;}.rightbox{text-align:right;padding-right:14px;}.innerTitle{margin-left:10px;font-weight:700;}.innerBoxComments{margin-left:10px;}.innerContainer{padding-right:20px;}.centerbox{text-align:center;min-height:45px;}.cInput{margin-left:15px;}.cCaptcha{border:1px solid #555;-webkit-border-radius:.5em;margin-left:15px;width:555px;background:#F9F9F9;padding:5px;}.tt{font-family:"courier new",courier,monospace;font-size:13px;}.cl{text-decoration:none;color:#000;}.cHead{font-size:large;font-weight:700;}.notFoundFace{text-align:center;font-size:100px;}.notFoundText{text-align:center;font-size:24px;font-weight:700;}.code{box-shadow:3px 3px 5px 1px #888;border-radius:10px;font-size:11pt;width:60em;color:#FFF;line-height:1.2em;font-family:'Droid Sans Mono', sans-serif;background:#000;background-image:url(/static/cbg.jpg);background-repeat:no-repeat;padding:.75em;}.code pre{font-family:'Droid Sans Mono', sans-serif;}kbd{font-family:'Droid Sans Mono', sans-serif;color:#333;font-size:.8em;}.wide{width:90em;}code{line-height:1.5em;border:1px;}.source-code{font-size:.75em;color:#666;}.warning{color:red;}.hs-keyword{color:#87CEEB;}.hs-comment,.hs-comment a{color:#5F9EA0;}.hs-str{color:#FF8C00;}.hs-chr{color:#BC8F8F;}.hs-conid{color:#ADFF2F;}.hs-sel{color:#B22222;}.hs-cpp{color:#FF0;}.hs-definition{color:#FFD700;}.hs-keyglyph,.hs-varop,.hs-conop{color:#B8860B;}.hs-layout,.hs-varid,.hs-num{color:#FFF;}

View file

@ -9,12 +9,14 @@ import Data.Monoid (mempty)
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Time import Data.Time
import Network.Captcha.ReCaptcha
import System.Locale (defaultTimeLocale) import System.Locale (defaultTimeLocale)
import Text.Blaze (toValue, preEscapedText) import Text.Blaze (toValue, preEscapedText, preEscapedString)
import Text.Blaze.Html5 (Html, (!), a, form, input, p, toHtml, label) import Text.Blaze.Html5 (Html, (!), a, form, input, p, toHtml, label)
import Text.Blaze.Html5.Attributes (action, enctype, href, name, size, type_, value) import Text.Blaze.Html5.Attributes (action, enctype, href, name, size, type_, value)
import qualified Text.Blaze.Html5 as H import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as A import qualified Text.Blaze.Html5.Attributes as A
import Text.XHtml.Strict (showHtmlFragment)
import Locales import Locales
import BlogDB import BlogDB
@ -26,6 +28,21 @@ intersperse' sep l = sep : intersperse sep l
replace :: Eq a => a -> a -> [a] -> [a] replace :: Eq a => a -> a -> [a] -> [a]
replace x y = map (\z -> if z == x then y else z) replace x y = map (\z -> if z == x then y else z)
-- javascript and others
captcha :: Html
captcha = H.div ! A.class_ "cCaptcha" $
do H.script ! A.src "http://api.recaptcha.net/challenge?k=6LfQXccSAAAAAIjKm26XlFnBMAgvaKlOAjVWEEnM" ! A.type_ "text/javascript" $ ""
H.noscript $ H.iframe ! A.src "http://api.recaptcha.net/noscript?k=6LfQXccSAAAAAIjKm26XlFnBMAgvaKlOAjVWEEnM" ! A.height "300" !
A.width "500" ! A.seamless "" $ do
H.br
H.textarea ! A.name "recaptcha_challenge_field" ! A.rows "3" ! A.cols "40" $ ""
H.input ! A.type_ "hidden" ! A.name "recaptcha_response_field" ! A.value "manual_challenge"
captchaOptions :: BlogLang -> Html
captchaOptions lang = H.script ! A.type_ "text/javascript" $ toHtml $
T.concat ["var RecaptchaOptions = { theme: 'clean', lang: '", showLangText lang, "'};"]
analytics :: Text analytics :: Text
analytics = T.pack $ unlines ["<script type=\"text/javascript\">" analytics = T.pack $ unlines ["<script type=\"text/javascript\">"
," var _gaq = _gaq || [];" ," var _gaq = _gaq || [];"
@ -38,12 +55,14 @@ analytics = T.pack $ unlines ["<script type=\"text/javascript\">"
," })();" ," })();"
,"</script>"] ,"</script>"]
-- blog HTML
blogTemplate :: BlogLang -> Text -> Html -> Html blogTemplate :: BlogLang -> Text -> Html -> Html
blogTemplate lang t_append body = H.docTypeHtml $ do --add body blogTemplate lang t_append body = H.docTypeHtml $ do --add body
H.head $ do H.head $ do
H.title $ (toHtml $ blogTitle lang t_append) H.title $ (toHtml $ blogTitle lang t_append)
H.link ! A.rel "alternate" ! A.type_ "application/rss+xml" ! A.title "RSS-Feed" ! A.href (toValue feedURL) H.link ! A.rel "alternate" ! A.type_ "application/rss+xml" ! A.title "RSS-Feed" ! A.href (toValue feedURL)
H.link ! A.rel "stylesheet" ! A.type_ "text/css" ! A.href "/static/blogv312.css" ! A.media "all" H.link ! A.rel "stylesheet" ! A.type_ "text/css" ! A.href "/static/blogv33.css" ! A.media "all"
--H.link ! A.rel "stylesheet" ! A.type_ "text/css" ! A.href "/res/blogstyle.css" ! A.media "all" --H.link ! A.rel "stylesheet" ! A.type_ "text/css" ! A.href "/res/blogstyle.css" ! A.media "all"
H.meta ! A.httpEquiv "content-type" ! A.content "text/html;charset=UTF-8" H.meta ! A.httpEquiv "content-type" ! A.content "text/html;charset=UTF-8"
--H.style ! A.type_ "text/css" ! A.title "iOS iMessage" ! A.media "screen and (max-device-width: 1024px)" $ "#cosx{display:none;}" --H.style ! A.type_ "text/css" ! A.title "iOS iMessage" ! A.media "screen and (max-device-width: 1024px)" $ "#cosx{display:none;}"
@ -116,10 +135,12 @@ renderEntry (Entry{..}) = do
renderCommentBox :: BlogLang -> EntryId -> Html renderCommentBox :: BlogLang -> EntryId -> Html
renderCommentBox cLang cId = do renderCommentBox cLang cId = do
H.div ! A.class_ "cHead" $ toHtml $ cwHead cLang H.div ! A.class_ "cHead" $ toHtml $ cwHead cLang
captchaOptions cLang
H.form ! A.method "POST" ! A.action (toValue $ "/" ++ (show cLang) ++ "/postcomment/" ++ show cId) $ do H.form ! A.method "POST" ! A.action (toValue $ "/" ++ (show cLang) ++ "/postcomment/" ++ show cId) $ do
H.p $ H.input ! A.name "cname" ! A.placeholder "Name" ! A.class_ "cInput" H.p $ H.input ! A.name "cname" ! A.placeholder "Name" ! A.class_ "cInput"
H.p $ H.label $ H.textarea ! A.name "ctext" ! A.cols "50" ! A.rows "13" ! A.class_ "cInput" ! H.p $ H.label $ H.textarea ! A.name "ctext" ! A.cols "50" ! A.rows "13" ! A.class_ "cInput" !
A.placeholder (toValue $ cTextPlaceholder cLang) $ mempty A.placeholder (toValue $ cTextPlaceholder cLang) $ mempty
H.p $ H.label $ captcha
H.p $ H.input ! A.class_ "cInput" ! A.style "width: 120px;" ! A.type_ "submit" ! A.value (toValue $ cSend cLang) H.p $ H.input ! A.class_ "cInput" ! A.style "width: 120px;" ! A.type_ "submit" ! A.value (toValue $ cSend cLang)
renderComments :: [Comment] -> BlogLang -> Html renderComments :: [Comment] -> BlogLang -> Html

View file

@ -16,7 +16,7 @@ import BlogDB (BlogLang (..))
data BlogError = NotFound | DBError data BlogError = NotFound | DBError
version = "3.2" version = "3.3"
allLang = [EN, DE] allLang = [EN, DE]
@ -28,6 +28,10 @@ blogTitle :: BlogLang -> Text -> Text
blogTitle DE s = T.concat ["Tazjins Blog", s] blogTitle DE s = T.concat ["Tazjins Blog", s]
blogTitle EN s = T.concat ["Tazjin's Blog", s] blogTitle EN s = T.concat ["Tazjin's Blog", s]
showLangText :: BlogLang -> Text
showLangText EN = "en"
showLangText DE = "de"
-- index site headline -- index site headline
topText DE = "Aktuelle Einträge" topText DE = "Aktuelle Einträge"
topText EN = "Latest entries" topText EN = "Latest entries"

View file

@ -24,6 +24,7 @@ import Data.Time
import Data.SafeCopy (base, deriveSafeCopy) import Data.SafeCopy (base, deriveSafeCopy)
import Happstack.Server hiding (Session) import Happstack.Server hiding (Session)
import Happstack.Server.Compression import Happstack.Server.Compression
import Network.Captcha.ReCaptcha
import Options import Options
import System.Locale (defaultTimeLocale) import System.Locale (defaultTimeLocale)
@ -38,6 +39,8 @@ defineOptions "MainOptions" $ do
stringOption "optState" "statedir" "../" stringOption "optState" "statedir" "../"
"Directory in which the /BlogState dir is located.\ "Directory in which the /BlogState dir is located.\
\ The default is ../ (if run from src/)" \ The default is ../ (if run from src/)"
stringOption "optCaptcha" "captchakey" ""
"The reCaptcha private key"
intOption "optPort" "port" 8000 intOption "optPort" "port" 8000
"The port to run the web server on. Default is 8000" "The port to run the web server on. Default is 8000"
@ -50,12 +53,12 @@ main = do
runCommand $ \opts args -> runCommand $ \opts args ->
bracket (openLocalStateFrom (optState opts ++ "BlogState") initialBlogState) bracket (openLocalStateFrom (optState opts ++ "BlogState") initialBlogState)
(createCheckpointAndClose) (createCheckpointAndClose)
(\acid -> simpleHTTP nullConf {port = optPort opts} $ tazBlog acid) (\acid -> simpleHTTP nullConf {port = optPort opts} $ tazBlog acid $ optCaptcha opts)
tazBlog :: AcidState Blog -> ServerPart Response tazBlog :: AcidState Blog -> String -> ServerPart Response
tazBlog acid = do tazBlog acid captchakey = do
compr <- compressedResponseFilter compr <- compressedResponseFilter
msum [ path $ \(lang :: BlogLang) -> blogHandler acid lang msum [ path $ \(lang :: BlogLang) -> blogHandler acid lang captchakey
, nullDir >> showIndex acid DE , nullDir >> showIndex acid DE
, dir " " $ nullDir >> , dir " " $ nullDir >>
seeOther ("https://plus.google.com/115916629925754851590" :: Text) (toResponse ()) seeOther ("https://plus.google.com/115916629925754851590" :: Text) (toResponse ())
@ -87,12 +90,12 @@ tazBlog acid = do
, notFound $ toResponse $ showError NotFound DE , notFound $ toResponse $ showError NotFound DE
] ]
blogHandler :: AcidState Blog -> BlogLang -> ServerPart Response blogHandler :: AcidState Blog -> BlogLang -> String -> ServerPart Response
blogHandler acid lang = blogHandler acid lang captchakey =
msum [ path $ \(eId :: Integer) -> showEntry acid lang $ EntryId eId msum [ path $ \(eId :: Integer) -> showEntry acid lang $ EntryId eId
, do decodeBody tmpPolicy , do decodeBody tmpPolicy
dir "postcomment" $ path $ dir "postcomment" $ path $
\(eId :: Integer) -> addComment acid lang $ EntryId eId \(eId :: Integer) -> addComment acid lang captchakey $ EntryId eId
, nullDir >> showIndex acid lang , nullDir >> showIndex acid lang
, dir "rss" $ nullDir >> showRSS acid lang , dir "rss" $ nullDir >> showRSS acid lang
, dir "rss.xml" $ nullDir >> showRSS acid lang , dir "rss.xml" $ nullDir >> showRSS acid lang
@ -134,15 +137,22 @@ showRSS acid lang = do
setHeaderM "content-type" "text/xml" setHeaderM "content-type" "text/xml"
ok $ toResponse feed ok $ toResponse feed
addComment :: AcidState Blog -> BlogLang -> EntryId -> ServerPart Response addComment :: AcidState Blog -> BlogLang -> String -> EntryId -> ServerPart Response
addComment acid lang eId = do addComment acid lang captchakey eId = do
now <- liftIO $ getCurrentTime >>= return now <- liftIO $ getCurrentTime >>= return
nCtext <- lookText' "ctext" nCtext <- lookText' "ctext"
nComment <- Comment <$> pure now nComment <- Comment <$> pure now
<*> lookText' "cname" <*> lookText' "cname"
<*> pure (commentEscape nCtext) <*> pure (commentEscape nCtext)
update' acid (AddComment eId nComment) -- captcha verification
seeOther ("/" ++ show lang ++ "/" ++ show eId) (toResponse()) challenge <- look "recaptcha_challenge_field"
response <- look "recaptcha_response_field"
(userIp, _) <- askRq >>= return . rqPeer
validation <- liftIO $ validateCaptcha captchakey userIp challenge response
case validation of
Right _ -> update' acid (AddComment eId nComment)
>> seeOther ("/" ++ show lang ++ "/" ++ show eId) (toResponse())
Left _ -> (liftIO $ putStrLn "Captcha failed") >> seeOther ("/" ++ show lang ++ "/" ++ show eId) (toResponse())
commentEscape :: Text -> Text commentEscape :: Text -> Text
commentEscape = newlineEscape . ltEscape . gtEscape . ampEscape commentEscape = newlineEscape . ltEscape . gtEscape . ampEscape