1 ! Copyright (C) 2021, 2022 Giftpflanze.
2 ! See http://factorcode.org/license.txt for BSD license.
3 USING: arrays accessors assocs assocs.extras calendar
4 combinators combinators.short-circuit continuations formatting
5 http http.client io json.reader kernel locals make math
6 math.parser namespaces oauth1 prettyprint sequences strings
10 TUPLE: oauth-login consumer-token consumer-secret access-token
12 TUPLE: password-login username password ;
14 C: <oauth-login> oauth-login
15 C: <password-login> password-login
17 SYMBOLS: botflag contact cookies csrf-token endpoint oauth-login
22 SYMBOLS: basetimestamp curtimestamp ;
30 : prepare ( params -- params' )
32 { [ dup t = ] [ drop "true" ] }
33 { [ dup number? ] [ number>string ] }
34 { [ dup string? ] [ ] }
37 { [ dup number? ] [ number>string ] }
43 : <api-request> ( params -- request )
48 } swap assoc-union prepare
51 contact get vm-version vm-git-id 7 head
52 "%s Factor/%s %s mediawiki.api" sprintf "User-Agent"
55 : oauth-post ( params -- response data )
58 over consumer-secret>> <token> consumer-token set
60 swap access-secret>> <token> access-token set
62 <oauth-request-params> set-oauth
65 : cookie-post* ( params -- assoc )
68 http-request [ cookies>> cookies set-global ] dip json> ;
70 : login-token ( -- token )
76 { "query" "tokens" "logintoken" } deep-of ;
78 : login ( -- cookies )
81 password-login get dup username>> "lgname" ,,
82 password>> "lgpassword" ,,
83 login-token "lgtoken" ,,
84 ] { } make cookie-post* drop cookies get ;
86 : cookie-post ( params -- response data )
88 cookies get [ login ] unless* >>cookies
91 : anon-post ( params -- response data )
92 <api-request> http-request ;
94 : code-200? ( response assoc -- ? )
95 over code>> dup 200 = dup [ 3nip ] [
96 -roll "http status code %d" printf
97 swap header>> [ "=" glue print ] assoc-each
102 : retry-after? ( response -- ? )
103 header>> "retry-after" of dup [ dup seconds sleep ] when ;
105 : nonce-already-used? ( assoc -- ? )
107 [ "code" of "mwoauth-invalid-authorization" = ]
108 [ "info" of "Nonce already used" subseq-of? ] bi
111 : readonly? ( assoc -- ? )
112 { "error" "code" } deep-of "readonly" = dup
113 [ 5 minutes sleep ] when ;
115 DEFER: get-csrf-token
116 : badtoken? ( assoc -- ? )
117 { "error" "code" } deep-of "badtoken" = dup
118 [ get-csrf-token drop ] when ;
120 : failed? ( response assoc -- response assoc ? )
123 [ drop retry-after? ]
124 [ nip nonce-already-used? ]
129 : dispatch-call ( params -- response data )
131 { [ oauth-login get ] [ oauth-post ] }
132 { [ password-login get ] [ cookie-post ] }
138 : api-call ( params -- assoc )
142 2drop dup dispatch-call
143 [ json> ] [ swap print rethrow ] recover
144 "warnings" "errors" [ over at [ ... ] when* ] bi@
149 :: (query) ( params -- obj assoc )
150 { { "action" "query" } } params assoc-union api-call dup
151 dup "query" of [ nip ] when*
152 "siprop" params key? [
153 params { "prop" "list" "meta" } values-of sift first of
158 :: call-continue ( params quot1: ( params -- obj assoc )
159 quot2: ( ... -- ... ) -- seq )
163 params assoc-union quot1 call
164 [ quot2 call >alist append ] dip
165 ] do while drop ; inline
167 : query ( params -- seq )
168 [ (query) ] [ ] call-continue ;
170 :: page-content ( title -- content )
173 { "prop" "revisions" }
174 { "rvprop" { "content" "timestamp" } }
180 [ "curtimestamp" of curtimestamp set-global ]
182 "query" of "pages" "revisions" [ of first ] bi@
183 [ "timestamp" of basetimestamp set-global ]
184 [ { "slots" "main" "content" } deep-of ] bi
189 : get-csrf-token ( -- csrf-token )
194 "csrftoken" of dup csrf-token set-global ;
198 : token-call ( params -- assoc )
201 csrf-token get [ get-csrf-token ] unless* "token" ,,
202 ] { } make api-call ;
204 :: edit-page ( title text summary params -- assoc )
210 curtimestamp get "now" or "starttimestamp" ,,
211 basetimestamp get "now" or "basetimestamp" ,,
213 botflag get { { "bot" t } } { } ?
214 params [ assoc-union ] bi@ token-call ;
216 :: move-page ( from to reason params -- assoc )
223 } params assoc-union token-call ;
225 :: email ( target subject text -- assoc )
227 { "action" "emailuser" }
229 { "subject" subject }