Files
shields/services/f-droid/f-droid.service.js
2018-12-07 00:45:19 -05:00

143 lines
4.0 KiB
JavaScript

'use strict'
const Joi = require('joi')
const yaml = require('js-yaml')
const BaseService = require('../base')
const { addv: versionText } = require('../../lib/text-formatters')
const { version: versionColor } = require('../../lib/color-formatters')
const { InvalidResponse } = require('../errors')
module.exports = class FDroid extends BaseService {
static render({ version }) {
return {
message: versionText(version),
color: versionColor(version),
}
}
async handle({ appId }, queryParams) {
const constructor = this.constructor
const { metadata_format: format } = constructor.validateParams(queryParams)
const url = `https://gitlab.com/fdroid/fdroiddata/raw/master/metadata/${appId}`
const fetchOpts = {
options: {},
errorMessages: {
404: 'app not found',
},
}
const fetch = format === 'yml' ? this.fetchYaml : this.fetchText
let result
try {
// currently, we only use the txt format to the initial fetch because
// there are more apps with that format but yml is now the standard format
// on f-droid, so if txt is not found we look for yml as the fallback
result = await fetch.call(this, url, fetchOpts)
} catch (error) {
if (format) {
// if the format was specified it doesn't make the fallback request
throw error
}
result = await this.fetchYaml(url, fetchOpts)
}
return constructor.render(result)
}
async fetchYaml(url, options) {
const { buffer } = await this._request({
url: `${url}.yml`,
...options,
})
// we assume the yaml layout as provided here:
// https://gitlab.com/fdroid/fdroiddata/raw/master/metadata/org.dystopia.email.yml
try {
const { CurrentVersion: version } = yaml.safeLoad(
buffer.toString(),
'utf8'
)
if (!version) {
throw new Error('could not find version on website')
}
return { version }
} catch (error) {
throw new InvalidResponse({
prettyMessage: 'invalid response',
underlyingError: error,
})
}
}
async fetchText(url, options) {
const { buffer } = await this._request({
url: `${url}.txt`,
...options,
})
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 validateParams(queryParams) {
const queryParamsSchema = Joi.object({
metadata_format: Joi.string().valid(['yml', 'txt']),
}).required()
return this._validateQueryParams(queryParams, queryParamsSchema)
}
// Metadata
static get defaultBadgeData() {
return { label: 'f-droid' }
}
static get category() {
return 'version'
}
static get route() {
return {
base: 'f-droid/v',
format: '(.+)',
capture: ['appId'],
queryParams: ['metadata_format'],
}
}
static get examples() {
return [
{
title: 'F-Droid',
exampleUrl: 'org.thosp.yourlocalweather',
pattern: ':appId',
staticExample: this.render({ version: '1.0' }),
keywords: ['fdroid', 'android', 'app'],
},
{
title: 'F-Droid (explicit metadata format)',
exampleUrl: 'org.dystopia.email',
pattern: ':appId',
queryParams: { metadata_format: 'yml' },
staticExample: this.render({ version: '1.2.1' }),
keywords: ['fdroid', 'android', 'app'],
},
]
}
}