diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright (c) 2012, Sergey Mironov
+
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+
+    * 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.
+
+    * Neither the name of Sergey Mironov nor the names of other
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"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 COPYRIGHT
+OWNER 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.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/VKHS.cabal b/VKHS.cabal
new file mode 100644
--- /dev/null
+++ b/VKHS.cabal
@@ -0,0 +1,49 @@
+-- Initial vk.cabal generated by cabal init.  For further documentation, 
+-- see http://haskell.org/cabal/users-guide/
+
+name:                VKHS
+version:             0.0.1
+synopsis:            Provides access to VkontakteAPI (Popular Russian social network)
+description:
+    Library written in Haskell provides access to Vkontakte API methods (popular
+    Russian social network).
+
+    Internally, library uses small curl-based HTTP automata and tagsoup for jumping
+    over relocations and submitting various 'Yes I agree' forms.
+
+license:             BSD3
+license-file:        LICENSE
+author:              Sergey Mironov
+maintainer:          ierton@gmail.com
+copyright:           Copyright (c) 2012, Sergey Mironov
+category:            Web
+build-type:          Simple
+cabal-version:       >=1.8
+
+source-repository head
+    type:     git
+    location: https://github.com/ierton/vkhs.git
+
+library
+  hs-source-dirs:    src
+  exposed-modules:   Web.VKHS
+                     Web.VKHS.Types
+                     Web.VKHS.Login
+                     Web.VKHS.Api
+
+  build-depends:     base ==4.5.*,
+                     containers ==0.4.*,
+                     tagsoup-parsec ==0.0.*,
+                     tagsoup ==0.12.*,
+                     mtl ==2.1.*,
+                     failure ==0.2.*,
+                     bytestring ==0.9.*,
+                     curlhs ==0.0.*,
+                     safe ==0.3.*,
+                     parsec ==3.1.*,
+                     split ==0.2.*,
+                     utf8-string ==0.3.*,
+                     bimap ==0.2.*,
+                     template-haskell ==2.7.*,
+                     transformers ==0.3.*
+
diff --git a/src/Web/VKHS.hs b/src/Web/VKHS.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/VKHS.hs
@@ -0,0 +1,9 @@
+module Web.VKHS
+    ( module Web.VKHS.Login
+    , module Web.VKHS.Types
+    , module Web.VKHS.Api
+    ) where
+
+import Web.VKHS.Types
+import Web.VKHS.Login
+import Web.VKHS.Api
diff --git a/src/Web/VKHS/Api.hs b/src/Web/VKHS/Api.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/VKHS/Api.hs
@@ -0,0 +1,25 @@
+module Web.VKHS.Api where
+
+import Control.Monad.Writer
+
+import Data.Label
+import qualified Data.ByteString.Char8 as BS
+
+import Network.Curlhs.Core
+
+import Network.Protocol.Http
+import Network.Protocol.Uri
+import Network.Protocol.Uri.Query
+
+import Text.Printf
+
+import Web.VKHS.Types
+import Web.VKHS.Curl
+
+api :: Env -> AccessToken -> String -> Parameters -> IO (Either String String)
+api e (at,_,_) 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)
+    in vk_curl e $ do
+        tell [CURLOPT_URL  uri]
+
diff --git a/src/Web/VKHS/Login.hs b/src/Web/VKHS/Login.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/VKHS/Login.hs
@@ -0,0 +1,227 @@
+{-# LANGUAGE FlexibleContexts #-}
+
+module Web.VKHS.Login
+    ( login
+    , Env(..)
+    , env
+    , AccessRight(..)
+    , Verbosity(..)
+    ) where
+
+import Prelude hiding ((.), id, catch)
+
+import Control.Applicative
+import Control.Category
+import Control.Exception
+import Control.Failure
+import Control.Monad.Trans
+import Control.Monad.Error
+import Control.Monad.Reader
+import Control.Monad.Writer
+import qualified Control.Monad.State as S
+
+import qualified Data.ByteString.Char8 as BS
+import Data.List
+import Data.String
+import Data.Char
+import Data.Monoid
+import Data.Either
+import Data.Label
+import qualified Data.Map as M
+import Data.Maybe
+import Data.Typeable
+
+import Network.Curlhs.Core
+import Network.Protocol.Http
+import Network.Protocol.Uri
+import Network.Protocol.Uri.Query
+import Network.Protocol.Cookie as C
+import Network.Shpider.Forms
+
+import Text.HTML.TagSoup
+import Text.Printf
+import System.IO
+
+import Web.VKHS.Types
+import Web.VKHS.Curl
+
+-- Test applications: 
+--
+-- pirocheck
+-- ID 3115622
+-- Key IOmaB10C1v7RoMiM6Lnu
+--
+-- pirofetch
+-- ID 3082266
+-- Key <lost>
+
+type Url = String
+type Email = String
+type Uid = String
+type Password = String
+type Body = String
+
+toarg :: [AccessRight] -> String
+toarg = intercalate "," . map (map toLower . show)
+
+-- | Gathers login information into Env data set. 
+env :: String 
+    -- ^ Client ID (provided by Application registration form)
+    -> String
+    -- ^ User email, able to authenticate the user
+    -> String
+    -- ^ User password
+    -> [AccessRight]
+    -- ^ 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"
+    [ ("email",email) , ("pass",pwd) ]
+    cid
+    500
+    ar
+
+vk_start_action :: ClientId -> [AccessRight] -> Action
+vk_start_action cid ac = OpenUrl start_url mempty where
+    start_url = (\f -> f $ toUri "http://oauth.vk.com/authorize") 
+        $ set query $ bw params
+            [ ("client_id",     cid)
+            , ("scope",         toarg ac)
+            , ("redirect_uri",  "http://oauth.vk.com/blank.html")
+            , ("display",       "wap")
+            , ("response_type", "token")
+            ]
+
+type FilledForm = Form
+
+type VK a = ReaderT Env (ErrorT String IO) a
+
+runVK :: Env -> VK a -> IO (Either String a)
+runVK e vk = runErrorT (runReaderT vk e)
+
+liftVK :: IO (Either String a) -> VK a
+liftVK m = liftIO m >>= either fail return
+
+dbgVK :: Verbosity -> IO () -> VK ()
+dbgVK v act = ask >>= \e -> do
+        if v > (verbose e) then return ()
+                            else liftIO $ act
+
+when_debug = dbgVK Debug
+when_trace = dbgVK Trace
+
+type Page = (Http Response, Body)
+
+-- | Browser action
+data Action = OpenUrl Uri Cookies | SendForm Form Cookies
+    deriving (Show)
+
+-- | Url assotiated with Action
+actionUri :: Action -> Uri
+actionUri (OpenUrl u _) = u
+actionUri (SendForm f _) = toUri . action $ f
+
+liftEIO act = (liftIO act) >>= either fail return
+
+-- | Send a get-request to the server
+vk_get :: Uri -> Cookies -> VK Page
+vk_get u c = let
+    c' = (BS.pack $ bw cookie $ map toShort $ bw gather c )
+    u' = (BS.pack $ showUri u)
+    in do
+        e <- ask
+        s <- liftEIO $ vk_curl e $ do
+            when ((not . BS.null) c') $ do
+                tell [CURLOPT_COOKIE c']
+            when ((not . BS.null) u') $ do
+                tell [CURLOPT_URL u']
+        liftVK (return $ parseResponse s)
+
+-- | Send a form to the server.
+vk_post :: FilledForm -> Cookies -> VK Page
+vk_post f c = let
+    c' = (BS.pack $ bw cookie $ map toShort $ bw gather c )
+    p' = (BS.pack $ bw params $ M.toList $ inputs f)
+    u' = (BS.pack $ action f)
+    in do
+        e <- ask
+        s <- liftEIO $ vk_curl e $ do
+            when ((not . BS.null) c') $ do
+                tell [CURLOPT_COOKIE c']
+            when ((not . BS.null) u') $ do
+                tell [CURLOPT_URL u']
+            tell [CURLOPT_POST True]
+            tell [CURLOPT_COPYPOSTFIELDS p']
+        liftVK (return $ parseResponse s)
+    
+-- | Splits parameters into 3 categories:
+-- 1)without a value, 2)filled from user dictionary, 3)with default values
+split_inputs :: [(String,String)]
+             -- ^ User dictionary
+             -> M.Map String String
+             -- ^ Form fields with default values (default is valid if non-zero)
+             -> (M.Map String (), M.Map String String, M.Map String String)
+split_inputs d m =
+    let (b,g) = M.mapEitherWithKey (match_field d) m
+    in (b, M.map (either id id) g, snd (M.mapEither id g))
+    where
+        match_field d k a
+            | not (null a) = maybe ((Right . Left) a)  (Right . Right) u
+            | otherwise    = maybe (Left ())           (Right . Right) u
+            where u = lookup k d
+
+vk_fill_form :: Form -> VK FilledForm
+vk_fill_form f = ask >>= \e -> do
+    let (bad,good,user) = split_inputs (formdata 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
+vk_move :: Action -> VK (Page, Cookies)
+vk_move (OpenUrl u c) = do
+    (h,b) <- (vk_get u c)
+    return ((h,b),c`mappend`(get setCookies h))
+vk_move (SendForm f c) = do
+    f' <- vk_fill_form f
+    (h,b) <- (vk_post f' c)
+    return ((h,b),c`mappend`(get setCookies h))
+
+uri_fragment :: Http Response -> Maybe AccessToken
+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?)"
+    where
+        l = get location h
+        f = parseTags >>> gatherForms $ b
+        a = uri_fragment h
+
+vk_dump_page :: Int -> Uri -> Page -> IO ()
+vk_dump_page n u (h,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)
+
+-- | Executes login procedure. AccessToken is returned 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
+        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
+        case a of
+            Right act' -> do
+                loop (n+1) act'
+            Left at -> do
+                return at
+
diff --git a/src/Web/VKHS/Types.hs b/src/Web/VKHS/Types.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/VKHS/Types.hs
@@ -0,0 +1,59 @@
+module Web.VKHS.Types where
+
+-- | AccessToken is a authentication data, required by all VK API
+-- functions. It is a tuple of access_token, user_id, expires_in fields,
+-- returned by login procedure.
+type AccessToken = (String,String,String)
+
+-- See http://vk.com/developers.php?oid=-1&p=Авторизация_клиентских_приложений
+-- (in Russian) for more details
+
+-- | Access rigth to request from VK.
+-- See API docs http://vk.com/developers.php?oid=-1&p=Права_доступа_приложений (in
+-- Russian) for details
+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)
+
+-- | Verbosity level. Setting Debug results in dumping *html files
+data Verbosity = Normal | Trace | Debug
+    deriving(Enum,Eq,Ord,Show)
+
+type ClientId = String
+
+-- | VKHS environment
+data Env = Env
+    { verbose :: Verbosity
+    -- ^ Verbosity level
+    , useragent :: String
+    -- ^ User agent identifier. Has an affordable default.
+    , formdata :: [(String,String)]
+    -- ^ A dictionary used for filling forms
+    , clientId :: ClientId
+    -- ^ Application ID provided by vk.com
+    , delay_ms :: Int
+    -- ^ Delay in milliseconds. Set after each request to prevent application
+    -- from being blocked for flooding.
+    , ac_rights :: [AccessRight]
+    -- ^ Access rights to request
+    }
+    deriving (Show)
+
