diff --git a/assets/components/home/home.vue b/assets/components/home/home.vue
index 0b6c893d..22e45e65 100644
--- a/assets/components/home/home.vue
+++ b/assets/components/home/home.vue
@@ -76,11 +76,21 @@
>{{ release.site.name }}
{{ formatDate(release.date, 'MMM D, YYYY') }}
+
+ {{ `(${formatDate(release.dateAdded, 'MMM D, YYYY')})` }}`
diff --git a/assets/img/database-add.svg b/assets/img/database-add.svg
new file mode 100644
index 00000000..90af0adf
--- /dev/null
+++ b/assets/img/database-add.svg
@@ -0,0 +1,6 @@
+
+
diff --git a/assets/img/drawer-in.svg b/assets/img/drawer-in.svg
new file mode 100644
index 00000000..77214a68
--- /dev/null
+++ b/assets/img/drawer-in.svg
@@ -0,0 +1,6 @@
+
+
diff --git a/migrations/20190325001339_releases.js b/migrations/20190325001339_releases.js
index 7c063bff..32fa68b0 100644
--- a/migrations/20190325001339_releases.js
+++ b/migrations/20190325001339_releases.js
@@ -53,7 +53,7 @@ exports.up = knex => Promise.resolve()
table.string('name');
table.string('url');
table.text('description');
- table.enum('orientation', ['gay', 'bi', 'lesbian', 'trans']);
+ table.string('parameters');
table.string('slug', 32)
.unique();
diff --git a/package-lock.json b/package-lock.json
index e57595ad..547bbb99 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1462,6 +1462,11 @@
"integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==",
"dev": true
},
+ "abab": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.2.tgz",
+ "integrity": "sha512-2scffjvioEmNz0OyDSLGWDfKCVwaKc6l9Pm9kOIREU13ClXZvHpg/nRL5xyjSSSLhOnXqft2HpsAzNEEA8cFFg=="
+ },
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
@@ -1479,8 +1484,7 @@
"acorn": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.1.tgz",
- "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA==",
- "dev": true
+ "integrity": "sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA=="
},
"acorn-dynamic-import": {
"version": "4.0.0",
@@ -1488,17 +1492,30 @@
"integrity": "sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw==",
"dev": true
},
+ "acorn-globals": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz",
+ "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==",
+ "requires": {
+ "acorn": "^6.0.1",
+ "acorn-walk": "^6.0.1"
+ }
+ },
"acorn-jsx": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz",
"integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==",
"dev": true
},
+ "acorn-walk": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz",
+ "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA=="
+ },
"ajv": {
"version": "6.9.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.2.tgz",
"integrity": "sha512-4UFy0/LgDo7Oa/+wOAlj44tp9K78u38E5/359eSrqEp1Z5PdVfimCcs7SluXMP755RUQu6d2b4AvF0R1C9RZjg==",
- "dev": true,
"requires": {
"fast-deep-equal": "^2.0.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -1624,6 +1641,11 @@
"resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz",
"integrity": "sha1-p5SvDAWrF1KEbudTofIRoFugxE8="
},
+ "array-equal": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz",
+ "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM="
+ },
"array-find-index": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz",
@@ -1659,7 +1681,6 @@
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
- "dev": true,
"requires": {
"safer-buffer": "~2.1.0"
}
@@ -1705,8 +1726,7 @@
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
- "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=",
- "dev": true
+ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
},
"assign-symbols": {
"version": "1.0.0",
@@ -1737,11 +1757,15 @@
"integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=",
"dev": true
},
+ "async-limiter": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
+ "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
+ },
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=",
- "dev": true
+ "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"atob": {
"version": "2.1.2",
@@ -1765,14 +1789,12 @@
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
- "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=",
- "dev": true
+ "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
},
"aws4": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
- "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==",
- "dev": true
+ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
},
"axobject-query": {
"version": "2.0.2",
@@ -2366,7 +2388,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
- "dev": true,
"requires": {
"tweetnacl": "^0.14.3"
}
@@ -2568,6 +2589,11 @@
"integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=",
"dev": true
},
+ "browser-process-hrtime": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.3.tgz",
+ "integrity": "sha512-bRFnI4NnjO6cnyLmOV/7PVoDEMJChlcfN0z4s1YMBY989/SvlfMI1lgCnkFUs53e9gQF+w7qu7XdllSTiSl8Aw=="
+ },
"browserify-aes": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz",
@@ -2779,8 +2805,7 @@
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
- "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=",
- "dev": true
+ "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
"chalk": {
"version": "2.4.2",
@@ -3000,7 +3025,6 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
"integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
- "dev": true,
"requires": {
"delayed-stream": "~1.0.0"
}
@@ -3333,6 +3357,26 @@
"integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==",
"dev": true
},
+ "cssom": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.1.tgz",
+ "integrity": "sha512-6Aajq0XmukE7HdXUU6IoSWuH1H6gH9z6qmagsstTiN7cW2FNTsb+J2Chs+ufPgZCsV/yo8oaEudQLrb9dGxSVQ=="
+ },
+ "cssstyle": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.0.0.tgz",
+ "integrity": "sha512-QXSAu2WBsSRXCPjvI43Y40m6fMevvyRm8JVAuF9ksQz5jha4pWP1wpaK7Yu5oLFc6+XAY+hj8YhefyXcBB53gg==",
+ "requires": {
+ "cssom": "~0.3.6"
+ },
+ "dependencies": {
+ "cssom": {
+ "version": "0.3.8",
+ "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz",
+ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg=="
+ }
+ }
+ },
"csv": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/csv/-/csv-4.0.0.tgz",
@@ -3387,11 +3431,20 @@
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
- "dev": true,
"requires": {
"assert-plus": "^1.0.0"
}
},
+ "data-urls": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz",
+ "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==",
+ "requires": {
+ "abab": "^2.0.0",
+ "whatwg-mimetype": "^2.2.0",
+ "whatwg-url": "^7.0.0"
+ }
+ },
"date-now": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/date-now/-/date-now-0.1.4.tgz",
@@ -3430,8 +3483,7 @@
"deep-is": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
- "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
- "dev": true
+ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ="
},
"defaults": {
"version": "1.0.3",
@@ -3490,8 +3542,7 @@
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=",
- "dev": true
+ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"delegates": {
"version": "1.0.0",
@@ -3578,6 +3629,14 @@
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz",
"integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w=="
},
+ "domexception": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz",
+ "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==",
+ "requires": {
+ "webidl-conversions": "^4.0.2"
+ }
+ },
"domhandler": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz",
@@ -3611,7 +3670,6 @@
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
- "dev": true,
"requires": {
"jsbn": "~0.1.0",
"safer-buffer": "^2.1.0"
@@ -3751,6 +3809,31 @@
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ="
},
+ "escodegen": {
+ "version": "1.12.0",
+ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.12.0.tgz",
+ "integrity": "sha512-TuA+EhsanGcme5T3R0L80u4t8CpbXQjegRmf7+FPTJrtCTErXFeelblRgHQa1FofEzqYYJmJ/OqjTwREp9qgmg==",
+ "requires": {
+ "esprima": "^3.1.3",
+ "estraverse": "^4.2.0",
+ "esutils": "^2.0.2",
+ "optionator": "^0.8.1",
+ "source-map": "~0.6.1"
+ },
+ "dependencies": {
+ "esprima": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz",
+ "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM="
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "optional": true
+ }
+ }
+ },
"eslint": {
"version": "5.16.0",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz",
@@ -4080,8 +4163,7 @@
"estraverse": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz",
- "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=",
- "dev": true
+ "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM="
},
"esutils": {
"version": "2.0.2",
@@ -4421,26 +4503,22 @@
"extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
- "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
- "dev": true
+ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
"fast-deep-equal": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
- "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=",
- "dev": true
+ "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
},
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
- "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=",
- "dev": true
+ "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
},
"fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
- "dev": true
+ "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc="
},
"figgy-pudding": {
"version": "3.5.1",
@@ -4656,14 +4734,12 @@
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
- "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=",
- "dev": true
+ "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
},
"form-data": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
- "dev": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.6",
@@ -5428,7 +5504,6 @@
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
- "dev": true,
"requires": {
"assert-plus": "^1.0.0"
}
@@ -5553,14 +5628,12 @@
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
- "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=",
- "dev": true
+ "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
},
"har-validator": {
"version": "5.1.3",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
"integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
- "dev": true,
"requires": {
"ajv": "^6.5.5",
"har-schema": "^2.0.0"
@@ -5705,6 +5778,14 @@
"integrity": "sha512-7T/BxH19zbcCTa8XkMlbK5lTo1WtgkFi3GvdWEyNuc4Vex7/9Dqbnpsf4JMydcfj9HCg4zUWFTL3Za6lapg5/w==",
"dev": true
},
+ "html-encoding-sniffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz",
+ "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==",
+ "requires": {
+ "whatwg-encoding": "^1.0.1"
+ }
+ },
"htmlparser2": {
"version": "3.10.1",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz",
@@ -5745,7 +5826,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
- "dev": true,
"requires": {
"assert-plus": "^1.0.0",
"jsprim": "^1.2.2",
@@ -6252,8 +6332,7 @@
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
- "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=",
- "dev": true
+ "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
},
"is-unc-path": {
"version": "1.0.0",
@@ -6297,8 +6376,7 @@
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
- "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=",
- "dev": true
+ "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"js-base64": {
"version": "2.5.1",
@@ -6341,8 +6419,52 @@
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
- "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
- "dev": true
+ "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+ },
+ "jsdom": {
+ "version": "15.2.0",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-15.2.0.tgz",
+ "integrity": "sha512-+hRyEfjRPFwTYMmSQ3/f7U9nP8ZNZmbkmUek760ZpxnCPWJIhaaLRuUSvpJ36fZKCGENxLwxClzwpOpnXNfChQ==",
+ "requires": {
+ "abab": "^2.0.0",
+ "acorn": "^7.1.0",
+ "acorn-globals": "^4.3.2",
+ "array-equal": "^1.0.0",
+ "cssom": "^0.4.1",
+ "cssstyle": "^2.0.0",
+ "data-urls": "^1.1.0",
+ "domexception": "^1.0.1",
+ "escodegen": "^1.11.1",
+ "html-encoding-sniffer": "^1.0.2",
+ "nwsapi": "^2.1.4",
+ "parse5": "5.1.0",
+ "pn": "^1.1.0",
+ "request": "^2.88.0",
+ "request-promise-native": "^1.0.7",
+ "saxes": "^3.1.9",
+ "symbol-tree": "^3.2.2",
+ "tough-cookie": "^3.0.1",
+ "w3c-hr-time": "^1.0.1",
+ "w3c-xmlserializer": "^1.1.2",
+ "webidl-conversions": "^4.0.2",
+ "whatwg-encoding": "^1.0.5",
+ "whatwg-mimetype": "^2.3.0",
+ "whatwg-url": "^7.0.0",
+ "ws": "^7.0.0",
+ "xml-name-validator": "^3.0.0"
+ },
+ "dependencies": {
+ "acorn": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.1.0.tgz",
+ "integrity": "sha512-kL5CuoXA/dgxlBbVrflsflzQ3PAas7RYZB52NOm/6839iVYJgKMJ3cQJD+t2i5+qFa8h3MDpEOJiS64E8JLnSQ=="
+ },
+ "parse5": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.0.tgz",
+ "integrity": "sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ=="
+ }
+ }
},
"jsesc": {
"version": "2.5.2",
@@ -6358,14 +6480,12 @@
"json-schema": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
- "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=",
- "dev": true
+ "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
},
"json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
- "dev": true
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
},
"json-stable-stringify-without-jsonify": {
"version": "1.0.1",
@@ -6376,8 +6496,7 @@
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
- "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=",
- "dev": true
+ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"json5": {
"version": "2.1.0",
@@ -6406,7 +6525,6 @@
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
- "dev": true,
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
@@ -6526,7 +6644,6 @@
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
"integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
- "dev": true,
"requires": {
"prelude-ls": "~1.1.2",
"type-check": "~0.3.2"
@@ -6689,6 +6806,11 @@
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
},
+ "lodash.sortby": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz",
+ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg="
+ },
"lodash.tail": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.tail/-/lodash.tail-4.1.1.tgz",
@@ -7482,11 +7604,15 @@
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
},
+ "nwsapi": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.1.4.tgz",
+ "integrity": "sha512-iGfd9Y6SFdTNldEy2L0GUhcarIutFmk+MPWIn9dmj8NMIup03G08uUF2KGbbmv/Ux4RT0VZJoP/sVbWA6d/VIw=="
+ },
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
- "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==",
- "dev": true
+ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
},
"object-assign": {
"version": "4.1.1",
@@ -7665,7 +7791,6 @@
"version": "0.8.2",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz",
"integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=",
- "dev": true,
"requires": {
"deep-is": "~0.1.3",
"fast-levenshtein": "~2.0.4",
@@ -7968,8 +8093,7 @@
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
- "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=",
- "dev": true
+ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"pg": {
"version": "7.9.0",
@@ -8070,6 +8194,11 @@
"find-up": "^2.1.0"
}
},
+ "pn": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz",
+ "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA=="
+ },
"posix-character-classes": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz",
@@ -8253,8 +8382,7 @@
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
- "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
- "dev": true
+ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ="
},
"prepend-http": {
"version": "1.0.4",
@@ -8751,7 +8879,6 @@
"version": "2.88.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
- "dev": true,
"requires": {
"aws-sign2": "~0.7.0",
"aws4": "^1.8.0",
@@ -8778,20 +8905,17 @@
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
- "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==",
- "dev": true
+ "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
- "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=",
- "dev": true
+ "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
},
"tough-cookie": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
- "dev": true,
"requires": {
"psl": "^1.1.24",
"punycode": "^1.4.1"
@@ -8800,8 +8924,36 @@
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
- "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
- "dev": true
+ "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
+ }
+ }
+ },
+ "request-promise-core": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.2.tgz",
+ "integrity": "sha512-UHYyq1MO8GsefGEt7EprS8UrXsm1TxEvFUX1IMTuSLU2Rh7fTIdFtl8xD7JiEYiWU2dl+NYAjCTksTehQUxPag==",
+ "requires": {
+ "lodash": "^4.17.11"
+ }
+ },
+ "request-promise-native": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.7.tgz",
+ "integrity": "sha512-rIMnbBdgNViL37nZ1b3L/VfPOpSi0TqVDQPAvO6U14lMzOLrt5nilxCQqtDKhZeDiW0/hkCXGoQjhgJd/tCh6w==",
+ "requires": {
+ "request-promise-core": "1.1.2",
+ "stealthy-require": "^1.1.1",
+ "tough-cookie": "^2.3.3"
+ },
+ "dependencies": {
+ "tough-cookie": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
+ "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
+ "requires": {
+ "psl": "^1.1.28",
+ "punycode": "^2.1.1"
+ }
}
}
},
@@ -9190,6 +9342,14 @@
}
}
},
+ "saxes": {
+ "version": "3.1.11",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-3.1.11.tgz",
+ "integrity": "sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==",
+ "requires": {
+ "xmlchars": "^2.1.1"
+ }
+ },
"scheduler": {
"version": "0.13.6",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.13.6.tgz",
@@ -9682,7 +9842,6 @@
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
"integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
- "dev": true,
"requires": {
"asn1": "~0.2.3",
"assert-plus": "^1.0.0",
@@ -9737,6 +9896,11 @@
"readable-stream": "^2.0.1"
}
},
+ "stealthy-require": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
+ "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
+ },
"stream-browserify": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz",
@@ -9884,6 +10048,11 @@
"has-flag": "^3.0.0"
}
},
+ "symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw=="
+ },
"table": {
"version": "5.4.0",
"resolved": "https://registry.npmjs.org/table/-/table-5.4.0.tgz",
@@ -10131,6 +10300,14 @@
"punycode": "^2.1.1"
}
},
+ "tr46": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz",
+ "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=",
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
"trim-newlines": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-1.0.0.tgz",
@@ -10271,7 +10448,6 @@
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
- "dev": true,
"requires": {
"safe-buffer": "^5.0.1"
}
@@ -10279,14 +10455,12 @@
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
- "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
- "dev": true
+ "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
},
"type-check": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
- "dev": true,
"requires": {
"prelude-ls": "~1.1.2"
}
@@ -10461,7 +10635,6 @@
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
"integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
- "dev": true,
"requires": {
"punycode": "^2.1.0"
}
@@ -10557,7 +10730,6 @@
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
- "dev": true,
"requires": {
"assert-plus": "^1.0.0",
"core-util-is": "1.0.2",
@@ -10685,6 +10857,24 @@
"resolved": "https://registry.npmjs.org/vuex/-/vuex-3.1.1.tgz",
"integrity": "sha512-ER5moSbLZuNSMBFnEBVGhQ1uCBNJslH9W/Dw2W7GZN23UQA69uapP5GTT9Vm8Trc0PzBSVt6LzF3hGjmv41xcg=="
},
+ "w3c-hr-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz",
+ "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=",
+ "requires": {
+ "browser-process-hrtime": "^0.1.2"
+ }
+ },
+ "w3c-xmlserializer": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz",
+ "integrity": "sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==",
+ "requires": {
+ "domexception": "^1.0.1",
+ "webidl-conversions": "^4.0.2",
+ "xml-name-validator": "^3.0.0"
+ }
+ },
"watchpack": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz",
@@ -10704,6 +10894,11 @@
"defaults": "^1.0.3"
}
},
+ "webidl-conversions": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz",
+ "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg=="
+ },
"webpack": {
"version": "4.32.2",
"resolved": "https://registry.npmjs.org/webpack/-/webpack-4.32.2.tgz",
@@ -10872,6 +11067,29 @@
}
}
},
+ "whatwg-encoding": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz",
+ "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==",
+ "requires": {
+ "iconv-lite": "0.4.24"
+ }
+ },
+ "whatwg-mimetype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz",
+ "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g=="
+ },
+ "whatwg-url": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz",
+ "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==",
+ "requires": {
+ "lodash.sortby": "^4.7.0",
+ "tr46": "^1.0.1",
+ "webidl-conversions": "^4.0.2"
+ }
+ },
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
@@ -10897,8 +11115,7 @@
"wordwrap": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
- "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=",
- "dev": true
+ "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus="
},
"worker-farm": {
"version": "1.7.0",
@@ -10965,6 +11182,24 @@
"mkdirp": "^0.5.1"
}
},
+ "ws": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.0.tgz",
+ "integrity": "sha512-+SqNqFbwTm/0DC18KYzIsMTnEWpLwJsiasW/O17la4iDRRIO9uaHbvKiAS3AHgTiuuWerK/brj4O6MYZkei9xg==",
+ "requires": {
+ "async-limiter": "^1.0.0"
+ }
+ },
+ "xml-name-validator": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
+ "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw=="
+ },
+ "xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw=="
+ },
"xtend": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-3.0.0.tgz",
diff --git a/package.json b/package.json
index 67ed5b0f..4d33e35f 100644
--- a/package.json
+++ b/package.json
@@ -75,6 +75,7 @@
"express-promise-router": "^3.0.3",
"express-react-views": "^0.11.0",
"fs-extra": "^7.0.1",
+ "jsdom": "^15.2.0",
"knex": "^0.16.3",
"knex-migrate": "^1.7.1",
"mime": "^2.4.4",
diff --git a/seeds/00_networks.js b/seeds/00_networks.js
index ca950512..36398c57 100644
--- a/seeds/00_networks.js
+++ b/seeds/00_networks.js
@@ -31,6 +31,13 @@ exports.seed = knex => Promise.resolve()
url: 'https://ddfnetwork.com',
description: 'European porn videos hub with exclusive VR, 4K and full HD XXX videos and hot sex photos of Europes finest porn star babes.',
},
+ {
+ slug: 'dogfartnetwork',
+ name: 'Dogfart Network',
+ url: 'https://dogfartnetwork.com',
+ description: 'The world famous Dogfart Interracial series. Online since 1996, we have the largest collection of Interracial videos, pictures and content on the web.',
+ parameters: JSON.stringify({ photoLimit: 25 }),
+ },
{
slug: 'evilangel',
name: 'Evil Angel',
diff --git a/seeds/01_sites.js b/seeds/01_sites.js
index af0fb923..44f92698 100644
--- a/seeds/01_sites.js
+++ b/seeds/01_sites.js
@@ -828,6 +828,168 @@ exports.seed = knex => Promise.resolve()
description: 'Fantasy Blowjobs & POV Cock Sucking Videos and Photos Produced in VR, 4K and full HD featuring Sexy European Pornstars',
network_id: networksMap['ddfnetwork'],
},
+ // DOGFART NETWORK
+ {
+ slug: 'blacksonblondes',
+ name: 'Blacks On Blondes',
+ url: 'https://blacksonblondes.com/tour',
+ description: 'Blacks On Blondes is the Worlds Largest and Best Interracial Sex and Interracial Porn website. Black Men and White Women. BlacksOnBlondes has 23 years worth of Hardcore Interracial Content. Featuring the entire Legendary Dogfart Movie Archive',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'cuckoldsessions',
+ name: 'Cuckold Sessions',
+ url: 'https://cuckoldsessions.com/tour',
+ description: 'Dogfart, the #1 Interracial Network in the World Presents CuckoldSessions.com/tour - Hardcore Cuckold Fetish Videos',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'gloryhole',
+ name: 'Glory Hole',
+ url: 'https://gloryhole.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'blacksoncougars',
+ name: 'Blacks On Cougars',
+ url: 'https://blacksoncougars.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'wefuckblackgirls',
+ name: 'We Fuck Black Girls',
+ url: 'https://wefuckblackgirls.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'watchingmymomgoblack',
+ name: 'Watching My Mom Go Black',
+ url: 'https://watchingmymomgoblack.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'interracialblowbang',
+ name: 'Interracial Blowbang',
+ url: 'https://interracialblowbang.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'cumbang',
+ name: 'Cumbang',
+ url: 'https://cumbang.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'interracialpickups',
+ name: 'Interracial Pickups',
+ url: 'https://interracialpickups.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'watchingmydaughtergoblack',
+ name: 'Watching My Daughter Go Black',
+ url: 'https://watchingmydaughtergoblack.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'zebragirls',
+ name: 'Zebra Girls',
+ url: 'https://zebragirls.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'gloryholeinitiations',
+ name: 'Gloryhole Initiations',
+ url: 'https://gloryhole-initiations.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'dogfartbehindthescenes',
+ name: 'Dogfart Behind The Scenes',
+ url: 'https://dogfartbehindthescenes.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'blackmeatwhitefeet',
+ name: 'Black Meat White Feet',
+ url: 'https://blackmeatwhitefeet.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'springthomas',
+ name: 'Spring Thomas',
+ url: 'https://springthomas.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'katiethomas',
+ name: 'Katie Thomas',
+ url: 'https://katiethomas.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'ruthblackwell',
+ name: 'Ruth Blackwell',
+ url: 'https://ruthblackwell.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'candymonroe',
+ name: 'Candy Monroe',
+ url: 'https://candymonroe.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'wifewriting',
+ name: 'Wife Writing',
+ url: 'https://wifewriting.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'barbcummings',
+ name: 'Barb Cummings',
+ url: 'https://barbcummings.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'theminion',
+ name: 'The Minion',
+ url: 'https://theminion.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'blacksonboys',
+ name: 'Blacks On Boys',
+ url: 'https://blacksonboys.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
+ {
+ slug: 'gloryholesandhandjobs',
+ name: 'Gloryholes And Handjobs',
+ url: 'https://gloryholesandhandjobs.com/tour',
+ description: '',
+ network_id: networksMap['dogfartnetwork'],
+ },
// EVIL ANGEL
{
slug: 'evilangel',
diff --git a/seeds/02_tags.js b/seeds/02_tags.js
index 12b902b1..d23bf9df 100644
--- a/seeds/02_tags.js
+++ b/seeds/02_tags.js
@@ -805,6 +805,10 @@ exports.seed = knex => Promise.resolve()
name: '2-on-1',
alias_for: tagsMap['mfm'],
},
+ {
+ name: '2 on 1',
+ alias_for: tagsMap['mfm'],
+ },
{
name: '3+ on 1',
alias_for: tagsMap['gangbang'],
@@ -873,6 +877,10 @@ exports.seed = knex => Promise.resolve()
name: 'big dick',
alias_for: tagsMap['big-cock'],
},
+ {
+ name: 'big booty',
+ alias_for: tagsMap['big-butt'],
+ },
{
name: 'big butts',
alias_for: tagsMap['big-butt'],
diff --git a/seeds/04_studios.js b/seeds/03_studios.js
similarity index 100%
rename from seeds/04_studios.js
rename to seeds/03_studios.js
diff --git a/src/argv.js b/src/argv.js
index 04001425..abed2329 100644
--- a/src/argv.js
+++ b/src/argv.js
@@ -30,6 +30,11 @@ const { argv } = yargs
type: 'string',
default: config.fetchAfter.join(' '),
})
+ .option('pages', {
+ describe: 'Limit pages to scrape per site. Only used when no dates are found or --after is unset.',
+ type: 'number',
+ default: 1,
+ })
.option('save', {
describe: 'Save fetched releases to database',
type: 'boolean',
diff --git a/src/fetch-releases.js b/src/fetch-releases.js
index 3230a987..4bc1356c 100644
--- a/src/fetch-releases.js
+++ b/src/fetch-releases.js
@@ -44,6 +44,7 @@ function curateSites(sites) {
id: site.network_id,
name: site.network_name,
slug: site.network_slug,
+ parameters: JSON.parse(site.network_parameters),
},
parameters: JSON.parse(site.parameters),
}));
@@ -55,7 +56,7 @@ async function accumulateIncludedSites() {
const networkIds = networks.map(network => network.id);
const rawSites = await knex('sites')
- .select('sites.*', 'networks.name as network_name', 'networks.slug as network_slug')
+ .select('sites.*', 'networks.name as network_name', 'networks.slug as network_slug', 'networks.parameters as network_parameters')
.whereIn('sites.slug', argv.sites || [])
.orWhereIn('network_id', networkIds)
.leftJoin('networks', 'sites.network_id', 'networks.id');
@@ -269,7 +270,7 @@ async function fetchNewReleases(scraper, site, afterDate, accReleases = [], page
const oldestReleaseOnPage = latestReleases.slice(-1)[0].date;
- if (uniqueReleases.length > 0 && moment(oldestReleaseOnPage).isAfter(afterDate)) {
+ if (uniqueReleases.length > 0 && moment(oldestReleaseOnPage).isAfter(afterDate) && (!oldestReleaseOnPage && page < argv.pages)) {
return fetchNewReleases(scraper, site, afterDate, accReleases.concat(uniqueReleases), page + 1);
}
diff --git a/src/fetch-scene.js b/src/fetch-scene.js
index b611ce5a..0af6bae3 100644
--- a/src/fetch-scene.js
+++ b/src/fetch-scene.js
@@ -12,7 +12,7 @@ async function findSite(url) {
const domain = hostname.replace(/^www./, '');
const site = await knex('sites')
- .select('sites.*', 'networks.name as network_name', 'networks.slug as network_slug')
+ .select('sites.*', 'networks.name as network_name', 'networks.slug as network_slug', 'networks.parameters as network_parameters')
.where('sites.url', 'like', `%${domain}`)
.leftJoin('networks', 'sites.network_id', 'networks.id')
.first()
@@ -30,6 +30,7 @@ async function findSite(url) {
network: {
id: site.network_id || site.id,
slug: site.network_slug || site.slug,
+ parameters: site.network_parameters && JSON.parse(site.network_parameters),
},
parameters: site.parameters && JSON.parse(site.parameters),
isFallback: site.network_id === undefined,
@@ -94,8 +95,8 @@ async function storeRelease(release) {
return release;
}
-async function fetchScene(url) {
- const site = await findSite(url);
+async function fetchScene(url, release) {
+ const site = release.site || await findSite(url);
const scraper = scrapers[site.slug] || scrapers[site.network.slug];
if (!scraper) {
diff --git a/src/releases.js b/src/releases.js
index 5f844b0b..7f80f4ee 100644
--- a/src/releases.js
+++ b/src/releases.js
@@ -21,6 +21,7 @@ async function curateRelease(release) {
id: release.id,
title: release.title,
date: release.date,
+ dateAdded: release.created_at,
description: release.description,
url: release.url,
shootId: release.shoot_id,
@@ -73,7 +74,7 @@ async function fetchReleases(releaseId) {
.leftJoin('sites', 'releases.site_id', 'sites.id')
.leftJoin('studios', 'releases.studio_id', 'studios.id')
.leftJoin('networks', 'sites.network_id', 'networks.id')
- .orderBy('date', 'desc')
+ .orderBy([{ column: 'date', order: 'desc' }, { column: 'created_at', order: 'desc' }])
.limit(100);
return curateReleases(releases);
diff --git a/src/scrapers/21sextury.js b/src/scrapers/21sextury.js
index 4fbd389b..d457a5af 100644
--- a/src/scrapers/21sextury.js
+++ b/src/scrapers/21sextury.js
@@ -91,7 +91,7 @@ async function scrapeScene(html, url, site) {
const [channelSite, tags] = await Promise.all([
site.isFallback
? knex('sites')
- .where({ id: siteId })
+ .where({ slug: siteId })
.orWhereRaw('name = ? collate NOCASE', [siteName])
.first()
: site,
diff --git a/src/scrapers/bangbros.js b/src/scrapers/bangbros.js
index 5d3bfe6a..7a0f88bf 100644
--- a/src/scrapers/bangbros.js
+++ b/src/scrapers/bangbros.js
@@ -69,10 +69,12 @@ async function scrapeScene(html, url, site) {
const rawTags = $('.vdoTags a').map((tagIndex, tagElement) => $(tagElement).text()).toArray();
const [channelSite, tags] = await Promise.all([
- knex('sites')
- .where({ slug: siteId })
- .orWhere({ name: siteName })
- .first(),
+ site.isFallback
+ ? knex('sites')
+ .where({ slug: siteId })
+ .orWhere({ name: siteName })
+ .first()
+ : site,
matchTags(rawTags),
]);
diff --git a/src/scrapers/dogfart.js b/src/scrapers/dogfart.js
new file mode 100644
index 00000000..c21ccbb1
--- /dev/null
+++ b/src/scrapers/dogfart.js
@@ -0,0 +1,170 @@
+'use strict';
+
+/* eslint-disable newline-per-chained-call */
+const Promise = require('bluebird');
+const bhttp = require('bhttp');
+const { JSDOM } = require('jsdom');
+const moment = require('moment');
+const knex = require('knex');
+
+const { matchTags } = require('../tags');
+
+async function getPhoto(url) {
+ const res = await bhttp.get(url);
+ const html = res.body.toString();
+ const { document } = new JSDOM(html).window;
+
+ const photoUrl = document.querySelector('.scenes-module img').src;
+
+ return photoUrl;
+}
+
+async function getPhotos(albumUrl, site, siteUrl) {
+ const res = await bhttp.get(albumUrl);
+ const html = res.body.toString();
+ const { document } = new JSDOM(html).window;
+
+ const lastPhotoPage = Array.from(document.querySelectorAll('.preview-image-container a')).slice(-1)[0].href;
+ const lastPhotoIndex = parseInt(lastPhotoPage.match(/\d+.jpg/)[0], 10);
+
+ // dogfart has massive albums, pick 20 or specified photos: first, last and evenly inbetween
+ const photoLimit = (site.network.parameters && site.network.parameters.photoLimit) || 25;
+ const photoIndexes = [1]
+ .concat(Array.from({ length: photoLimit - 2 }, (value, index) => Math.floor((index + 1) * (lastPhotoIndex / (photoLimit - 2)))))
+ .concat(lastPhotoIndex);
+
+ if (photoLimit > 25) {
+ console.log(`${site.name}: Scraping ${photoLimit} album photos from ${siteUrl}, this may take some time...`);
+ }
+
+ const photoUrls = await Promise.map(photoIndexes, async (index) => {
+ const pageUrl = `https://blacksonblondes.com${lastPhotoPage.replace(/\d+.jpg/, `${index.toString().padStart(3, '0')}.jpg`)}`;
+
+ return getPhoto(pageUrl);
+ }, {
+ concurrency: 5,
+ });
+
+ return photoUrls;
+}
+
+function scrapeLatest(html, site) {
+ const { document } = new JSDOM(html).window;
+ const sceneElements = Array.from(document.querySelectorAll('.recent-updates'));
+
+ return sceneElements.reduce((acc, element) => {
+ const siteUrl = element.querySelector('.help-block').textContent;
+
+ if (siteUrl.toLowerCase() !== new URL(site.url).host) {
+ // different dogfart site
+ return acc;
+ }
+
+ const sceneLinkElement = element.querySelector('.thumbnail');
+ const url = `https://dogfartnetwork.com${sceneLinkElement.href}`;
+ const { pathname } = new URL(url);
+ const entryId = `${site.slug}_${pathname.split('/')[4]}`;
+
+ const title = element.querySelector('.scene-title').textContent;
+ const actors = title.split(/[,&]|\band\b/).map(actor => actor.trim());
+
+ const poster = `https:${element.querySelector('img').src}`;
+ const trailer = sceneLinkElement.dataset.preview_clip_url;
+
+ return [
+ ...acc,
+ {
+ url,
+ entryId,
+ title,
+ actors,
+ poster,
+ trailer: {
+ src: trailer,
+ },
+ site,
+ },
+ ];
+ }, []);
+}
+
+async function scrapeScene(html, url, site) {
+ const { document } = new JSDOM(html).window;
+
+ const title = document.querySelector('.description-title').textContent;
+ const actors = Array.from(document.querySelectorAll('.more-scenes a')).map(({ textContent }) => textContent);
+ const metaDescription = document.querySelector('meta[itemprop="description"]').content;
+ const description = metaDescription
+ ? metaDescription.content
+ : document.querySelector('.description')
+ .textContent
+ .replace(/[ \t\n]{2,}/g, ' ')
+ .replace('...read more', '')
+ .trim();
+
+ const siteSlug = document.querySelector('.site-name').textContent.split('.')[0].toLowerCase();
+ const date = new Date(document.querySelector('meta[itemprop="uploadDate"]').content);
+ const duration = moment
+ .duration(document
+ .querySelectorAll('.extra-info p')[1]
+ .textContent
+ .match(/\d+:\d+$/)[0])
+ .asSeconds();
+
+ const trailerElement = document.querySelector('.html5-video');
+ const poster = `https:${trailerElement.dataset.poster}`;
+ const { trailer } = trailerElement.dataset;
+
+ const lastPhotosUrl = Array.from(document.querySelectorAll('.pagination a')).slice(-1)[0].href;
+ const { origin, pathname } = new URL(url);
+ const photos = await getPhotos(`${origin}${pathname}${lastPhotosUrl}`, site, url);
+
+ const stars = Number(document.querySelector('span[itemprop="average"]').textContent) / 2;
+ const rawTags = Array.from(document.querySelectorAll('.scene-details .categories a')).map(({ textContent }) => textContent);
+
+ const [channelSite, tags] = await Promise.all([
+ site.isFallback
+ ? knex('sites')
+ .where({ slug: siteSlug })
+ .orWhere({ url: `https://${siteSlug}.com` })
+ .first()
+ : site,
+ matchTags(rawTags),
+ ]);
+
+ return {
+ url,
+ title,
+ description,
+ actors,
+ date,
+ duration,
+ poster,
+ photos,
+ trailer: {
+ src: trailer,
+ },
+ tags,
+ rating: {
+ stars,
+ },
+ site: channelSite || site,
+ };
+}
+
+async function fetchLatest(site, page = 1) {
+ const res = await bhttp.get(`https://dogfartnetwork.com/tour/scenes/?p=${page}`);
+
+ return scrapeLatest(res.body.toString(), site);
+}
+
+async function fetchScene(url, site) {
+ const res = await bhttp.get(url);
+
+ return scrapeScene(res.body.toString(), url, site);
+}
+
+module.exports = {
+ fetchLatest,
+ fetchScene,
+};
diff --git a/src/scrapers/index.js b/src/scrapers/index.js
index 3ca6637b..f0400a1d 100644
--- a/src/scrapers/index.js
+++ b/src/scrapers/index.js
@@ -5,6 +5,7 @@ const bangbros = require('./bangbros');
const blowpass = require('./blowpass');
const brazzers = require('./brazzers');
const ddfnetwork = require('./ddfnetwork');
+const dogfart = require('./dogfart');
const evilangel = require('./evilangel');
const julesjordan = require('./julesjordan');
const kink = require('./kink');
@@ -23,6 +24,8 @@ module.exports = {
blowpass,
brazzers,
ddfnetwork,
+ dogfart,
+ dogfartnetwork: dogfart,
evilangel,
julesjordan,
kink,
diff --git a/src/scrapers/kink.js b/src/scrapers/kink.js
index 94eadcdb..111469c0 100644
--- a/src/scrapers/kink.js
+++ b/src/scrapers/kink.js
@@ -3,8 +3,8 @@
const bhttp = require('bhttp');
const cheerio = require('cheerio');
const moment = require('moment');
+const knex = require('knex');
-const knex = require('../knex');
const { matchTags } = require('../tags');
function scrapeLatest(html, site) {
@@ -75,7 +75,9 @@ async function scrapeScene(html, url, shootId, ratingRes, site) {
const rawTags = $('.tag-list > a[href*="/tag"]').map((tagIndex, tagElement) => $(tagElement).text()).toArray();
const [channelSite, tags] = await Promise.all([
- knex('sites').where({ slug: sitename }).first(),
+ site.isFallback
+ ? knex('sites').where({ slug: sitename }).first()
+ : site,
matchTags(rawTags),
]);
diff --git a/src/scrapers/mofos.js b/src/scrapers/mofos.js
index 7325a942..a41360f7 100644
--- a/src/scrapers/mofos.js
+++ b/src/scrapers/mofos.js
@@ -58,7 +58,7 @@ async function scrapeScene(html, url, site) {
const [channelSite, tags] = await Promise.all([
knex('sites')
- .where({ id: siteId })
+ .where({ slug: siteId })
.orWhere({ url: `https://www.mofos.com${siteUrl}` })
.orWhere({ name: sitename })
.first(),
diff --git a/src/scrapers/naughtyamerica.js b/src/scrapers/naughtyamerica.js
index c0e94c58..a055472c 100644
--- a/src/scrapers/naughtyamerica.js
+++ b/src/scrapers/naughtyamerica.js
@@ -70,7 +70,7 @@ async function scrapeScene(html, url, site) {
const [channelSite, tags] = await Promise.all([
knex('sites')
- .where({ id: siteId })
+ .where({ slug: siteId })
.orWhere({ name: siteName })
.first(),
matchTags(rawTags),
diff --git a/src/scrapers/realitykings.js b/src/scrapers/realitykings.js
index 72b740ca..faa84257 100644
--- a/src/scrapers/realitykings.js
+++ b/src/scrapers/realitykings.js
@@ -13,6 +13,8 @@ function scrapeLatest(html, site) {
const $ = cheerio.load(html, { normalizeWhitespace: true });
const sceneElements = $('.card.card--release').toArray();
+ console.log(sceneElements);
+
return sceneElements.map((element) => {
const sceneLinkElement = $(element).find('.card-info__title a');
const title = sceneLinkElement.attr('title');
@@ -22,6 +24,8 @@ function scrapeLatest(html, site) {
const date = moment.utc($(element).find('.card-info__meta-date').text(), 'MMMM DD, YYYY').toDate();
const actors = $(element).find('.card-info__cast a').map((actorIndex, actorElement) => $(actorElement).text().trim()).toArray();
+ console.log(date, actors, title);
+
return {
url,
entryId,
@@ -54,6 +58,8 @@ async function scrapeScene(data, url, site) {
const { likes, dislikes } = data.stats;
const duration = data.videos.mediabook.length;
+ console.log(data);
+
const rawTags = data.tags.map(tag => tag.name);
const tags = await matchTags(rawTags);
diff --git a/src/scrapers/xempire.js b/src/scrapers/xempire.js
index 99e8d22b..692c775a 100644
--- a/src/scrapers/xempire.js
+++ b/src/scrapers/xempire.js
@@ -3,9 +3,9 @@
const Promise = require('bluebird');
const bhttp = require('bhttp');
const cheerio = require('cheerio');
+const knex = require('knex');
const moment = require('moment');
-const knex = require('../knex');
const { matchTags } = require('../tags');
async function fetchPhotos(url) {
@@ -126,7 +126,6 @@ async function scrapeScene(html, url, site) {
const duration = moment.duration(data.duration.slice(2).split(':')).asSeconds();
- const rawTags = data.keywords.split(', ');
const siteDomain = $('meta[name="twitter:domain"]').attr('content');
const siteId = siteDomain && siteDomain.split('.')[0].toLowerCase();
const siteUrl = siteDomain && `https://www.${siteDomain}`;
@@ -136,11 +135,13 @@ async function scrapeScene(html, url, site) {
const photos = await getPhotos($('.picturesItem a').attr('href'), siteDomain);
+ const rawTags = data.keywords.split(', ');
+
const [channelSite, tags] = await Promise.all([
site.isFallback
? knex('sites')
.where({ url: siteUrl })
- .orWhere({ id: siteId })
+ .orWhere({ slug: siteId })
.first()
: site,
matchTags(rawTags),