bin.jsView |
---|
33 | 33 … | } |
34 | 34 … | return JSON.stringify(draftMsg, null, 2).length |
35 | 35 … | } |
36 | 36 … | |
37 | | -function getJson(url, cb) { |
| 37 … | +function get(url, cb) { |
38 | 38 … | var opts = URL.parse(url) |
39 | 39 … | opts.headers = { |
40 | 40 … | 'User-Agent': userAgentBase |
41 | 41 … | + (userAgentContact ? ' (' + userAgentContact + ')' : '') |
52 | 52 … | bufs.push(buf) |
53 | 53 … | }) |
54 | 54 … | res.on('end', function () { |
55 | 55 … | res.removeListener('error', cb) |
56 | | - var buf = Buffer.concat(bufs) |
57 | | - bufs = null |
58 | | - var data |
| 56 … | + var str |
59 | 57 … | try { |
60 | | - data = JSON.parse(buf.toString('utf8')) |
| 58 … | + str = Buffer.concat(bufs).toString('utf8') |
61 | 59 … | } catch(e) { |
62 | 60 … | return cb(e) |
63 | 61 … | } |
64 | | - cb(null, data) |
| 62 … | + cb(null, str) |
65 | 63 … | }) |
66 | 64 … | res.on('error', cb) |
67 | 65 … | }) |
68 | 66 … | } |
69 | 67 … | |
| 68 … | +function getJson(url, cb) { |
| 69 … | + get(url, function (err, str) { |
| 70 … | + var data |
| 71 … | + try { |
| 72 … | + data = JSON.parse(str) |
| 73 … | + } catch(e) { |
| 74 … | + return cb(e) |
| 75 … | + } |
| 76 … | + cb(null, data) |
|
| 77 … | + }) |
| 78 … | +} |
| 79 … | + |
70 | 80 … | function publishDrafts(sbot, drafts, cb) { |
71 | 81 … | var draftIdIndex = {} |
72 | 82 … | drafts.forEach(function (draft, i) { |
73 | 83 … | draftIdIndex[draft.draftId] = i |
124 | 134 … | process.stdout.write(fs.readFileSync(path.join(__dirname, 'usage.txt'))) |
125 | 135 … | process.exit(0) |
126 | 136 … | } |
127 | 137 … | |
| 138 … | +var apiHrefRe = /(?:>api\.php<|<link rel="EditURI").* href="([^"]+\/api\.php)(?:[?#][^"]*)?".*/ |
| 139 … | + |
128 | 140 … | ssbClient(function (err, sbot, config) { |
129 | 141 … | if (err) throw err |
130 | 142 … | var conf = config.wikimedia || {} |
131 | 143 … | userAgentContact = conf.contact |
140 | 152 … | return sbot.close() |
141 | 153 … | } |
142 | 154 … | } |
143 | 155 … | |
| 156 … | + var siteWikiBases = {} |
144 | 157 … | var pagesInfo = urls.map(function (page) { |
145 | | - var m = /^(https?:\/\/.*?)(\/wiki)?\/(.*)$/.exec(page) |
| 158 … | + |
| 159 … | + var m = /^(https?:\/\/.*?\/)(wiki\/)?(.*)$/.exec(page) |
146 | 160 … | if (!m) throw 'Unable to parse page URL ' + page |
147 | 161 … | return { |
148 | | - site: m[1] + '/', |
149 | | - api: m[1] + (m[2] ? '/w' : '/wiki') + '/api.php', |
| 162 … | + url: page, |
| 163 … | + site: m[1], |
| 164 … | + wikiBase: m[1] + (m[2] || ''), |
150 | 165 … | title: m[3] |
151 | 166 … | } |
152 | 167 … | }) |
153 | | - var pagesInfoByApi = {} |
| 168 … | + |
| 169 … | + var pagesInfoBySite = {} |
154 | 170 … | pagesInfo.forEach(function (pageInfo) { |
155 | | - var infos = pagesInfoByApi[pageInfo.api] || (pagesInfoByApi[pageInfo.api] = []) |
| 171 … | + var infos = pagesInfoBySite[pageInfo.site] |
| 172 … | + || (pagesInfoBySite[pageInfo.site] = []) |
156 | 173 … | infos.push(pageInfo) |
| 174 … | + siteWikiBases[pageInfo.site] = pageInfo.wikiBase |
157 | 175 … | }) |
158 | | - console.log('Normalizing titles...') |
159 | | - var waiting = 0 |
160 | | - for (var api in pagesInfoByApi) (function (api) { |
161 | | - var pagesInfoForApi = pagesInfoByApi[api] |
162 | | - var pagesInfoForApiByTitle = {} |
163 | | - var titles = pagesInfoForApi.map(function (info) { |
164 | | - pagesInfoForApiByTitle[info.title] = info |
165 | | - return info.title |
166 | | - }) |
167 | | - var url = api + '?format=json&action=query' + |
168 | | - '&titles=' + encodeURIComponent('\x1f' + titles.join('\x1f')) + |
169 | | - '&' |
170 | | - waiting++ |
171 | | - getJson(url, function (err, data) { |
172 | | - if (err) throw err |
173 | | - if (data.warnings) console.trace('Warnings:', data.warnings) |
174 | | - if (data.query.normalized) data.query.normalized.forEach(function (norm) { |
175 | | - var info = pagesInfoForApiByTitle[norm.from] |
176 | | - if (!info) { |
177 | | - console.error(JSON.stringify({titles: titles, response: data}, 0, 2)) |
178 | | - throw new Error('Unexpected title in server response') |
179 | | - } |
180 | | - |
181 | | - info.title = norm.to |
| 176 … | + |
| 177 … | + var apiBySite = {} |
| 178 … | + findApis() |
| 179 … | + |
| 180 … | + function findApis() { |
| 181 … | + console.log('Finding Wikimedia APIs...') |
| 182 … | + |
| 183 … | + |
| 184 … | + |
| 185 … | + |
| 186 … | + |
| 187 … | + |
| 188 … | + var waiting = 0 |
| 189 … | + for (var site in pagesInfoBySite) (function (site) { |
| 190 … | + waiting++ |
| 191 … | + var base = siteWikiBases[site] |
| 192 … | + var url = base + 'Special:Version' |
| 193 … | + get(url, function (err, html) { |
| 194 … | + if (err) throw err |
| 195 … | + var m = apiHrefRe.exec(html) |
| 196 … | + if (!m) throw new Error('Unable to find api.php for ' + site) |
| 197 … | + var api = URL.resolve(url, m[1]) |
| 198 … | + apiBySite[site] = api |
| 199 … | + if (!--waiting) normalizeTitles() |
182 | 200 … | }) |
183 | | - if (!--waiting) next() |
184 | | - }) |
185 | | - }(api)) |
| 201 … | + }(site)) |
| 202 … | + } |
186 | 203 … | |
187 | | - function next() { |
| 204 … | + function normalizeTitles() { |
| 205 … | + console.log('Normalizing titles...') |
| 206 … | + var waiting = 0 |
| 207 … | + for (var site in pagesInfoBySite) (function (site) { |
| 208 … | + var pagesInfoForSite = pagesInfoBySite[site] |
| 209 … | + var pagesInfoForSiteByTitle = {} |
| 210 … | + var titles = pagesInfoForSite.map(function (info) { |
| 211 … | + pagesInfoForSiteByTitle[info.title] = info |
| 212 … | + return info.title |
| 213 … | + }) |
| 214 … | + var api = apiBySite[site] |
| 215 … | + var url = api |
| 216 … | + + '?format=json&action=query' |
| 217 … | + + '&titles=' + encodeURIComponent('\x1f' + titles.join('\x1f')) |
| 218 … | + + '&' |
| 219 … | + waiting++ |
| 220 … | + getJson(url, function (err, data) { |
| 221 … | + if (err) throw err |
| 222 … | + if (data.warnings) console.trace('Warnings:', data.warnings) |
| 223 … | + if (data.query.normalized) data.query.normalized.forEach(function (norm) { |
| 224 … | + var info = pagesInfoForSiteByTitle[norm.from] |
| 225 … | + if (!info) { |
| 226 … | + console.error(JSON.stringify({titles: titles, response: data}, 0, 2)) |
| 227 … | + throw new Error('Unexpected title in server response') |
| 228 … | + } |
| 229 … | + |
| 230 … | + info.title = norm.to |
| 231 … | + }) |
| 232 … | + if (!--waiting) getRevisions() |
| 233 … | + }) |
| 234 … | + }(site)) |
| 235 … | + } |
| 236 … | + |
| 237 … | + function getRevisions() { |
188 | 238 … | console.log('Getting revisions...') |
189 | 239 … | var userHashes = {} |
190 | 240 … | pull( |
191 | 241 … | pull.values(pagesInfo), |
248 | 298 … | if (rvdone) return cb(true) |
249 | 299 … | |
250 | 300 … | console.log('Getting revisions for', pageInfo.title + '...', |
251 | 301 … | rvstart || '', rvcontinue || '') |
252 | | - var url = api + '?format=json&action=query&prop=revisions&rvslots=*' |
| 302 … | + var url = apiBySite[pageInfo.site] |
| 303 … | + + '?format=json&action=query&prop=revisions&rvslots=*' |
253 | 304 … | + '&titles=' + encodeURIComponent(pageInfo.title) |
254 | 305 … | + '&rvprop=ids|timestamp|comment|user|sha1|size|slotsha1|slotsize|content|roles|flags|tags' |
255 | 306 … | + '&rvdir=newer' |
256 | 307 … | + (rvcontinue ? '&rvcontinue=' + rvcontinue : '') |