New badge: f-droid (#1965)
This commit is contained in:
22
services/base-http.js
Normal file
22
services/base-http.js
Normal file
@@ -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
|
||||
@@ -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,
|
||||
@@ -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, {
|
||||
|
||||
@@ -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
|
||||
)}`
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
services/f-droid/f-droid.service.js
Normal file
79
services/f-droid/f-droid.service.js
Normal file
@@ -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
services/f-droid/f-droid.tester.js
Normal file
99
services/f-droid/f-droid.tester.js
Normal file
@@ -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://github.com/axxapy/apkExtractor/blob/HEAD/README.md\n' +
|
||||
'Source Code:https://github.com/axxapy/apkExtractor\n' +
|
||||
'Issue Tracker:https://github.com/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://github.com/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,
|
||||
})
|
||||
)
|
||||
Reference in New Issue
Block a user