Skip to content

Commit

Permalink
New badge: f-droid (#1965)
Browse files Browse the repository at this point in the history
  • Loading branch information
niccokunzmann authored and PyvesB committed Aug 30, 2018
1 parent e37c0b6 commit 11fa611
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 12 deletions.
22 changes: 22 additions & 0 deletions services/base-http.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
'use strict'

// See available emoji at http://emoji.muan.co/
const emojic = require('emojic')
const { checkErrorResponse } = require('../lib/error-helper')
const BaseService = require('./base')
const trace = require('./trace')

class BaseHTTPService extends BaseService {
async _requestHTTP({ url, options = {}, errorMessages = {} }) {
const logTrace = (...args) => trace.logTrace('fetch', ...args)
logTrace(emojic.bowAndArrow, 'Request', url, '\n', options)
return this._sendAndCacheRequest(url, options)
.then(({ res, buffer }) => {
logTrace(emojic.dart, 'Response status code', res.statusCode)
return { res, buffer }
})
.then(checkErrorResponse.asPromise(errorMessages))
}
}

module.exports = BaseHTTPService
14 changes: 4 additions & 10 deletions services/base-json.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
// See available emoji at http://emoji.muan.co/
const emojic = require('emojic')
const Joi = require('joi')
const { checkErrorResponse, asJson } = require('../lib/error-helper')
const BaseService = require('./base')
const { asJson } = require('../lib/error-helper')
const BaseHTTPService = require('./base-http')
const { InvalidResponse } = require('./errors')
const trace = require('./trace')

class BaseJsonService extends BaseService {
class BaseJsonService extends BaseHTTPService {
static _validate(json, schema) {
const { error, value } = Joi.validate(json, schema, {
allowUnknown: true,
Expand Down Expand Up @@ -46,13 +46,7 @@ class BaseJsonService extends BaseService {
...{ headers: { Accept: 'application/json' } },
...options,
}
logTrace(emojic.bowAndArrow, 'Request', url, '\n', mergedOptions)
return this._sendAndCacheRequest(url, mergedOptions)
.then(({ res, buffer }) => {
logTrace(emojic.dart, 'Response status code', res.statusCode)
return { res, buffer }
})
.then(checkErrorResponse.asPromise(errorMessages))
return this._requestHTTP({ url, mergedOptions, errorMessages })
.then(asJson)
.then(json => {
logTrace(emojic.dart, 'Response JSON (before validation)', json, {
Expand Down
14 changes: 12 additions & 2 deletions services/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,19 @@ class BaseService {
static _makeStaticExampleUrl(serviceData) {
const badgeData = this._makeBadgeData({}, serviceData)
const color = badgeData.colorscheme || badgeData.colorB
return this._makeStaticExampleUrlFromTextAndColor(
badgeData.text[0],
badgeData.text[1],
color
)
}

static _makeStaticExampleUrlFromTextAndColor(text1, text2, color) {
return `/badge/${encodeURIComponent(
badgeData.text[0]
)}-${encodeURIComponent(badgeData.text[1])}-${color}`
text1.replace('-', '--')
)}-${encodeURIComponent(text2).replace('-', '--')}-${encodeURIComponent(
color
)}`
}

/**
Expand Down
29 changes: 29 additions & 0 deletions services/base.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -392,4 +392,33 @@ describe('BaseService', function() {
})
})
})

describe('a generated static badge url', function() {
it('is concatenated text and color', function() {
const url = DummyService._makeStaticExampleUrlFromTextAndColor(
'name',
'value',
'green'
)
expect(url).to.equal('/badge/name-value-green')
})
it('uses url encoding', function() {
const url = DummyService._makeStaticExampleUrlFromTextAndColor(
'Hello World',
'Привет Мир',
'#aabbcc'
)
expect(url).to.equal(
'/badge/Hello%20World-%D0%9F%D1%80%D0%B8%D0%B2%D0%B5%D1%82%20%D0%9C%D0%B8%D1%80-%23aabbcc'
)
})
it('uses escapes minus signs', function() {
const url = DummyService._makeStaticExampleUrlFromTextAndColor(
'123-123',
'abc-abc',
'blue'
)
expect(url).to.equal('/badge/123--123-abc--abc-blue')
})
})
})
79 changes: 79 additions & 0 deletions services/f-droid/f-droid.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
'use strict'

const BaseHTTPService = require('../base-http')
const { addv: versionText } = require('../../lib/text-formatters')
const { version: versionColor } = require('../../lib/color-formatters')
const { InvalidResponse } = require('../errors')

module.exports = class FDroid extends BaseHTTPService {
async fetch({ appId }) {
// currently, we only use the txt format. There are few apps using the yml format.
const url = `https://gitlab.com/fdroid/fdroiddata/raw/master/metadata/${appId}.txt`
return this._requestHTTP({
url,
options: {},
errorMessages: {
404: 'app not found',
},
}).then(({ res, buffer }) => {
const metadata = buffer.toString()
// we assume the layout as provided here:
// https://gitlab.com/fdroid/fdroiddata/raw/master/metadata/axp.tool.apkextractor.txt
const positionOfCurrentVersionAtEndOfTheFile = metadata.lastIndexOf(
'Current Version:'
) // credits: https://stackoverflow.com/a/11134049
const lastVersion = metadata.substring(
positionOfCurrentVersionAtEndOfTheFile
)
const match = lastVersion.match(/^Current Version:\s*(.*?)\s*$/m)
if (!match) {
throw new InvalidResponse({
prettyMessage: 'invalid response',
underlyingError: new Error('could not find version on website'),
})
}
return { version: match[1] }
})
}

static render({ version }) {
return {
message: versionText(version),
color: versionColor(version),
}
}

async handle({ appId }) {
const result = await this.fetch({ appId })
return this.constructor.render(result)
}

// Metadata
static get defaultBadgeData() {
return { label: 'f-droid' }
}

static get category() {
return 'version'
}

static get url() {
return {
base: 'f-droid/v',
format: '(.+)',
capture: ['appId'],
}
}

static get examples() {
return [
{
title: 'F-Droid',
exampleUrl: 'org.thosp.yourlocalweather',
urlPattern: ':appId',
staticExample: this.render({ version: '1.0' }),
keywords: ['fdroid', 'android', 'app'],
},
]
}
}
99 changes: 99 additions & 0 deletions services/f-droid/f-droid.tester.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
'use strict'

const { isVPlusDottedVersionAtLeastOne } = require('../test-validators')
const ServiceTester = require('../service-tester')
const t = new ServiceTester({ id: 'f-droid', title: 'F-Droid' })
const Joi = require('joi')
module.exports = t

const testString =
'Categories:System\n' +
'License:MIT\n' +
'Web Site:https:/axxapy/apkExtractor/blob/HEAD/README.md\n' +
'Source Code:https:/axxapy/apkExtractor\n' +
'Issue Tracker:https:/axxapy/apkExtractor/issues\n' +
'\n' +
'Auto Name:Apk Extractor\n' +
'Summary:Get APK files from installed apps\n' +
'Description:\n' +
'Extract APKs from your device, even if installed from the Playstore. Root access\n' +
'is required for paid apps.\n' +
'\n' +
'* Fast and easy to use.\n' +
'* Extracts almost all applications, includes system applications.\n' +
'* ROOT access only required for extracting paid apps.\n' +
"* Apk's will be saved in /sdcard/Download/Eimon/.\n" +
'* Provided Search option to search applications.\n' +
'* Compatible with latest version of Android 6.0\n' +
'* Saved apk format : AppPackageName.apk.\n' +
'Current Version:1.8\n' +
'.\n' +
'\n' +
'Repo Type:git\n' +
'Repo:https:/axxapy/apkExtractor\n' +
'\n' +
'Build:1.0,1\n' +
' commit=9b3b62c3ceda74b17eaa22c9e4f893aac10c4442\n' +
' gradle=yes\n' +
'\n' +
'Build:1.1,2\n' +
' commit=1.1\n' +
' gradle=yes\n' +
'\n' +
'Build:1.2,3\n' +
' disable=lintVitalRelease fails\n' +
' commit=1.2\n' +
' gradle=yes\n' +
'\n' +
'Build:1.3,4\n' +
' commit=1.3\n' +
' gradle=yes\n' +
'\n' +
'Build:1.4,5\n' +
' commit=1.4\n' +
' gradle=yes\n' +
'\n' +
'Auto Update Mode:Version %v\n' +
'Update Check Mode:Tags\n' +
'Current Version:1.4\n' +
'Current Version Code:5\n'
const base = 'https://gitlab.com'
const path = '/fdroid/fdroiddata/raw/master/metadata/axp.tool.apkextractor.txt'

t.create('Package is found')
.get('/v/axp.tool.apkextractor.json')
.intercept(nock =>
nock(base)
.get(path)
.reply(200, testString)
)
.expectJSON({ name: 'f-droid', value: 'v1.4' })

t.create('Package is not found')
.get('/v/axp.tool.apkextractor.json')
.intercept(nock =>
nock(base)
.get(path)
.reply(404, testString)
)
.expectJSON({ name: 'f-droid', value: 'app not found' })

t.create('The api changed')
.get('/v/axp.tool.apkextractor.json')
.intercept(nock =>
nock(base)
.get(path)
.reply(200, '')
)
.expectJSON({ name: 'f-droid', value: 'invalid response' })

/* If this test fails, either the API has changed or the app was deleted. */
t.create('The real api did not change')
.get('/v/org.thosp.yourlocalweather.json')
.timeout(10000)
.expectJSONTypes(
Joi.object().keys({
name: 'f-droid',
value: isVPlusDottedVersionAtLeastOne,
})
)

0 comments on commit 11fa611

Please sign in to comment.