{-# LANGUAGE TupleSections #-}
import Network.HTTP
import Network.URI
import Data.List
import System.Random
import System.Time
import Control.Arrow
import Control.Applicative
import Data.Digest.Pure.SHA
import Codec
.Binary
.UTF8
.String (encodeString
) import qualified Codec.Binary.Base64 as B64
import qualified Data.ByteString.Lazy as L
-- OAuth型
data OAuth = OAuth {
}
-- パラメータ型
-- パラメータをパース
parseParameter
:: String -> [Parameter
]parseParameter
= map splitByEqual
. splitByAnd
where
splitByAnd str
= case span (/= '&') str
of (x, "") -> [x]
(x, _:xs) -> x : splitByAnd xs
splitByEqual
= second
tail . span (/= '=')
-- パラメータのリストから特定のパラメータを取得
getParameter
:: Monad m
=> [Parameter
] -> String -> m Parameter
getParameter parameters parameterName =
case find
((== parameterName
) . fst) parameters
of Just parameter
-> return parameter
Nothing
-> fail "Parameter not found."
-- パラメータを '=' で結合し、 ',' 区切りで並べる
urlEncodeParams
:: [Parameter
] -> StringurlEncodeParams parameters
= intercalate
", " . map (\
(x
, y
) -> urlEncode x
++ "=" ++ doubleQuote
(urlEncode y
)) $ parameters
where
doubleQuote str = "\"" ++ str ++ "\""
-- リクエストの署名を生成
genSignature consumerSecret tokenSecret method uri parameters =
let parameters' = urlEncode . urlEncodeVars . sort $ parameters -- パラメータのソート・結合・エンコード
signatureKey = L.pack . map (fromIntegral . ord) $ urlEncode consumerSecret ++ "&" ++ urlEncode tokenSecret -- 署名キー生成
signatureBaseString = L.pack . map (fromIntegral . ord) $ urlEncode (show method) ++ "&" ++ urlEncode uri ++ "&" ++ parameters' -- 署名対象文字列生成
in
B64.encode . L.unpack . bytestringDigest $ hmacSha1 signatureKey signatureBaseString -- HMAC-SHA1アルゴリズムでダイジェスト値を生成・Base64でエンコード
-- リクエストトークン取得URL
requestTokenURL = "http://a...content-available-to-author-only...r.com/oauth/request_token"
authorizeURL = "http://a...content-available-to-author-only...r.com/oauth/authorize"
-- アクセストークン取得URL
accessTokenURL = "http://a...content-available-to-author-only...r.com/oauth/access_token"
-- APIリクエストURL
apiRequestURL api = "http://a...content-available-to-author-only...r.com/1/statuses/" ++ api ++ ".json"
-- OAuth Request 生成
oauthRequest
:: OAuth
-> String -> String -> [Parameter
] -> IO Request
_String
oauthRequest oauth url token parameter = do
let key = consumerKey oauth -- Consumer key
secret = consumerSecret oauth -- Consumer Secret
uri = fromJust . parseURI $ url -- URI
timestamp
<- show . (\
(TOD i
_) -> i
) <$> getClockTime
-- タイムスタンプ取得 let authorizationParameters_ = parameter ++ [
("oauth_consumer_key", key),
("oauth_nonce", nonce),
("oauth_timestamp", timestamp),
("oauth_signature_method", "HMAC-SHA1"),
("oauth_version", "1.0")
] -- 各種基本パラメータをセット
signature = genSignature secret token POST url authorizationParameters_ -- 署名生成
authorizationParameters = authorizationParameters_++[("oauth_signature", signature)] -- 署名をパラメータに加える
authorizationHeader = mkHeader HdrAuthorization . ("OAuth "++) . urlEncodeParams $ authorizationParameters -- Authorizationヘッダ生成
-- Request を構成
rqURI = uri,
rqMethod = POST,
rqHeaders = [authorizationHeader],
rqBody = ""
}
-- APIリクエスト
apiRequest
:: OAuth
-> String -> RequestMethod
-> [Parameter
] -> IO Request
_String
apiRequest oauth api method args = do
let key = consumerKey oauth -- Consumer key
token = accessToken oauth -- AccessToken
secret_Consumer = consumerSecret oauth -- Consumer Secret
secret_AccessToken = accessTokenSecret oauth -- AccessToken Secret
url = apiRequestURL api
uri = fromJust . parseURI $ if method == POST then url else url ++ "?" ++ urlEncodeVars args -- URI
timestamp
<- show . (\
(TOD i
_) -> i
) <$> getClockTime
-- タイムスタンプ取得 let authorizationParameters_ = [
("oauth_token", token),
("oauth_consumer_key", key),
("oauth_nonce", nonce),
("oauth_timestamp", timestamp),
("oauth_signature_method", "HMAC-SHA1"),
("oauth_version", "1.0")
] -- 各種基本パラメータをセット
signature = genSignature secret_Consumer secret_AccessToken method url (args ++ authorizationParameters_) -- 署名生成
authorizationParameters = authorizationParameters_++[("oauth_signature", signature)] -- 署名をパラメータに加える
authorizationHeader = mkHeader HdrAuthorization . ("OAuth "++) . urlEncodeParams $ authorizationParameters -- Authorizationヘッダ生成
contentLengthHeader
= mkHeader HdrContentLength
(show . length . urlEncodeVars
$ args
) -- Request を構成
rqURI = uri,
rqMethod = method,
rqHeaders = if method == POST then [authorizationHeader, contentLengthHeader] else [authorizationHeader] ,
rqBody = if method == POST then urlEncodeVars args else ""
}
-- simpleHTTP のIO版
simpleHTTPIO
:: HStream a
=> Request a
-> IO (Response a
)simpleHTTPIO req = do
res <- simpleHTTP req
case res of
Right res
' -> if rspCode res' == (2, 0, 0) then return res
' else fail.show $ res'
-- ツイートする(Ctrl+Cで抜ける)
main
_loop
:: OAuth
-> IO ()main_loop oauth = do
tweet <- apiRequest oauth "update" POST [("status", encodeString content)]
res <- simpleHTTPIO tweet
main_loop oauth
main = do
-- Consumer Key / Consumer Secret読み込み
fin <- openFile "./config.ini" ReadMode
(consumerKey:consumerSecret:
_) <- fmap lines $ hGetContents fin
let oauth_ = OAuth consumerKey consumerSecret "" ""
-- リクエストトークン発行要求リクエスト生成
requestForGetRequestToken <- oauthRequest oauth_ requestTokenURL "" []
-- リクエストトークン取得
requestTokenParameters
<- (fmap $ parseParameter
. rspBody
) . simpleHTTPIO
$ requestForGetRequestToken
requestToken <- getParameter requestTokenParameters "oauth_token"
requestTokenSecret <- getParameter requestTokenParameters "oauth_token_secret"
-- 認証ページのアドレス表示
putStrLn $ authorizeURL
++ "?" ++ urlEncodeVars
[requestToken
] -- PIN入力 -> oauth_verifierパラメータとして束縛
verifier
<- ("oauth_varifier",) <$> getLine -- アクセストークン発行要求リクエスト生成
requestForGetAccessToken
<- oauthRequest oauth
_ accessTokenURL
(snd requestTokenSecret
) [requestToken
, verifier
] -- アクセストークン取得
accessTokenParameters
<- (fmap $ parseParameter
. rspBody
) . simpleHTTPIO
$ requestForGetAccessToken
accessToken <- getParameter accessTokenParameters "oauth_token"
accessTokenSecret <- getParameter accessTokenParameters "oauth_token_secret"
let oauth
= OAuth consumerKey consumerSecret
(snd accessToken
) (snd accessTokenSecret
) main_loop oauth
ey0jIExBTkdVQUdFIFR1cGxlU2VjdGlvbnMgIy19CmltcG9ydCBOZXR3b3JrLkhUVFAKaW1wb3J0IE5ldHdvcmsuVVJJCmltcG9ydCBEYXRhLk1heWJlCmltcG9ydCBEYXRhLkxpc3QKaW1wb3J0IFN5c3RlbS5JTwppbXBvcnQgU3lzdGVtLlJhbmRvbQppbXBvcnQgU3lzdGVtLlRpbWUKaW1wb3J0IENvbnRyb2wuQXJyb3cKaW1wb3J0IENvbnRyb2wuQXBwbGljYXRpdmUKaW1wb3J0IERhdGEuRGlnZXN0LlB1cmUuU0hBCmltcG9ydCBDb2RlYy5CaW5hcnkuVVRGOC5TdHJpbmcgKGVuY29kZVN0cmluZykKaW1wb3J0IERhdGEuQ2hhcgppbXBvcnQgcXVhbGlmaWVkIENvZGVjLkJpbmFyeS5CYXNlNjQgYXMgQjY0CmltcG9ydCBxdWFsaWZpZWQgRGF0YS5CeXRlU3RyaW5nLkxhenkgYXMgTAoKLS0gT0F1dGjlnosKZGF0YSBPQXV0aCA9IE9BdXRoIHsKICAgICAgY29uc3VtZXJLZXkgOjogU3RyaW5nLAogICAgICBjb25zdW1lclNlY3JldCA6OiBTdHJpbmcsCiAgICAgIGFjY2Vzc1Rva2VuIDo6IFN0cmluZywKICAgICAgYWNjZXNzVG9rZW5TZWNyZXQgOjogU3RyaW5nCiAgICB9Ci0tIOODkeODqeODoeODvOOCv+Weiwp0eXBlIFBhcmFtZXRlciA9IChTdHJpbmcsIFN0cmluZykKCi0tIOODkeODqeODoeODvOOCv+OCkuODkeODvOOCuQpwYXJzZVBhcmFtZXRlciA6OiBTdHJpbmcgLT4gW1BhcmFtZXRlcl0KcGFyc2VQYXJhbWV0ZXIgPSBtYXAgc3BsaXRCeUVxdWFsIC4gc3BsaXRCeUFuZAogICAgd2hlcmUKICAgICAgc3BsaXRCeUFuZCBzdHIgPSBjYXNlIHNwYW4gKC89ICcmJykgc3RyIG9mCiAgICAgICAgICAgICAgICAgICAgICAgICAoeCwgIiIpIC0+IFt4XQogICAgICAgICAgICAgICAgICAgICAgICAgKHgsIF86eHMpIC0+IHggOiBzcGxpdEJ5QW5kIHhzCiAgICAgIHNwbGl0QnlFcXVhbCA9IHNlY29uZCB0YWlsIC4gc3BhbiAoLz0gJz0nKQoKLS0g44OR44Op44Oh44O844K/44Gu44Oq44K544OI44GL44KJ54m55a6a44Gu44OR44Op44Oh44O844K/44KS5Y+W5b6XCmdldFBhcmFtZXRlciA6OiBNb25hZCBtID0+IFtQYXJhbWV0ZXJdIC0+IFN0cmluZyAtPiBtIFBhcmFtZXRlcgpnZXRQYXJhbWV0ZXIgcGFyYW1ldGVycyBwYXJhbWV0ZXJOYW1lID0KICAgIGNhc2UgZmluZCAoKD09IHBhcmFtZXRlck5hbWUpIC4gZnN0KSBwYXJhbWV0ZXJzIG9mCiAgICAgIEp1c3QgcGFyYW1ldGVyIC0+IHJldHVybiBwYXJhbWV0ZXIKICAgICAgTm90aGluZyAtPiBmYWlsICJQYXJhbWV0ZXIgbm90IGZvdW5kLiIKCi0tIOODkeODqeODoeODvOOCv+OCkiAnPScg44Gn57WQ5ZCI44GX44CBICcsJyDljLrliIfjgorjgafkuKbjgbnjgosKdXJsRW5jb2RlUGFyYW1zIDo6IFtQYXJhbWV0ZXJdIC0+IFN0cmluZwp1cmxFbmNvZGVQYXJhbXMgcGFyYW1ldGVycyA9IGludGVyY2FsYXRlICIsICIgLiBtYXAgKFwoeCwgeSkgLT4gdXJsRW5jb2RlIHggKysgIj0iICsrIGRvdWJsZVF1b3RlICh1cmxFbmNvZGUgeSkpICQgcGFyYW1ldGVycwogICAgd2hlcmUKICAgICAgZG91YmxlUXVvdGUgc3RyID0gIlwiIiArKyBzdHIgKysgIlwiIgoKLS0g44Oq44Kv44Ko44K544OI44Gu572y5ZCN44KS55Sf5oiQCmdlblNpZ25hdHVyZSA6OiBTdHJpbmcgLT4gU3RyaW5nIC0+IFJlcXVlc3RNZXRob2QgLT4gU3RyaW5nIC0+IFtQYXJhbWV0ZXJdIC0+IFN0cmluZwpnZW5TaWduYXR1cmUgY29uc3VtZXJTZWNyZXQgdG9rZW5TZWNyZXQgbWV0aG9kIHVyaSBwYXJhbWV0ZXJzID0KICAgIGxldCBwYXJhbWV0ZXJzJyA9IHVybEVuY29kZSAuIHVybEVuY29kZVZhcnMgLiBzb3J0ICQgcGFyYW1ldGVycyAtLSDjg5Hjg6njg6Hjg7zjgr/jga7jgr3jg7zjg4jjg7vntZDlkIjjg7vjgqjjg7PjgrPjg7zjg4kKICAgICAgICBzaWduYXR1cmVLZXkgPSBMLnBhY2sgLiBtYXAgKGZyb21JbnRlZ3JhbCAuIG9yZCkgJCB1cmxFbmNvZGUgY29uc3VtZXJTZWNyZXQgKysgIiYiICsrIHVybEVuY29kZSB0b2tlblNlY3JldCAtLSDnvbLlkI3jgq3jg7znlJ/miJAKICAgICAgICBzaWduYXR1cmVCYXNlU3RyaW5nID0gTC5wYWNrIC4gbWFwIChmcm9tSW50ZWdyYWwgLiBvcmQpICQgdXJsRW5jb2RlIChzaG93IG1ldGhvZCkgKysgIiYiICsrIHVybEVuY29kZSB1cmkgKysgIiYiICsrIHBhcmFtZXRlcnMnIC0tIOe9suWQjeWvvuixoeaWh+Wtl+WIl+eUn+aIkAogICAgaW4KICAgICAgICBCNjQuZW5jb2RlIC4gTC51bnBhY2sgLiBieXRlc3RyaW5nRGlnZXN0ICQgaG1hY1NoYTEgc2lnbmF0dXJlS2V5IHNpZ25hdHVyZUJhc2VTdHJpbmcgLS0gSE1BQy1TSEEx44Ki44Or44K044Oq44K644Og44Gn44OA44Kk44K444Kn44K544OI5YCk44KS55Sf5oiQ44O7QmFzZTY044Gn44Ko44Oz44Kz44O844OJCgotLSDjg6rjgq/jgqjjgrnjg4jjg4jjg7zjgq/jg7Plj5blvpdVUkwKcmVxdWVzdFRva2VuVVJMID0gImh0dHA6Ly9hLi4uY29udGVudC1hdmFpbGFibGUtdG8tYXV0aG9yLW9ubHkuLi5yLmNvbS9vYXV0aC9yZXF1ZXN0X3Rva2VuIgphdXRob3JpemVVUkwgPSAiaHR0cDovL2EuLi5jb250ZW50LWF2YWlsYWJsZS10by1hdXRob3Itb25seS4uLnIuY29tL29hdXRoL2F1dGhvcml6ZSIKLS0g44Ki44Kv44K744K544OI44O844Kv44Oz5Y+W5b6XVVJMCmFjY2Vzc1Rva2VuVVJMID0gImh0dHA6Ly9hLi4uY29udGVudC1hdmFpbGFibGUtdG8tYXV0aG9yLW9ubHkuLi5yLmNvbS9vYXV0aC9hY2Nlc3NfdG9rZW4iCgotLSBBUEnjg6rjgq/jgqjjgrnjg4hVUkwKYXBpUmVxdWVzdFVSTCA6OiBTdHJpbmcgLT4gU3RyaW5nCmFwaVJlcXVlc3RVUkwgYXBpID0gImh0dHA6Ly9hLi4uY29udGVudC1hdmFpbGFibGUtdG8tYXV0aG9yLW9ubHkuLi5yLmNvbS8xL3N0YXR1c2VzLyIgKysgYXBpICsrICIuanNvbiIKCi0tIE9BdXRoIFJlcXVlc3Qg55Sf5oiQCm9hdXRoUmVxdWVzdCA6OiBPQXV0aCAtPiBTdHJpbmcgLT4gU3RyaW5nIC0+IFtQYXJhbWV0ZXJdIC0+IElPIFJlcXVlc3RfU3RyaW5nCm9hdXRoUmVxdWVzdCBvYXV0aCB1cmwgdG9rZW4gcGFyYW1ldGVyID0gZG8KICAgIGxldCBrZXkgPSBjb25zdW1lcktleSBvYXV0aCAtLSBDb25zdW1lciBrZXkKICAgICAgICBzZWNyZXQgPSBjb25zdW1lclNlY3JldCBvYXV0aCAtLSBDb25zdW1lciBTZWNyZXQKICAgICAgICB1cmkgPSBmcm9tSnVzdCAuIHBhcnNlVVJJICQgdXJsIC0tIFVSSQogICAgdGltZXN0YW1wIDwtIHNob3cgLiAoXChUT0QgaSBfKSAtPiBpKSA8JD4gZ2V0Q2xvY2tUaW1lIC0tIOOCv+OCpOODoOOCueOCv+ODs+ODl+WPluW+lwogICAgbm9uY2UgPC0gc2hvdyA8JD4gcmFuZG9tUklPICgwLCBtYXhCb3VuZDo6SW50KSAtLSDkubHmlbDlj5blvpcKICAgIGxldCBhdXRob3JpemF0aW9uUGFyYW1ldGVyc18gPSBwYXJhbWV0ZXIgKysgWwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICgib2F1dGhfY29uc3VtZXJfa2V5Iiwga2V5KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoIm9hdXRoX25vbmNlIiwgbm9uY2UpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICgib2F1dGhfdGltZXN0YW1wIiwgdGltZXN0YW1wKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoIm9hdXRoX3NpZ25hdHVyZV9tZXRob2QiLCAiSE1BQy1TSEExIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKCJvYXV0aF92ZXJzaW9uIiwgIjEuMCIpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBdIC0tIOWQhOeoruWfuuacrOODkeODqeODoeODvOOCv+OCkuOCu+ODg+ODiAogICAgICAgIHNpZ25hdHVyZSA9IGdlblNpZ25hdHVyZSBzZWNyZXQgdG9rZW4gUE9TVCB1cmwgYXV0aG9yaXphdGlvblBhcmFtZXRlcnNfIC0tIOe9suWQjeeUn+aIkAogICAgICAgIGF1dGhvcml6YXRpb25QYXJhbWV0ZXJzID0gYXV0aG9yaXphdGlvblBhcmFtZXRlcnNfKytbKCJvYXV0aF9zaWduYXR1cmUiLCBzaWduYXR1cmUpXSAtLSDnvbLlkI3jgpLjg5Hjg6njg6Hjg7zjgr/jgavliqDjgYjjgosKICAgICAgICBhdXRob3JpemF0aW9uSGVhZGVyID0gbWtIZWFkZXIgSGRyQXV0aG9yaXphdGlvbiAuICgiT0F1dGggIisrKSAuIHVybEVuY29kZVBhcmFtcyAkIGF1dGhvcml6YXRpb25QYXJhbWV0ZXJzIC0tIEF1dGhvcml6YXRpb27jg5jjg4Pjg4DnlJ/miJAKICAgIC0tIFJlcXVlc3Qg44KS5qeL5oiQCiAgICByZXR1cm4gJCBSZXF1ZXN0IHsKICAgICAgICAgICAgICAgICBycVVSSSA9IHVyaSwKICAgICAgICAgICAgICAgICBycU1ldGhvZCA9IFBPU1QsCiAgICAgICAgICAgICAgICAgcnFIZWFkZXJzID0gW2F1dGhvcml6YXRpb25IZWFkZXJdLAogICAgICAgICAgICAgICAgIHJxQm9keSA9ICIiCiAgICAgICAgICAgICAgIH0KCi0tIEFQSeODquOCr+OCqOOCueODiAphcGlSZXF1ZXN0IDo6IE9BdXRoIC0+IFN0cmluZyAtPiBSZXF1ZXN0TWV0aG9kIC0+IFtQYXJhbWV0ZXJdIC0+IElPIFJlcXVlc3RfU3RyaW5nCmFwaVJlcXVlc3Qgb2F1dGggYXBpIG1ldGhvZCBhcmdzID0gZG8KICAgIGxldCBrZXkgPSBjb25zdW1lcktleSBvYXV0aCAtLSBDb25zdW1lciBrZXkKICAgICAgICB0b2tlbiA9IGFjY2Vzc1Rva2VuIG9hdXRoIC0tIEFjY2Vzc1Rva2VuCiAgICAgICAgc2VjcmV0X0NvbnN1bWVyID0gY29uc3VtZXJTZWNyZXQgb2F1dGggLS0gQ29uc3VtZXIgU2VjcmV0CiAgICAgICAgc2VjcmV0X0FjY2Vzc1Rva2VuID0gYWNjZXNzVG9rZW5TZWNyZXQgb2F1dGggLS0gQWNjZXNzVG9rZW4gU2VjcmV0CiAgICAgICAgdXJsID0gYXBpUmVxdWVzdFVSTCBhcGkKICAgICAgICB1cmkgPSBmcm9tSnVzdCAuIHBhcnNlVVJJICQgaWYgbWV0aG9kID09IFBPU1QgdGhlbiB1cmwgZWxzZSB1cmwgKysgIj8iICsrIHVybEVuY29kZVZhcnMgYXJncyAgLS0gVVJJCiAgICB0aW1lc3RhbXAgPC0gc2hvdyAuIChcKFRPRCBpIF8pIC0+IGkpIDwkPiBnZXRDbG9ja1RpbWUgLS0g44K/44Kk44Og44K544K/44Oz44OX5Y+W5b6XCiAgICBub25jZSA8LSBzaG93IDwkPiByYW5kb21SSU8gKDAsIG1heEJvdW5kOjpJbnQpIC0tIOS5seaVsOWPluW+lwogICAgbGV0IGF1dGhvcml6YXRpb25QYXJhbWV0ZXJzXyA9IFsKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKCJvYXV0aF90b2tlbiIsIHRva2VuKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKCJvYXV0aF9jb25zdW1lcl9rZXkiLCBrZXkpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoIm9hdXRoX25vbmNlIiwgbm9uY2UpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAoIm9hdXRoX3RpbWVzdGFtcCIsIHRpbWVzdGFtcCksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICgib2F1dGhfc2lnbmF0dXJlX21ldGhvZCIsICJITUFDLVNIQTEiKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgKCJvYXV0aF92ZXJzaW9uIiwgIjEuMCIpCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXSAtLSDlkITnqK7ln7rmnKzjg5Hjg6njg6Hjg7zjgr/jgpLjgrvjg4Pjg4gKICAgICAgICBzaWduYXR1cmUgPSBnZW5TaWduYXR1cmUgc2VjcmV0X0NvbnN1bWVyIHNlY3JldF9BY2Nlc3NUb2tlbiBtZXRob2QgdXJsIChhcmdzICsrIGF1dGhvcml6YXRpb25QYXJhbWV0ZXJzXykgLS0g572y5ZCN55Sf5oiQCiAgICAgICAgYXV0aG9yaXphdGlvblBhcmFtZXRlcnMgPSBhdXRob3JpemF0aW9uUGFyYW1ldGVyc18rK1soIm9hdXRoX3NpZ25hdHVyZSIsIHNpZ25hdHVyZSldIC0tIOe9suWQjeOCkuODkeODqeODoeODvOOCv+OBq+WKoOOBiOOCiwogICAgICAgIGF1dGhvcml6YXRpb25IZWFkZXIgPSBta0hlYWRlciBIZHJBdXRob3JpemF0aW9uIC4gKCJPQXV0aCAiKyspIC4gdXJsRW5jb2RlUGFyYW1zICQgYXV0aG9yaXphdGlvblBhcmFtZXRlcnMgLS0gQXV0aG9yaXphdGlvbuODmOODg+ODgOeUn+aIkAogICAgICAgIGNvbnRlbnRMZW5ndGhIZWFkZXIgPSBta0hlYWRlciBIZHJDb250ZW50TGVuZ3RoIChzaG93IC4gbGVuZ3RoIC4gdXJsRW5jb2RlVmFycyAkIGFyZ3MpCiAgICAtLSBSZXF1ZXN0IOOCkuani+aIkAogICAgcmV0dXJuICQgUmVxdWVzdCB7CiAgICAgICAgICAgICAgICAgcnFVUkkgPSB1cmksCiAgICAgICAgICAgICAgICAgcnFNZXRob2QgPSBtZXRob2QsCiAgICAgICAgICAgICAgICAgcnFIZWFkZXJzID0gaWYgbWV0aG9kID09IFBPU1QgdGhlbiBbYXV0aG9yaXphdGlvbkhlYWRlciwgY29udGVudExlbmd0aEhlYWRlcl0gZWxzZSBbYXV0aG9yaXphdGlvbkhlYWRlcl0gLAogICAgICAgICAgICAgICAgIHJxQm9keSA9IGlmIG1ldGhvZCA9PSBQT1NUIHRoZW4gdXJsRW5jb2RlVmFycyBhcmdzIGVsc2UgIiIKICAgICAgICAgICAgICAgfQoKLS0gc2ltcGxlSFRUUCDjga5JT+eJiApzaW1wbGVIVFRQSU8gOjogSFN0cmVhbSBhID0+IFJlcXVlc3QgYSAtPiBJTyAoUmVzcG9uc2UgYSkKc2ltcGxlSFRUUElPIHJlcSA9IGRvCiAgcmVzIDwtIHNpbXBsZUhUVFAgcmVxCiAgY2FzZSByZXMgb2YKICAgIFJpZ2h0IHJlcycgLT4gaWYgcnNwQ29kZSByZXMnID09ICgyLCAwLCAwKSB0aGVuIHJldHVybiByZXMnIGVsc2UgZmFpbC5zaG93ICQgcmVzJwogICAgTGVmdCBlcnIgLT4gZmFpbC5zaG93ICQgZXJyCgotLSDjg4TjgqTjg7zjg4jjgZnjgosoQ3RybCtD44Gn5oqc44GR44KLKQptYWluX2xvb3AgOjogT0F1dGggLT4gSU8gKCkKbWFpbl9sb29wIG9hdXRoID0gZG8KICBjb250ZW50IDwtIGdldExpbmUKICB0d2VldCA8LSBhcGlSZXF1ZXN0IG9hdXRoICJ1cGRhdGUiIFBPU1QgWygic3RhdHVzIiwgZW5jb2RlU3RyaW5nIGNvbnRlbnQpXQogIHJlcyA8LSBzaW1wbGVIVFRQSU8gdHdlZXQKICBtYWluX2xvb3Agb2F1dGgKCm1haW4gOjogSU8gKCkKbWFpbiA9IGRvCiAgLS0gQ29uc3VtZXIgS2V5IC8gQ29uc3VtZXIgU2VjcmV06Kqt44G/6L6844G/CiAgZmluIDwtIG9wZW5GaWxlICIuL2NvbmZpZy5pbmkiIFJlYWRNb2RlCiAgKGNvbnN1bWVyS2V5OmNvbnN1bWVyU2VjcmV0Ol8pIDwtIGZtYXAgbGluZXMgJCBoR2V0Q29udGVudHMgZmluCiAgbGV0IG9hdXRoXyA9IE9BdXRoIGNvbnN1bWVyS2V5IGNvbnN1bWVyU2VjcmV0ICIiICIiCiAgLS0g44Oq44Kv44Ko44K544OI44OI44O844Kv44Oz55m66KGM6KaB5rGC44Oq44Kv44Ko44K544OI55Sf5oiQCiAgcmVxdWVzdEZvckdldFJlcXVlc3RUb2tlbiA8LSBvYXV0aFJlcXVlc3Qgb2F1dGhfIHJlcXVlc3RUb2tlblVSTCAiIiBbXQogIC0tIOODquOCr+OCqOOCueODiOODiOODvOOCr+ODs+WPluW+lwogIHJlcXVlc3RUb2tlblBhcmFtZXRlcnMgPC0gKGZtYXAgJCBwYXJzZVBhcmFtZXRlciAuIHJzcEJvZHkpIC4gc2ltcGxlSFRUUElPICQgcmVxdWVzdEZvckdldFJlcXVlc3RUb2tlbgogIHJlcXVlc3RUb2tlbiA8LSBnZXRQYXJhbWV0ZXIgcmVxdWVzdFRva2VuUGFyYW1ldGVycyAib2F1dGhfdG9rZW4iCiAgcmVxdWVzdFRva2VuU2VjcmV0IDwtIGdldFBhcmFtZXRlciByZXF1ZXN0VG9rZW5QYXJhbWV0ZXJzICJvYXV0aF90b2tlbl9zZWNyZXQiCiAgLS0g6KqN6Ki844Oa44O844K444Gu44Ki44OJ44Os44K56KGo56S6CiAgcHV0U3RyTG4gJCBhdXRob3JpemVVUkwgKysgIj8iICsrIHVybEVuY29kZVZhcnMgW3JlcXVlc3RUb2tlbl0KICAtLSBQSU7lhaXlipsgLT4gb2F1dGhfdmVyaWZpZXLjg5Hjg6njg6Hjg7zjgr/jgajjgZfjgabmnZ/nuJsKICB2ZXJpZmllciA8LSAoIm9hdXRoX3ZhcmlmaWVyIiwpIDwkPiBnZXRMaW5lCiAgLS0g44Ki44Kv44K744K544OI44O844Kv44Oz55m66KGM6KaB5rGC44Oq44Kv44Ko44K544OI55Sf5oiQCiAgcmVxdWVzdEZvckdldEFjY2Vzc1Rva2VuIDwtIG9hdXRoUmVxdWVzdCBvYXV0aF8gYWNjZXNzVG9rZW5VUkwgKHNuZCByZXF1ZXN0VG9rZW5TZWNyZXQpIFtyZXF1ZXN0VG9rZW4sIHZlcmlmaWVyXQogIC0tIOOCouOCr+OCu+OCueODiOODvOOCr+ODs+WPluW+lwogIGFjY2Vzc1Rva2VuUGFyYW1ldGVycyA8LSAoZm1hcCAkIHBhcnNlUGFyYW1ldGVyIC4gcnNwQm9keSkgLiBzaW1wbGVIVFRQSU8gJCByZXF1ZXN0Rm9yR2V0QWNjZXNzVG9rZW4KICBhY2Nlc3NUb2tlbiA8LSBnZXRQYXJhbWV0ZXIgYWNjZXNzVG9rZW5QYXJhbWV0ZXJzICJvYXV0aF90b2tlbiIKICBhY2Nlc3NUb2tlblNlY3JldCA8LSBnZXRQYXJhbWV0ZXIgYWNjZXNzVG9rZW5QYXJhbWV0ZXJzICJvYXV0aF90b2tlbl9zZWNyZXQiCiAgbGV0IG9hdXRoID0gT0F1dGggY29uc3VtZXJLZXkgY29uc3VtZXJTZWNyZXQgKHNuZCBhY2Nlc3NUb2tlbikgKHNuZCBhY2Nlc3NUb2tlblNlY3JldCkKICBtYWluX2xvb3Agb2F1dGgKICA=