packages feed

wp-archivebot (empty) → 0.1

raw patch · 4 files changed

+169/−0 lines, 4 filesdep +HTTPdep +basedep +feedsetup-changed

Dependencies added: HTTP, base, feed, network, parallel, tagsoup

Files

+ LICENSE view
@@ -0,0 +1,30 @@+Copyright (c) 2009++All rights reserved.++Redistribution and use in source and binary forms, with or without+modification, are permitted provided that the following conditions+are met:++1. Redistributions of source code must retain the above copyright+   notice, this list of conditions and the following disclaimer.++2. 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.++3. Neither the name of the author nor the names of his contributors+   may be used to endorse or promote products derived from this software+   without specific prior written permission.++THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``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 AUTHORS 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.
+ Main.hs view
@@ -0,0 +1,102 @@+import Control.Concurrent (forkIO)+import Control.Monad (liftM, forM_)+import Data.Char (intToDigit)+import Data.List (isInfixOf, isPrefixOf, foldl', deleteBy, nubBy)+import Data.Maybe (fromJust)+import Network.HTTP hiding (port)+import Network.Stream (ConnError)+import Network.URI+import System.Environment (getArgs)++import Text.HTML.TagSoup (parseTags, Tag(TagOpen))++import Text.Feed.Import (parseFeedString)+import Text.Feed.Types (Feed(RSSFeed))+import Text.RSS.Syntax (rssChannel, rssItems, RSSItem(..))++main :: IO ()+main = do args <- getArgs+          -- Webcite requires a valid email, and they filter out public+          -- emails like mailinator.com. So we demand an email from the user.+          let email = head args+          -- This is largely intended for the English Wikipedia, so we default to En's NewPages+          -- but we let the user override; any second argument is assumed to be a MediaWiki RSS+          let url = if length args > 1 then args !! 1+                     else "http://en.wikipedia.org/w/index.php?title=Special:NewPages&feed=rss"+          reader email url++-- inspiried by rss2irc+-- | wait on an RSS thread, updating every so often. Each RSS item links to some diff or page,+-- in addition to whatever other content the RSS item may contain (date, summary, etc.)+-- This runs 'archiveBot' on just that link, ignoring the rest.+reader :: String -> String -> IO ()+reader email url = items url >>= go+ where+   go old = do new <- items url+               -- remove duplicates+               let diff = foldl' (flip $ deleteBy matchingTitles) new old+               forM_ (take 100 diff) $ \itm ->+                case rssItemLink itm of+                    Nothing -> return ()+                    Just t  -> ignore $ forkIO $ archiveBot email t+               go new++   matchingTitles :: RSSItem -> RSSItem -> Bool+   matchingTitles x y = let title = (fromJust . rssItemTitle) in title x == title y++   -- Actually fetch a RSS feed and turn it from String to [RSSItem]+   items :: String -> IO [RSSItem]+   items rurl = do s <- get' rurl+                   let RSSFeed r = fromJust $ parseFeedString s+                   return $ nubBy matchingTitles $ rssItems $ rssChannel r++-- | Given the URL of an article, we suck down the HTML, grep it for http:// links,+-- filter out certain links that we don't want to archive (boilerplate links, interwiki links)+-- and then fire off an archive request for each link left.+archiveBot :: String -> String -> IO ()+archiveBot email ls = liftM uniq (fetchArticleURLs ls) >>= mapM_ (archiveURL email)+ where  uniq :: [String] -> [String] -- So hideous+        uniq = filter (\x ->not $ any (flip isInfixOf x) exceptions)+        exceptions :: [String]+        exceptions = ["wikimediafoundation", "http://www.mediawiki.org/", "wikipedia",+                      "&curid=", "index.php?title=", "&action="]++-- | Run 'extractURLs' on some page's raw HTML+fetchArticleURLs ::  String -> IO [String]+fetchArticleURLs = fmap extractURLs . get'++-- | Use the TagSoup library to extract all the hyperlinks in a page. This is really parsing the HTML,+-- so hopefully there won't be any spurious links.+extractURLs :: String -> [String]+extractURLs arg = [x | TagOpen "a" atts <- parseTags arg, (_,x) <- atts, "http://" `isPrefixOf` x]++-- | Webcitation.org is set up so one can archive a url just by doing a request for 'webcitation.org/archive?url=url&email=email'+-- So it's very easy, given a URL and an email, to archive a page. You don't even need to see what the response was.+archiveURL :: String -> String -> IO ()+archiveURL email url = print url' >> ignore (get' url')+              where url' = "http://www.webcitation.org/archive?url=" ++ escapeURIString isAllowedInURI url ++ "&email=" ++ email++-- | Convenient wrapper over the complexity of Network.HTTP. Given a URL, we get the raw HTML. No fuss, no muss.+-- Of course, this means we paper over a bunch of possible errors and issues, but we've no time for them! There are links to archive!+get' :: String -> IO String+get' = get . fromJust . parseURI+ where get :: URI -> IO String+       get uri = do resp <- simpleHTTP (request uri) >>= handleE (error . show)+                    case rspCode resp of+                        (2,0,0) -> return (rspBody resp)+                        _ -> error (httpError resp)+           where+               httpError resp = showRspCode (rspCode resp) ++ " " ++ rspReason resp+               showRspCode (a,b,c) = map intToDigit [a,b,c]++               request :: URI -> Request String+               request ur = Request{rqURI=ur, rqMethod=GET, rqHeaders=[], rqBody=""}++               handleE :: Monad m => (ConnError -> m a) -> Either ConnError a -> m a+               handleE h (Left e) = h e+               handleE _ (Right v) = return v++-- | Convenience function. 'forkIO' and 'forM_' demand return types of 'IO ()', but most interesting+-- IO functions don't return void. So one adds a call to 'return ()'; this just factors it out.+ignore ::(Monad m) => m a -> m ()+ignore x = x >> return ()
+ Setup.lhs view
@@ -0,0 +1,3 @@+#!/usr/bin/env runhaskell+> import Distribution.Simple+> main = defaultMain
+ wp-archivebot.cabal view
@@ -0,0 +1,34 @@+name:          wp-archivebot+version:       0.1+license:       BSD3+license-file:  LICENSE+author:        Gwern+maintainer:    gwern0@gmail.com+stability:     Experimental+tested-with:   GHC==6.10.2+category:      Network+synopsis:      Subscribe to a wiki's RSS feed and archive external links+description:   A MediaWiki's RecentChanges or NewPages links to every new edit or article; this bot will+               poll the corresponding RSS feeds (easier and more reliable than parsing the HTML), follow+               the links to the new edit/article, and then use TagSoup to filter out every off-wiki link+               (eg. to http://cnn.com).+               .+               With this list of external links, the bot will then fire off requests to http://webcitation.org/,+               which will make a backup (similar to the Internet Archive, but on-demand).+               .+               Example: to archive links from every article in the English Wikipedia's RecentChanges:+               .+               > wp-archivebot gwern0@gmail.com 'http://en.wikipedia.org/w/index.php?title=Special:RecentChanges&feed=rss'+               .+cabal-version: >= 1.2+build-type:    Simple++executable wp-archivebot+    main-is:       Main.hs++    build-depends: feed,+                   tagsoup,+                   network,+                   HTTP+    build-depends: base >= 3 && < 4, parallel+    ghc-options:   -Wall