Skip to content

Commit

Permalink
feat: implement entity transfer
Browse files Browse the repository at this point in the history
  • Loading branch information
m90 committed Jul 31, 2024
1 parent e0502d6 commit 3e8641f
Show file tree
Hide file tree
Showing 6 changed files with 183 additions and 5 deletions.
35 changes: 34 additions & 1 deletion bin/cmd.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,36 @@
#!/usr/bin/env node

console.log('hello transferbot', process.argv.slice(2))
import WikibaseRepo from './../lib/repo.js'
import { pickLanguages, pickKeys } from './../lib/util.js'

void (async() => {
const [source, target, ...entities] = process.argv.slice(2)
const sourceRepo = new WikibaseRepo(source)
const targetRepo = new WikibaseRepo(target, {
oauth: {
consumer_key: process.env.TARGET_WIKI_OAUTH_CONSUMER_TOKEN,
consumer_secret: process.env.TARGET_WIKI_OAUTH_CONSUMER_SECRET,
token: process.env.TARGET_WIKI_OAUTH_ACCESS_TOKEN,
token_secret: process.env.TARGET_WIKI_OAUTH_ACCESS_SECRET
}
})

const contentLanguages = await targetRepo.getContentLanguages()

let data = await sourceRepo.getEntities(...entities)
data = data
.map(e => pickKeys(e, 'type', 'labels', 'descriptions', 'aliases', 'datatype'))
.map(e => pickLanguages(e, ...contentLanguages))

await targetRepo.createEntities(...data)
return `Sucessfully transferred ${entities.length} entities from ${source} to ${target}.`
})()
.then((result) => {
if (result) {
console.log(result)
}
})
.catch((err) => {
console.error(err)
process.exit(1)
})
52 changes: 52 additions & 0 deletions lib/repo.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import WBEdit from 'wikibase-edit'
import { WBK } from 'wikibase-sdk'

export default class WikibaseRepository {
constructor(origin, opts = {}) {
this.origin = origin

this.read = new WBK({
instance: origin
})

if (opts.oauth) {
this.edit = new WBEdit({
instance: origin,
credentials: {
oauth: opts.oauth
}
})
}
}

getContentLanguages() {
return fetch(`${this.origin}/w/api.php?action=query&meta=wbcontentlanguages&format=json`)
.then(r => r.json())
.then(body => Object.keys(body.query.wbcontentlanguages))
}

async createEntities(...entities) {
if (!this.edit) {
throw new Error('Cannot edit a read only instance.')
}
return Promise.all(entities.map(async entity => {
return this.edit.entity.create(entity)
}))
}

getEntities(...identifiers) {
return Promise.all(identifiers.map(async identifier => {
const [entityId, revision] = identifier.split('@')
let url = revision
? await this.read.getEntityRevision({
id: entityId,
revision: revision
})
: await this.read.getEntities({
ids: [entityId]
})
const { entities } = await fetch(url).then(res => res.json())
return entities[entityId]
}))
}
}
31 changes: 31 additions & 0 deletions lib/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export const pickKeys = (obj, ...keys) => {
const result = {}
for (const [key, value] of Object.entries(obj)) {
if (keys.includes(key)) {
result[key] = value
}
}
return result
}

export const pickLanguages = (obj, ...languages) => {
const result = {}
for (const [key, value] of Object.entries(obj)) {
if (!isObject(value)) {
result[key] = value
continue
}
const filteredChild = {}
for (const [language, childValue] of Object.entries(value)) {
if (languages.includes(language)) {
filteredChild[language] = childValue
}
}
result[key] = filteredChild
}
return result
}

function isObject (x) {
return Object.prototype.toString.call(x) === '[object Object]'
}
3 changes: 3 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"wikibase-edit": "^7.0.4",
"wikibase-sdk": "^10.0.2"
},
"type": "module",
"bin": {
"transferbot": "./bin/cmd.js"
}
Expand Down
66 changes: 62 additions & 4 deletions test/test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,66 @@
#!/usr/bin/env node

const assert = require('assert')
const test = require('node:test')
import assert from 'assert'
import test from 'node:test'

test('setting this up', t => {
assert.ok(true)
import { pickKeys, pickLanguages } from './../lib/util.js'

test('util.pickKeys', async t => {
await t.test('empty', t => {
assert.deepStrictEqual(pickKeys({}), {})
})
await t.test('picks keys', t => {
const result = pickKeys({
foo: 'bar',
baz: {
foo: 'bar',
},
qux: 12
}, 'baz', 'qux')

assert.deepStrictEqual(result, {
baz: {
foo: 'bar'
},
qux: 12
})
})
})

test('util.pickLanguages', async t => {
await t.test('empty', t => {
assert.deepStrictEqual(pickLanguages({}), {})
})

await t.test('skip languages', t => {
const result = pickLanguages({
labels: {
en: {
language: 'en',
value: 'pipe',
},
fr: {
language: 'fr',
value: 'pipe',
},
de: {
language: 'de',
value: 'Pfeife',
}
}
}, 'en', 'fr')

assert.deepStrictEqual(result, {
labels: {
en: {
language: 'en',
value: 'pipe',
},
fr: {
language: 'fr',
value: 'pipe',
}
}
})
})
})

0 comments on commit 3e8641f

Please sign in to comment.