packages feed

VKHS 0.1.6 → 0.2.0

raw patch · 9 files changed

+239/−84 lines, 9 filesdep +optparse-applicativedep ~fclabelsnew-component:exe:vkqPVP ok

version bump matches the API change (PVP)

Dependencies added: optparse-applicative

Dependency ranges changed: fclabels

API changes (from Hackage documentation)

- Web.VKHS.Login: Ads :: AccessRight
- Web.VKHS.Login: Audio :: AccessRight
- Web.VKHS.Login: Debug :: Verbosity
- Web.VKHS.Login: Docs :: AccessRight
- Web.VKHS.Login: Env :: Verbosity -> String -> [(String, String)] -> ClientId -> Int -> [AccessRight] -> Env
- Web.VKHS.Login: Friends :: AccessRight
- Web.VKHS.Login: Groups :: AccessRight
- Web.VKHS.Login: Messages :: AccessRight
- Web.VKHS.Login: Normal :: Verbosity
- Web.VKHS.Login: Notes :: AccessRight
- Web.VKHS.Login: Notifications :: AccessRight
- Web.VKHS.Login: Notify :: AccessRight
- Web.VKHS.Login: Offers :: AccessRight
- Web.VKHS.Login: Offline :: AccessRight
- Web.VKHS.Login: Pages :: AccessRight
- Web.VKHS.Login: Photos :: AccessRight
- Web.VKHS.Login: Questions :: AccessRight
- Web.VKHS.Login: Stats :: AccessRight
- Web.VKHS.Login: Status :: AccessRight
- Web.VKHS.Login: Trace :: Verbosity
- Web.VKHS.Login: Video :: AccessRight
- Web.VKHS.Login: Wall :: AccessRight
- Web.VKHS.Login: ac_rights :: Env -> [AccessRight]
- Web.VKHS.Login: clientId :: Env -> ClientId
- Web.VKHS.Login: data AccessRight
- Web.VKHS.Login: data Env
- Web.VKHS.Login: data Verbosity
- Web.VKHS.Login: delay_ms :: Env -> Int
- Web.VKHS.Login: formdata :: Env -> [(String, String)]
- Web.VKHS.Login: useragent :: Env -> String
- Web.VKHS.Login: verbose :: Env -> Verbosity
- Web.VKHS.Types: instance Show Env
+ Web.VKHS.API: envcall :: String -> Env CallEnv
+ Web.VKHS.Login: instance Show ActionHist
+ Web.VKHS.Types: CallEnv :: String -> CallEnv
+ Web.VKHS.Types: LoginEnv :: [(String, String)] -> [AccessRight] -> ClientId -> LoginEnv
+ Web.VKHS.Types: access_token :: CallEnv -> String
+ Web.VKHS.Types: allAccess :: [AccessRight]
+ Web.VKHS.Types: callEnv :: Env a -> String -> Env CallEnv
+ Web.VKHS.Types: data CallEnv
+ Web.VKHS.Types: data LoginEnv
+ Web.VKHS.Types: instance Show CallEnv
+ Web.VKHS.Types: instance Show LoginEnv
+ Web.VKHS.Types: instance Show subenv => Show (Env subenv)
+ Web.VKHS.Types: mkEnv :: s -> Env s
+ Web.VKHS.Types: sub :: Env subenv -> subenv
- Web.VKHS.API: api :: Env -> AccessToken -> String -> Parameters -> IO (Either String String)
+ Web.VKHS.API: api :: Env CallEnv -> String -> [(String, String)] -> IO (Either String String)
- Web.VKHS.API.JSON: api :: Env -> AccessToken -> String -> [(String, String)] -> IO (Either String JSValue)
+ Web.VKHS.API.JSON: api :: Env CallEnv -> String -> [(String, String)] -> IO (Either String JSValue)
- Web.VKHS.Curl: vk_curl :: Env -> Writer [CURLoption] () -> IO (Either String String)
+ Web.VKHS.Curl: vk_curl :: Env a -> Writer [CURLoption] () -> IO (Either String String)
- Web.VKHS.Login: env :: String -> String -> String -> [AccessRight] -> Env
+ Web.VKHS.Login: env :: String -> String -> String -> [AccessRight] -> Env LoginEnv
- Web.VKHS.Login: login :: Env -> IO (Either String AccessToken)
+ Web.VKHS.Login: login :: Env LoginEnv -> IO (Either String AccessToken)
- Web.VKHS.Types: Env :: Verbosity -> String -> [(String, String)] -> ClientId -> Int -> [AccessRight] -> Env
+ Web.VKHS.Types: Env :: subenv -> Verbosity -> String -> Int -> Env subenv
- Web.VKHS.Types: ac_rights :: Env -> [AccessRight]
+ Web.VKHS.Types: ac_rights :: LoginEnv -> [AccessRight]
- Web.VKHS.Types: clientId :: Env -> ClientId
+ Web.VKHS.Types: clientId :: LoginEnv -> ClientId
- Web.VKHS.Types: data Env
+ Web.VKHS.Types: data Env subenv
- Web.VKHS.Types: delay_ms :: Env -> Int
+ Web.VKHS.Types: delay_ms :: Env subenv -> Int
- Web.VKHS.Types: formdata :: Env -> [(String, String)]
+ Web.VKHS.Types: formdata :: LoginEnv -> [(String, String)]
- Web.VKHS.Types: useragent :: Env -> String
+ Web.VKHS.Types: useragent :: Env subenv -> String
- Web.VKHS.Types: verbose :: Env -> Verbosity
+ Web.VKHS.Types: verbose :: Env subenv -> Verbosity

Files

VKHS.cabal view
@@ -1,10 +1,10 @@  name:                VKHS-version:             0.1.6-synopsis:            Provides access to Vkontakte social network, popular in Russia+version:             0.2.0+synopsis:            Provides access to Vkontakte social network description:     Provides access to Vkontakte API methods. Library requires no interaction-    with user during Implicit-flow authentication.+    with the user during Implicit-flow authentication.  license:             BSD3 license-file:        LICENSE@@ -20,6 +20,10 @@     type:     git     location: https://github.com/ierton/vkhs.git +executable vkq+  hs-source-dirs:    src+  main-is:           VKQ.hs+ library   hs-source-dirs:    src   other-modules:     Test.Debug, Test.Data, Test.API, Network.Shpider.Forms, Network.Protocol.Uri, Network.Protocol.Mime, Network.Protocol.Http, Network.Protocol.Cookie, Network.Protocol.Uri.Remap, Network.Protocol.Uri.Query, Network.Protocol.Uri.Printer, Network.Protocol.Uri.Path, Network.Protocol.Uri.Parser, Network.Protocol.Uri.Encode, Network.Protocol.Uri.Data, Network.Protocol.Uri.Chars, Network.Protocol.Http.Status, Network.Protocol.Http.Printer, Network.Protocol.Http.Parser, Network.Protocol.Http.Headers, Network.Protocol.Http.Data  @@ -46,5 +50,6 @@                      bimap ==0.2.*,                      template-haskell ==2.8.*,                      transformers ==0.3.*,-                     fclabels >=1.1.4+                     fclabels >=1.1.4 && <1.1.6,+                     optparse-applicative >=0.4 && <0.6 
src/Test/API.hs view
@@ -1,16 +1,17 @@ module Test.API where -import Web.VKHS.Login-import Web.VKHS.API+import Web.VKHS import Text.Printf  -- | Almost working example. Just set correct values for client_id/login/password print_user_info = do     let e = env "3213232" "user@example.com" "password" [Photos,Audio,Groups]-    (Right at) <- login e-    (Right ans) <- api e{verbose = Debug} at "users.get" [+    r <- login e{verbose = Debug}+    print r+    let (Right (at,_,_)) = r+    ans <- api (callEnv e{verbose = Debug} at) "users.get" [           ("uids","911727")         , ("fields","first_name,last_name,nickname,screen_name")         , ("name_case","nom")         ]-    putStrLn ans+    print ans
+ src/VKQ.hs view
@@ -0,0 +1,74 @@+module Main where++import Control.Monad+import Data.Label.Abstract+import Network.Protocol.Uri.Query+import Options.Applicative+import System.Exit+import System.IO+import Text.Printf+import Web.VKHS++data Options = Options+  { verb :: Verbosity+  , cmdOpts :: CmdOptions+  } deriving(Show)++data CmdOptions+  = Login LoginOptions+  | Call CallOptions+  deriving(Show)++data LoginOptions = LoginOptions+  { appid :: String+  , username :: String+  , password :: String+  } deriving(Show)++data CallOptions = CallOptions+  { accessToken :: String+  , method :: String+  , args :: String+  } deriving(Show)++loginOptions :: Parser CmdOptions+loginOptions = Login <$> (LoginOptions+  <$> argument str (metavar "APPID" & help "Application identifier (provided to you by vk.com)")+  <*> argument str (metavar "USER" & help "User name or email")+  <*> argument str (metavar "PASS" & help "User password"))++callOptions :: Parser CmdOptions+callOptions = Call <$> (CallOptions+  <$> argument str (metavar "ACCESS_TOKEN" & help "access_token")+  <*> argument str (metavar "METHOD" & help "Method to call")+  <*> argument str (metavar "PARAMS" & help "Method arguments, KEY=VALUE[,KEY2=VALUE2[,,,]]"))++opts :: Parser Options+opts = Options+  <$> flag Normal Debug (long "verbose" & help "Be verbose")+  <*> subparser (+    command "login" (info loginOptions+        ( progDesc "Login into vk.com (see --help for details); Print access_token (among user_id and expiration time) on success" ))+    & command "call" (info callOptions+        ( progDesc "call VK API method (see --help for details)" ))+    )++ifeither e fl fr = either fl fr e++errexit e = hPutStrLn stderr e >> exitFailure++cmd :: Options -> IO ()+cmd (Options v (Login (LoginOptions a u p))) = do+  let e = (env a u p allAccess) { verbose = v }+  ea <- login e+  ifeither ea errexit $ \(at,uid,expin) -> do+    printf "%s %s %s\n" at uid expin++cmd (Options v (Call (CallOptions act mn args))) = do+  let e = (envcall act) { verbose = v }+  ea <- api e mn (fw (keyValues "," "=") args)+  ifeither ea errexit putStrLn++main :: IO ()+main = execParser (info opts idm) >>= cmd+
src/Web/VKHS.hs view
@@ -82,3 +82,4 @@ import Web.VKHS.Types import Web.VKHS.Login import Web.VKHS.API+
src/Web/VKHS/API.hs view
@@ -1,5 +1,6 @@ module Web.VKHS.API     ( api+    , envcall     )  where  import Control.Monad@@ -21,10 +22,23 @@ import Web.VKHS.Types import Web.VKHS.Curl -api :: Env -> AccessToken -> String -> Parameters -> IO (Either String String)-api e (at,_,_) mn mp =+envcall :: String -> Env CallEnv+envcall at = mkEnv (CallEnv at)++-- | Invoke the request. Return answer (normally, string representation of+-- JSON data). See documentation:+--+-- <http://vk.com/developers.php?oid=-1&p=%D0%9E%D0%BF%D0%B8%D1%81%D0%B0%D0%BD%D0%B8%D0%B5_%D0%BC%D0%B5%D1%82%D0%BE%D0%B4%D0%BE%D0%B2_API>+api :: Env CallEnv+    -- ^ the VKHS environment+    -> String+    -- ^ API method name+    -> [(String, String)]+    -- ^ API method parameters (name-value pairs)+    -> IO (Either String String)+api e mn mp =     let uri = BS.pack $ showUri $ (\f -> f $ toUri $ printf "https://api.vk.com/method/%s" mn) $-                set query $ bw params (("access_token",at):mp)+                set query $ bw params (("access_token",(access_token . sub) e):mp)     in runErrorT $ do         r <- ErrorT $ vk_curl e (tell [CURLOPT_URL  uri])         (_,b) <- ErrorT $ return $ parseResponse r
src/Web/VKHS/API/JSON.hs view
@@ -15,5 +15,6 @@ liftR (Ok a) = return a liftR (Error s) = fail s -api :: Env -> AccessToken -> String -> [(String,String)] -> IO (Either String JSValue)-api e a mn mp = runErrorT $ ErrorT (Base.api e a mn mp)  >>= liftR . J.decode+api :: Env CallEnv -> String -> [(String,String)] -> IO (Either String JSValue)+api e mn mp = runErrorT $ ErrorT (Base.api e mn mp)  >>= liftR . J.decode+
src/Web/VKHS/Curl.hs view
@@ -16,7 +16,7 @@  -- | Generic request sender. Uses delay to prevent application from being -- blocked for flooding-vk_curl :: Env -> Writer [CURLoption] () -> IO (Either String String)+vk_curl :: Env a -> Writer [CURLoption] () -> IO (Either String String) vk_curl e w = do     let askE x = return (x e)     v <- askE verbose
src/Web/VKHS/Login.hs view
@@ -2,10 +2,7 @@  module Web.VKHS.Login     ( login-    , Env(..)     , env-    , AccessRight(..)-    , Verbosity(..)     ) where  import Prelude hiding ((.), id, catch)@@ -73,17 +70,16 @@     -- ^ User password     -> [AccessRight]     -- ^ Access rights to request-    -> Env-env cid email pwd ar = Env-    Normal-    "Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20120702 Firefox/12.0"+    -> Env LoginEnv+env cid email pwd ar = mkEnv +  (LoginEnv     [ ("email",email) , ("pass",pwd) ]-    cid-    500     ar+    cid+  ) -vk_start_action :: ClientId -> [AccessRight] -> Action-vk_start_action cid ac = OpenUrl start_url mempty where+vk_start_action :: ClientId -> [AccessRight] -> ActionHist+vk_start_action cid ac = AH [] $ OpenUrl start_url mempty where     start_url = (\f -> f $ toUri "http://oauth.vk.com/authorize")          $ set query $ bw params             [ ("client_id",     cid)@@ -95,9 +91,9 @@  type FilledForm = Form -type VK a = ReaderT Env (ErrorT String IO) a+type VK a = ReaderT (Env LoginEnv) (ErrorT String IO) a -runVK :: Env -> VK a -> IO (Either String a)+runVK :: Env LoginEnv -> VK a -> IO (Either String a) runVK e vk = runErrorT (runReaderT vk e)  liftVK :: IO (Either String a) -> VK a@@ -114,8 +110,14 @@ type Page = (Http Response, Body)  -- | Browser action++type Hist = [[String]]++data ActionHist = AH Hist Action+  deriving(Show)+ data Action = OpenUrl Uri Cookies | SendForm Form Cookies-    deriving (Show)+  deriving (Show)  -- | Url assotiated with Action actionUri :: Action -> Uri@@ -160,7 +162,7 @@ split_inputs :: [(String,String)]              -- ^ User dictionary              -> M.Map String String-             -- ^ Form fields with default values (default is valid if non-zero)+             -- ^ Fields with default values (default doesn't exist if zero string)              -> (M.Map String (), M.Map String String, M.Map String String) split_inputs d m =     let (b,g) = M.mapEitherWithKey (match_field d) m@@ -171,14 +173,17 @@             | otherwise    = maybe (Left ())           (Right . Right) u             where u = lookup k d +inputs_to_fill :: Form -> [String]+inputs_to_fill f = let (inps,_,_) = split_inputs [] (inputs f) in M.keys inps+ vk_fill_form :: Form -> VK FilledForm vk_fill_form f = ask >>= \e -> do-    let (bad,good,user) = split_inputs (formdata e) (inputs f)+    let (bad,good,user) = split_inputs ((formdata . sub) e) (inputs f)     when (not $ M.null bad) (fail $ "Unmatched form parameters: " ++ (show bad))     return f { inputs = good } --- | Executes an action, returns Web-server's answer, adjusts cookies--- Cookie management algorithm is very primitive: just merging+-- | Execute an action, return Web-server's answer and adjusted cookies. Cookie+-- management is very primitive: it's no more than merging old and new ones vk_move :: Action -> VK (Page, Cookies) vk_move (OpenUrl u c) = do     (h,b) <- (vk_get u c)@@ -192,36 +197,47 @@ uri_fragment = get location >=> pure . get fragment >=> pure . fw (keyValues "&" "=") >=> \f -> do     (\a b c -> (a,b,c)) <$> lookup "access_token" f <*> lookup "user_id" f <*> lookup "expires_in" f --- | Suggests new action-vk_analyze :: (Page,Cookies) -> VK (Either AccessToken Action)-vk_analyze ((h,b),c)-    | isJust a       = return $ Left (fromJust a)-    | isJust l       = return $ Right $ OpenUrl (fromJust l) c-    | (not . null) f = return $ Right $ SendForm (head f) c-    | otherwise      = fail "HTML processing failure (new design of VK login dialog?)"+-- | Suggest new action+vk_analyze :: Hist -> (Page,Cookies) -> VK (Either AccessToken (Action,Hist))+vk_analyze hist ((h,b),c)+    | isJust a        = return $ Left (fromJust a)+    | isJust l        = return $ Right (OpenUrl (fromJust l) c, hist)+    | not (null fs) && not (is `elem` hist)+                      = return $ Right (SendForm f c, is:hist)+    | not (null fs) && (is `elem` hist)+                      = fail $ "Invalid password/Form containig following inputs already seen: " ++ (show is)+    | not gs          = fail $ "HTTP error: status " ++ (show s)+    | otherwise       = fail $ "HTML processing failure (new design of VK login dialog?)"     where+        gs = s`elem` [OK,Created,Found,SeeOther,Accepted,Continue]+        s = get status h         l = get location h-        f = parseTags >>> gatherForms $ b+        fs = parseTags >>> gatherForms $ b+        f = head fs+        is = inputs_to_fill f         a = uri_fragment h  vk_dump_page :: Int -> Uri -> Page -> IO ()-vk_dump_page n u (h,b) = +vk_dump_page n u (h,b) +  | (>0) . length . filter (isAlpha) $ b =      let name = printf "%02d-%s.html" n (showAuthority (get authority u))     in bracket (openFile name WriteMode) (hClose) $ \f -> do         hPutStrLn f b         hPutStrLn stderr $ "dumped: name " ++ (name) ++ " size " ++ (show $ length b)+  | otherwise = return () --- | Start login procedure, return AccessToken on success-login :: Env -> IO (Either String AccessToken)-login e =  runVK e $ loop 0 (vk_start_action (clientId e) (ac_rights e)) where -    loop n act = do+-- | Execute login procedure, return (Right AccessToken) on success+login :: Env LoginEnv -> IO (Either String AccessToken)+login e@(Env (LoginEnv _ acr cid) _ _ _) = +  runVK e $ loop [0..] (vk_start_action cid acr) where +    loop (n:ns) (AH h act) = do         when_trace $ printf "VK => %02d %s" n (show act)         ans@(p,c) <- vk_move act         when_debug $ vk_dump_page n (actionUri act) p-        a <- vk_analyze ans+        a <- vk_analyze h ans         case a of-            Right act' -> do-                loop (n+1) act'+            Right (act',h') -> do+                loop ns (AH h' act')             Left at -> do                 return at 
src/Web/VKHS/Types.hs view
@@ -10,49 +10,92 @@  -- | Access rigth to request from VK. data AccessRight-    = Notify  -- Пользователь разрешил отправлять ему уведомления.-    | Friends -- Доступ к друзьям.-    | Photos  -- Доступ к фотографиям.-    | Audio   -- Доступ к аудиозаписям.-    | Video   -- Доступ к видеозаписям.-    | Docs    -- Доступ к документам.-    | Notes   -- Доступ заметкам пользователя.-    | Pages   -- Доступ к wiki-страницам.-    | Status  -- Доступ к статусу пользователя.-    | Offers  -- Доступ к предложениям (устаревшие методы).-    | Questions   -- Доступ к вопросам (устаревшие методы).-    | Wall    -- Доступ к обычным и расширенным методам работы со стеной.-              -- Внимание, данное право доступа недоступно для сайтов (игнорируется при попытке авторизации).-    | Groups  -- Доступ к группам пользователя.-    | Messages    -- (для Standalone-приложений) Доступ к расширенным методам работы с сообщениями.-    | Notifications   -- Доступ к оповещениям об ответах пользователю.-    | Stats   -- Доступ к статистике групп и приложений пользователя, администратором которых он является.-    | Ads     -- Доступ к расширенным методам работы с рекламным API.-    | Offline -- Доступ к API в любое время со стороннего сервера. -    deriving(Show)+  = Notify  -- Пользователь разрешил отправлять ему уведомления.+  | Friends -- Доступ к друзьям.+  | Photos  -- Доступ к фотографиям.+  | Audio   -- Доступ к аудиозаписям.+  | Video   -- Доступ к видеозаписям.+  | Docs    -- Доступ к документам.+  | Notes   -- Доступ заметкам пользователя.+  | Pages   -- Доступ к wiki-страницам.+  | Status  -- Доступ к статусу пользователя.+  | Offers  -- Доступ к предложениям (устаревшие методы).+  | Questions   -- Доступ к вопросам (устаревшие методы).+  | Wall    -- Доступ к обычным и расширенным методам работы со стеной.+            -- Внимание, данное право доступа недоступно для сайтов (игнорируется при попытке авторизации).+  | Groups  -- Доступ к группам пользователя.+  | Messages    -- (для Standalone-приложений) Доступ к расширенным методам работы с сообщениями.+  | Notifications   -- Доступ к оповещениям об ответах пользователю.+  | Stats   -- Доступ к статистике групп и приложений пользователя, администратором которых он является.+  | Ads     -- Доступ к расширенным методам работы с рекламным API.+  | Offline -- Доступ к API в любое время со стороннего сервера. +  deriving(Show) +allAccess :: [AccessRight]+allAccess =+  [ +  --   Notify+    Friends+  , Photos+  , Audio+  , Video+  , Docs+  , Notes+  -- , Pages+  , Status+  , Offers+  , Questions+  , Wall+  , Groups+  , Messages+  , Notifications+  , Stats+  -- , Ads+  -- , Offline+  ]+ -- See API docs http://vk.com/developers.php?oid=-1&p=Права_доступа_приложений (in -- Russian) for details  -- | Verbosity level. Debug will dump *html and output curl log data Verbosity = Normal | Trace | Debug-    deriving(Enum,Eq,Ord,Show)+  deriving(Enum,Eq,Ord,Show)  type ClientId = String +data LoginEnv = LoginEnv+  { formdata :: [(String,String)]+  -- ^ Dictionary containig forms input/value+  , ac_rights :: [AccessRight]+  -- ^ Access rights, required by later API calls+  , clientId :: ClientId+  -- ^ Application ID provided by vk.com+  } deriving(Show)++data CallEnv = CallEnv+  { access_token :: String+  -- ^ Access token, the result of login operation+  } deriving(Show)+ -- | VKHS environment-data Env = Env { verbose :: Verbosity-    -- ^ Verbosity level-    , useragent :: String-    -- ^ User agent identifier, defaults to Mozilla Firefox-    , formdata :: [(String,String)]-    -- ^ Dictionary containig forms input/value-    , clientId :: ClientId-    -- ^ Application ID provided by vk.com-    , delay_ms :: Int-    -- ^ Delay after each transaction, in milliseconds. Library uses it for-    -- preventing application from being banned for flooding.-    , ac_rights :: [AccessRight]-    -- ^ Access rights, required by later API calls-    } deriving (Show)+data Env subenv = Env+  { sub :: subenv+  , verbose :: Verbosity+  -- ^ Verbosity level+  , useragent :: String+  -- ^ User agent identifier, defaults to Mozilla Firefox+  , delay_ms :: Int+  -- ^ Delay after each transaction, in milliseconds. Library uses it for+  -- preventing application from being banned for flooding.+  } deriving (Show)++mkEnv :: s -> Env s+mkEnv x = Env+  x+  Normal+  "Mozilla/5.0 (X11; Linux x86_64; rv:12.0) Gecko/20120702 Firefox/12.0"+  500++callEnv :: Env a -> String -> Env CallEnv+callEnv (Env _ a b c) at = Env (CallEnv at) a b c