diff --git a/.eslintrc.js b/.eslintrc.js
index 8ff448efd..f91394cce 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -2,19 +2,18 @@ module.exports = {
root: true,
env: {
browser: true,
- node: true
+ node: true,
},
parserOptions: {
- parser: 'babel-eslint'
+ parser: 'babel-eslint',
},
extends: [
'@nuxtjs',
'prettier',
'prettier/vue',
'plugin:prettier/recommended',
- 'plugin:nuxt/recommended'
- ],
- plugins: [
- 'prettier'
+ 'plugin:nuxt/recommended',
+ 'plugin:json/recommended',
],
+ plugins: ['prettier'],
}
diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml
new file mode 100644
index 000000000..e9041ab8f
--- /dev/null
+++ b/.github/workflows/validate.yml
@@ -0,0 +1,25 @@
+name: Validate
+on:
+ push:
+ pull_request:
+
+jobs:
+ validate:
+ name: Validate
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v2.3.3
+ - uses: actions/setup-node@v2.1.2
+ with:
+ node-version: '14'
+ - name: Restore npm cache
+ uses: actions/cache@v2
+ with:
+ path: ~/.npm
+ key: ${{ runner.os }}-node-${{ hashFiles('**/package.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-
+ - name: Install dependencies
+ run: npm install
+ - name: Validate
+ run: npm run validate
diff --git a/bin/validate.js b/bin/validate.js
index cdf931123..7c9c9b238 100755
--- a/bin/validate.js
+++ b/bin/validate.js
@@ -6,174 +6,167 @@ const { technologies, categories } = JSON.parse(
fs.readFileSync('./src/technologies.json')
)
-try {
- Object.keys(technologies).forEach((name) => {
- const technology = technologies[name]
-
- // Validate regular expressions
- ;['url', 'html', 'meta', 'headers', 'cookies', 'script', 'js'].forEach(
- (type) => {
- if (technology[type]) {
- const keyed =
- typeof technology[type] === 'string' ||
- Array.isArray(technology[type])
- ? { _: technology[type] }
- : technology[type]
-
- Object.keys(keyed).forEach((key) => {
- const patterns = Array.isArray(keyed[key])
- ? keyed[key]
- : [keyed[key]]
-
- patterns.forEach((pattern, index) => {
- const id = `${name}: ${type}[${key === '_' ? `${index}` : key}]`
-
- const [regex, ...flags] = pattern.split('\\;')
-
- let maxGroups = 0
-
- flags.forEach((flag) => {
- const [key, value] = flag.split(':')
-
- if (key === 'version') {
- const refs = value.match(/\\(\d+)/g)
-
- if (refs) {
- maxGroups = refs.reduce((max, ref) =>
- Math.max(max, parseInt(refs[1] || 0))
- )
- }
- } else if (key === 'confidence') {
- if (
- !/^\d+$/.test(value) ||
- parseInt(value, 10) < 0 ||
- parseInt(value, 10) > 99
- ) {
- throw new Error(
- `Confidence value must a number between 0 and 99: ${value} (${id})`
- )
- }
- } else {
- throw new Error(`Invalid flag: ${key} (${id})`)
- }
- })
-
- // Validate regular expression
- try {
- // eslint-disable-next-line no-new
- new RegExp(regex)
- } catch (error) {
- throw new Error(`${error.message} (${id})`)
- }
-
- // Count capture groups
- const groups = new RegExp(`${regex}|`).exec('').length - 1
+Object.keys(technologies).forEach((name) => {
+ const technology = technologies[name]
- if (groups > maxGroups) {
- throw new Error(
- `Too many non-capturing groups, expected ${maxGroups}: ${regex} (${id})`
- )
- }
+ // Validate regular expressions
+ ;['url', 'html', 'meta', 'headers', 'cookies', 'script', 'js'].forEach(
+ (type) => {
+ if (technology[type]) {
+ const keyed =
+ typeof technology[type] === 'string' ||
+ Array.isArray(technology[type])
+ ? { _: technology[type] }
+ : technology[type]
- if (type === 'html' && !/[<>]/.test(regex)) {
- throw new Error(
- `HTML pattern must include < or >: ${regex} (${id})`
- )
- }
- })
- })
- }
- }
- )
+ Object.keys(keyed).forEach((key) => {
+ const patterns = Array.isArray(keyed[key]) ? keyed[key] : [keyed[key]]
- // Validate categories
- technology.cats.forEach((id) => {
- if (!categories[id]) {
- throw new Error(`No such category: ${id} (${name})`)
- }
- })
+ patterns.forEach((pattern, index) => {
+ const id = `${name}: ${type}[${key === '_' ? `${index}` : key}]`
- // Validate icons
- if (technology.icon && !fs.existsSync(`${iconPath}/${technology.icon}`)) {
- throw new Error(`No such icon: ${technology.icon} (${name})`)
- }
+ const [regex, ...flags] = pattern.split('\\;')
- // Validate website URLs
- try {
- // eslint-disable-next-line no-new
- const { protocol } = new URL(technology.website)
+ let maxGroups = 0
- if (protocol !== 'http:' && protocol !== 'https:') {
- throw new Error('Invalid protocol')
- }
- } catch (error) {
- throw new Error(`Invalid website URL: ${technology.website} (${name})`)
- }
+ flags.forEach((flag) => {
+ const [key, value] = flag.split(':')
- // Validate implies and excludes
- const { implies, excludes } = technology
+ if (key === 'version') {
+ const refs = value.match(/\\(\d+)/g)
- if (implies) {
- ;(Array.isArray(implies) ? implies : [implies]).forEach((implied) => {
- const [_name, ...flags] = implied.split('\\;')
+ if (refs) {
+ maxGroups = refs.reduce((max, ref) =>
+ Math.max(max, parseInt(refs[1] || 0))
+ )
+ }
+ } else if (key === 'confidence') {
+ if (
+ !/^\d+$/.test(value) ||
+ parseInt(value, 10) < 0 ||
+ parseInt(value, 10) > 99
+ ) {
+ throw new Error(
+ `Confidence value must a number between 0 and 99: ${value} (${id})`
+ )
+ }
+ } else {
+ throw new Error(`Invalid flag: ${key} (${id})`)
+ }
+ })
- const id = `${name}: implies[${implied}]`
+ // Validate regular expression
+ try {
+ // eslint-disable-next-line no-new
+ new RegExp(regex)
+ } catch (error) {
+ throw new Error(`${error.message} (${id})`)
+ }
- if (!technologies[_name]) {
- throw new Error(`Implied technology does not exist: ${_name} (${id})`)
- }
+ // Count capture groups
+ const groups = new RegExp(`${regex}|`).exec('').length - 1
- flags.forEach((flag) => {
- const [key, value] = flag.split(':')
+ if (groups > maxGroups) {
+ throw new Error(
+ `Too many non-capturing groups, expected ${maxGroups}: ${regex} (${id})`
+ )
+ }
- if (key === 'confidence') {
- if (
- !/^\d+$/.test(value) ||
- parseInt(value, 10) < 0 ||
- parseInt(value, 10) > 99
- ) {
+ if (type === 'html' && !/[<>]/.test(regex)) {
throw new Error(
- `Confidence value must a number between 0 and 99: ${value} (${id})`
+ `HTML pattern must include < or >: ${regex} (${id})`
)
}
- } else {
- throw new Error(`Invalid flag: ${key} (${id})`)
- }
+ })
})
- })
+ }
}
+ )
- if (excludes) {
- ;(Array.isArray(excludes) ? excludes : [excludes]).forEach((excluded) => {
- const id = `${name}: excludes[${excluded}]`
-
- if (!technologies[excluded]) {
- throw new Error(
- `Excluded technology does not exist: ${excluded} (${id})`
- )
- }
- })
+ // Validate categories
+ technology.cats.forEach((id) => {
+ if (!categories[id]) {
+ throw new Error(`No such category: ${id} (${name})`)
}
})
// Validate icons
- fs.readdirSync(iconPath).forEach((file) => {
- const filePath = `${iconPath}/${file}`
+ if (technology.icon && !fs.existsSync(`${iconPath}/${technology.icon}`)) {
+ throw new Error(`No such icon: ${technology.icon} (${name})`)
+ }
+
+ // Validate website URLs
+ try {
+ // eslint-disable-next-line no-new
+ const { protocol } = new URL(technology.website)
+
+ if (protocol !== 'http:' && protocol !== 'https:') {
+ throw new Error('Invalid protocol')
+ }
+ } catch (error) {
+ throw new Error(`Invalid website URL: ${technology.website} (${name})`)
+ }
+
+ // Validate implies and excludes
+ const { implies, excludes } = technology
+
+ if (implies) {
+ ;(Array.isArray(implies) ? implies : [implies]).forEach((implied) => {
+ const [_name, ...flags] = implied.split('\\;')
+
+ const id = `${name}: implies[${implied}]`
- if (fs.statSync(filePath).isFile() && !file.startsWith('.')) {
- if (!/^(png|svg)$/i.test(file.split('.').pop())) {
- throw new Error(`Incorrect file type, expected PNG or SVG: ${filePath}`)
+ if (!technologies[_name]) {
+ throw new Error(`Implied technology does not exist: ${_name} (${id})`)
}
- if (
- !Object.values(technologies).some(({ icon }) => icon === file) &&
- file !== 'default.svg'
- ) {
- throw new Error(`Extraneous file: ${filePath}}`)
+ flags.forEach((flag) => {
+ const [key, value] = flag.split(':')
+
+ if (key === 'confidence') {
+ if (
+ !/^\d+$/.test(value) ||
+ parseInt(value, 10) < 0 ||
+ parseInt(value, 10) > 99
+ ) {
+ throw new Error(
+ `Confidence value must a number between 0 and 99: ${value} (${id})`
+ )
+ }
+ } else {
+ throw new Error(`Invalid flag: ${key} (${id})`)
+ }
+ })
+ })
+ }
+
+ if (excludes) {
+ ;(Array.isArray(excludes) ? excludes : [excludes]).forEach((excluded) => {
+ const id = `${name}: excludes[${excluded}]`
+
+ if (!technologies[excluded]) {
+ throw new Error(
+ `Excluded technology does not exist: ${excluded} (${id})`
+ )
}
+ })
+ }
+})
+
+// Validate icons
+fs.readdirSync(iconPath).forEach((file) => {
+ const filePath = `${iconPath}/${file}`
+
+ if (fs.statSync(filePath).isFile() && !file.startsWith('.')) {
+ if (!/^(png|svg)$/i.test(file.split('.').pop())) {
+ throw new Error(`Incorrect file type, expected PNG or SVG: ${filePath}`)
}
- })
-} catch (error) {
- // eslint-disable-next-line no-console
- console.error(error.message)
-}
+
+ if (
+ !Object.values(technologies).some(({ icon }) => icon === file) &&
+ file !== 'default.svg'
+ ) {
+ throw new Error(`Extraneous file: ${filePath}}`)
+ }
+ }
+})
diff --git a/package.json b/package.json
index c24bd1c32..6e6ff2e39 100644
--- a/package.json
+++ b/package.json
@@ -8,15 +8,17 @@
"@nuxtjs/eslint-config": "^3.1.0",
"@nuxtjs/eslint-module": "^2.0.0",
"babel-eslint": "^10.1.0",
- "eslint": "^7.7.0",
- "eslint-config-prettier": "^6.11.0",
+ "eslint": "^7.13.0",
+ "eslint-config-prettier": "^6.15.0",
+ "eslint-plugin-json": "^2.1.2",
"eslint-plugin-nuxt": "^1.0.0",
"eslint-plugin-prettier": "^3.1.4",
- "prettier": "^2.1.0"
+ "prettier": "^2.1.2"
},
"scripts": {
"link": "node ./bin/link.js",
- "lint": "eslint --fix src/**/*.js",
+ "lint": "eslint src/**/*.{js,json}",
+ "lint:fix": "eslint --fix src/**/*.{js,json}",
"validate": "yarn run lint && jsonlint -qV ./schema.json ./src/technologies.json && node ./bin/validate.js",
"convert": "cd ./src/drivers/webextension/images/icons ; cp *.svg converted ; cd converted ; convert-svg-to-png *.svg --width 32 --height 32 ; rm *.svg",
"prettify": "jsonlint -si --trim-trailing-commas --enforce-double-quotes ./src/technologies.json",
diff --git a/src/drivers/webextension/_locales/ca/messages.json b/src/drivers/webextension/_locales/ca/messages.json
index 0ea5132c7..9df2f83aa 100644
--- a/src/drivers/webextension/_locales/ca/messages.json
+++ b/src/drivers/webextension/_locales/ca/messages.json
@@ -93,6 +93,6 @@
"categoryName71": { "message": "Affiliate program" },
"categoryName72": { "message": "Appointment scheduling" },
"categoryName73": { "message": "Surveys" },
- "categoryName75": { "message": "A/B testing" },
+ "categoryName74": { "message": "A/B testing" },
"categoryName75": { "message": "Email" }
}
diff --git a/src/drivers/webextension/images/icons/Ochanoko.svg b/src/drivers/webextension/images/icons/Ochanoko.svg
new file mode 100644
index 000000000..2d878dd2c
--- /dev/null
+++ b/src/drivers/webextension/images/icons/Ochanoko.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/drivers/webextension/images/icons/Oxatis.svg b/src/drivers/webextension/images/icons/Oxatis.svg
new file mode 100644
index 000000000..f999989fa
--- /dev/null
+++ b/src/drivers/webextension/images/icons/Oxatis.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/drivers/webextension/images/icons/Plataforma NEO.svg b/src/drivers/webextension/images/icons/Plataforma NEO.svg
deleted file mode 100644
index 5a112f6e0..000000000
--- a/src/drivers/webextension/images/icons/Plataforma NEO.svg
+++ /dev/null
@@ -1,4 +0,0 @@
-
\ No newline at end of file
diff --git a/src/drivers/webextension/images/icons/Profitwell.svg b/src/drivers/webextension/images/icons/Profitwell.svg
new file mode 100644
index 000000000..379810e3d
--- /dev/null
+++ b/src/drivers/webextension/images/icons/Profitwell.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/src/drivers/webextension/images/icons/ThriveCart.svg b/src/drivers/webextension/images/icons/ThriveCart.svg
new file mode 100644
index 000000000..48852f2c3
--- /dev/null
+++ b/src/drivers/webextension/images/icons/ThriveCart.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/drivers/webextension/images/icons/Visualsoft.svg b/src/drivers/webextension/images/icons/Visualsoft.svg
new file mode 100644
index 000000000..5096b2fdb
--- /dev/null
+++ b/src/drivers/webextension/images/icons/Visualsoft.svg
@@ -0,0 +1,9 @@
+
diff --git a/src/drivers/webextension/images/icons/apostrophecms.svg b/src/drivers/webextension/images/icons/apostrophecms.svg
deleted file mode 100644
index 8ccef8d6c..000000000
--- a/src/drivers/webextension/images/icons/apostrophecms.svg
+++ /dev/null
@@ -1,33 +0,0 @@
-
diff --git a/src/drivers/webextension/js/driver.js b/src/drivers/webextension/js/driver.js
index e1eb75ab9..81557cddc 100644
--- a/src/drivers/webextension/js/driver.js
+++ b/src/drivers/webextension/js/driver.js
@@ -54,7 +54,7 @@ const Driver = {
),
tabs: {},
robots: await getOption('robots', {}),
- ads: {},
+ ads: [],
}
chrome.browserAction.setBadgeBackgroundColor({ color: '#6B39BD' }, () => {})
diff --git a/src/technologies.json b/src/technologies.json
index 139de686a..1e8a78b3b 100644
--- a/src/technologies.json
+++ b/src/technologies.json
@@ -806,7 +806,8 @@
18
],
"cookies": {
- "cookie_name": "adonis-session"
+ "adonis-session": "",
+ "adonis-session-values": ""
},
"icon": "AdonisJS.png",
"implies": "Node.js",
@@ -2547,7 +2548,10 @@
"_bsap": "",
"_bsap_serving_callback": ""
},
- "scripts": "^https?://s\\d\\.buysellads\\.com/",
+ "scripts": [
+ "^https?://s\\d\\.buysellads\\.com/",
+ "servedby-buysellads\\.com/monetization(?:\\.[\\w\\d]+)?\\.js"
+ ],
"website": "http://buysellads.com"
},
"CCV Shop": {
@@ -6683,6 +6687,15 @@
},
"website": "https://www.hubspot.com"
},
+ "HubSpot Analytics": {
+ "cats": [
+ 10
+ ],
+ "description": "HubSpot is a marketing and sales software that helps companies attract visitors, convert leads, and close customers.",
+ "icon": "HubSpot.png",
+ "scripts": "js\\.hs-analytics\\.net/analytics",
+ "website": "https://www.hubspot.com/products/marketing/analytics"
+ },
"Hugo": {
"cats": [
57
@@ -9961,6 +9974,18 @@
},
"website": "https://en.oxid-esales.com/en/home.html"
},
+ "Ochanoko": {
+ "cats": [
+ 6
+ ],
+ "description": "Ochanoko is a ecommerce online shopping cart solutions, ecommerce web site hosting.",
+ "icon": "Ochanoko.svg",
+ "js": {
+ "ocnkProducts": ""
+ },
+ "scripts": "ocnk-min\\.js",
+ "website": "https://www.ocnk.com"
+ },
"October CMS": {
"cats": [
1
@@ -10454,6 +10479,17 @@
"url": "/owa/auth/log(?:on|off)\\.aspx",
"website": "http://help.outlook.com"
},
+ "Oxatis": {
+ "cats": [
+ 6
+ ],
+ "description": "Oxatis is a cloud-based ecommerce solution which enables users to create and manage their own online store websites.",
+ "icon": "Oxatis.svg",
+ "meta": {
+ "generator": "^Oxatis\\s\\(www\\.oxatis\\.com\\)$"
+ },
+ "website": "https://www.oxatis.com/"
+ },
"Oxygen": {
"cats": [
51
@@ -11258,6 +11294,16 @@
"scripts": "prism\\.js",
"website": "http://prismjs.com"
},
+ "Profitwell": {
+ "cats": [
+ 10
+ ],
+ "icon": "Profitwell.svg",
+ "scripts": [
+ "public\\.profitwell\\.com/js/profitwell\\.js"
+ ],
+ "website": "https://www.profitwell.com/"
+ },
"Project Wonderful": {
"cats": [
36
@@ -13373,7 +13419,7 @@
"SMARTSTORE.VISITOR": ""
},
"html": "",
- "icon": "smartstore.png",
+ "icon": "Smartstore.png",
"implies": "Microsoft ASP.NET",
"meta": {
"generator": "^SmartStore.NET (.+)$\\;version:\\1"
@@ -14522,6 +14568,18 @@
"implies": "PHP",
"website": "http://www.thinkphp.cn"
},
+ "ThriveCart": {
+ "cats": [
+ 6
+ ],
+ "description": "ThriveCart is a sales cart solution that lets you create checkout pages for your online products and services.",
+ "icon": "ThriveCart.svg",
+ "js": {
+ "ThriveCart": ""
+ },
+ "scripts": "thrivecart\\.js",
+ "website": "https://thrivecart.com"
+ },
"Ticimax": {
"cats": [
6
@@ -15347,6 +15405,21 @@
"scripts": "secure\\.checkout\\.visa\\.com",
"website": "https://checkout.visa.com"
},
+ "Visualsoft": {
+ "cats": [
+ 6
+ ],
+ "description": "Visualsoft is an ecommerce agency that delivers web design, development and marketing services to online retailers.",
+ "icon": "Visualsoft.svg",
+ "cookies": {
+ "vscommerce": ""
+ },
+ "meta": {
+ "vs_status_checker_version": "\\d+",
+ "vsvatprices": ""
+ },
+ "website": "https://www.visualsoft.co.uk/"
+ },
"Visual Website Optimizer": {
"cats": [
10