mirror of
1
2
Fork 0

Added image tags and update readme

This commit is contained in:
Devshh 2020-03-24 18:06:20 +04:00
parent a58928f060
commit bdae28a825
12 changed files with 115 additions and 104 deletions

View File

@ -7,7 +7,11 @@ It has much the same API and has an additional benefit of helping [OpenStreetCam
# Donate
If you use uCaptcha in production, or want to see the project grow, consider funding the project to help pay for hosting and bandwidth costs.
XMR: `45uYweaLaasTFKdEHK9qADRZMatRE3U9vKFwT1kpaig26GnruZm1t21ipVjsC1KLeeL7sG3m8bHfhcce4tQJLDKNCFBLPar`
BTC: `bc1qjj45cuu7d8r6g83al7yrtkkmzu6ud4j9qzq3dn`
@ -16,6 +20,7 @@ BTC: `bc1qjj45cuu7d8r6g83al7yrtkkmzu6ud4j9qzq3dn`
- [ ] Think of a way to detect if user's selection is correct answer
- [ ] Create tests
- [ ] Configure Rollup to create 2 packages 1x esm 1x cjs
- [ ] Improve the UI
## Medium term (> 1 month)
- [ ] Increase security and accuracy of predictions

View File

@ -1,46 +1 @@
{
"B4mXt9gEOp1r": {
"mat": [
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.4,
0.6,
0.6,
0.6,
0.4,
0.6,
0.6,
0.6
],
"nums": 1,
"obj": 0
},
"U63lqPRdPinG": {
"mat": [
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.30000000000000004,
0.7,
0.7,
0.7,
0.30000000000000004,
0.7,
0.7,
0.7
],
"nums": 2,
"obj": 0
}
}
{}

View File

@ -11,3 +11,9 @@ export const PROJECT_ROOT = process.cwd();
export const IMAGES_FOLDER = path.join(PROJECT_ROOT, 'public', 'images');
export const MAX_SESSION_TIME = 60 * 30;
export const TAGS = [
null,
'vehicle',
'house',
]

View File

@ -1,7 +1,7 @@
import reloadSession from './helpers/reloadSession.js';
import getImage from './helpers/getImage.js';
import verifyImage from './helpers/verifyImage.js';
import {createElement} from './util/document.js';
import {createElement as ce, querySelector as qs} from './util/document.js';
/**
* Create a uCaptcha box
@ -10,6 +10,9 @@ import {createElement} from './util/document.js';
*/
function uCaptchaBox(websiteKey) {
const styles = `
#ucaptcha-container {
font-family: Arial;
}
#ucaptcha-next {
border: none;
background-color: royalblue;
@ -21,6 +24,10 @@ function uCaptchaBox(websiteKey) {
#ucaptcha-grid {
width: 384px;
height: 384px;
transition: .2s ease;
}
#ucaptcha-grid.blurred {
background: rgba(255, 255, 255, 0.4);
}
#ucaptcha-grid td {
transition: .1s ease;
@ -33,11 +40,11 @@ function uCaptchaBox(websiteKey) {
border: 10px solid white;
}
`;
const styleTag = createElement('style');
const styleTag = ce('style');
styleTag.innerHTML = styles;
document.head.appendChild(styleTag);
const checkbox = createElement('div', {
const checkbox = ce('div', {
style: 'height:25px;display:inline-block',
});
checkbox.style.cursor = 'pointer';
@ -45,29 +52,34 @@ function uCaptchaBox(websiteKey) {
checkbox.style.border = '2px solid #888';
checkbox.style.width = '25px';
const captchaBox = createElement('div');
const captchaBox = ce('div');
captchaBox.appendChild(checkbox);
const imageContainer = createElement('div', {id: 'ucaptcha-container'});
const captchaContainer = ce('div', {id: 'ucaptcha-container'});
const image = createElement('img', {id: 'ucaptcha-img', style: 'position:absolute;display:block;z-index:-999'});
imageContainer.appendChild(image);
const imageTagTitle = ce('h3', {id: 'ucaptcha-caption'});
captchaContainer.appendChild(imageTagTitle);
const btn = createElement('button', {id: 'ucaptcha-next'});
const image = ce('img', {id: 'ucaptcha-img', style: 'position:absolute;display:block;z-index:-999'});
captchaContainer.appendChild(image);
const btn = ce('button', {id: 'ucaptcha-next'});
btn.textContent = 'Next';
captchaBox.appendChild(imageContainer);
captchaBox.appendChild(captchaContainer);
captchaBox.appendChild(btn);
checkbox.onclick = async function() {
/** @type {import('../shared/models/UserSession.js').UserSession} */
const session = await reloadSession(websiteKey);
const imageGrid = createElement('table', {id: 'ucaptcha-grid'});
qs('#ucaptcha-caption').innerHTML = `Select all squares with <b>${session.getImageTag()}s</b>`;
const imageGrid = ce('table', {id: 'ucaptcha-grid'});
for (let i = 0; i < 4; i++) {
const tr = createElement('tr');
const tr = ce('tr');
for (let ii = 0; ii < 4; ii++) {
const td = createElement('td',
const td = ce('td',
{'style': 'cursor:pointer'});
td.addEventListener('click', (e)=>{
e.target.classList.toggle('selected');
@ -82,7 +94,7 @@ function uCaptchaBox(websiteKey) {
getImage(session, captchaBox);
});
imageContainer.appendChild(imageGrid);
captchaContainer.appendChild(imageGrid);
getImage(session, captchaBox);
@ -98,8 +110,8 @@ function uCaptchaBox(websiteKey) {
* @param {string} selector
*/
export function create(websiteKey, selector) {
// const iframe = createElement("iframe");
// const iframe = ce("iframe");
// iframe.setAttribute("src", "https://localhost:444/?k="+websiteKey)
document.querySelector(selector).appendChild(uCaptchaBox(websiteKey));
qs(selector).appendChild(uCaptchaBox(websiteKey));
}

View File

@ -5,7 +5,7 @@ import UserSession from '../../models/UserSession.js';
* @param {string} websiteKey
* @return {Promise<UserSession>}
*/
export default function initializeSession(websiteKey) {
export default function reloadSession(websiteKey) {
return new Promise((resolve, reject)=>{
request(`/api/reload?k=${websiteKey}`)
.then((resp)=>{

View File

@ -1,7 +1,9 @@
import request from '../util/request.js';
import UserSession from '../../models/UserSession.js';
import {querySelector as qs} from '../util/document.js';
/**
* @typedef { import('../../models/UserSession.js').UserSession } UserSession
* @typedef {import('../../models/UserSession.js')} UserSession
*/
@ -10,7 +12,8 @@ import request from '../util/request.js';
* @param {UserSession} session
* @param {HTMLElement} captchaGrid
*/
export async function verfiyImage(session, captchaGrid) {
export default async function verifyImage(session, captchaGrid) {
captchaGrid.classList.add('blurred');
const tds = captchaGrid.querySelectorAll('td');
const selectedTds = [];
@ -29,11 +32,15 @@ export async function verfiyImage(session, captchaGrid) {
'mat': mat,
};
await request('/api/verify', {_method: 'POST', _body: body});
const resp = await request('/api/verify', {_method: 'POST', _body: body});
session.deserialize(resp);
qs('#ucaptcha-caption')
.innerHTML = `Select all squares with <b>${session.getImageTag()}s</b>`;
captchaGrid.classList.remove('blurred');
selectedTds.map((td)=>{
td.classList.remove('selected');
});
}
export default verfiyImage;

View File

@ -7,4 +7,3 @@
* @property {number} obj Image object ID
* @property {number} score How likely is the user to be a human?
*/
export const IDBSession = {};

View File

@ -1,5 +0,0 @@
export default [
null,
'Vehicle',
'House',
];

View File

@ -8,6 +8,9 @@ export default function UserSession() {
/** @private @type {string | undefined} */
this._websiteKey = undefined;
/** @private @type {string | undefined} */
this._imageTag = undefined;
/**
* Get session identifier
@ -43,6 +46,23 @@ export default function UserSession() {
};
/**
* Get image tag as text
* @return {string}
*/
this.getImageTag = function() {
return this._imageTag;
};
/**
* Set image tag as text
* @param {string} imageObject
*/
this.setImageTag = function(imageObject) {
this._imageTag = imageObject;
};
/**
* Serialize the session into a JSON object
* @return {Array<any>}
@ -50,14 +70,12 @@ export default function UserSession() {
this.serialize = function() {
const sessionId = this.getSessionId();
const websiteKey = this.getWebsiteKey();
if (!sessionId || !websiteKey) {
throw Error();
}
const imageTag = this.getImageTag();
return [
sessionId,
websiteKey,
imageTag,
];
};
@ -65,9 +83,13 @@ export default function UserSession() {
* Deserialize session data from JSON object
* @param {Array<any>} payload
*/
this.deserialize = function([_sessionId, _websiteKey]) {
console.log(_sessionId, _websiteKey);
this.setSessionId(_sessionId);
this.setWebsiteKey(_websiteKey);
this.deserialize = function([
sessionId,
websiteKey,
imageTag,
]) {
this.setSessionId(sessionId);
this.setWebsiteKey(websiteKey);
this.setImageTag(imageTag);
};
}

View File

@ -49,10 +49,8 @@ router.post('/verify', async (req, res)=>{
const mat = req.body.mat;
console.log(req.body);
await verifyImage(sessionId, mat);
sendJson(res, {
ok: 1,
});
const session = await verifyImage(sessionId, mat);
sendJson(res, session.serialize());
});
export default router;

View File

@ -5,11 +5,11 @@ import pickRandomFile from './pickRandomFile.js';
import UserSession from '../../models/UserSession.js';
import {randomBytes} from '../../helpers/utils.js';
import {client} from '../../helpers/idb.js';
import {MAX_SESSION_TIME} from '../../R.js';
import {MAX_SESSION_TIME, TAGS} from '../../R.js';
/**
* @typedef {import('../../models/IDBSession.js').IDBSession} IDBSession
*/
// /**
// * @typedef {import('../../models/IDBSession.js')} IDBSession
// */
/**
@ -20,28 +20,27 @@ import {MAX_SESSION_TIME} from '../../R.js';
export default async function initializeSession(websiteKey, cookies) {
let randomSessionId;
const session = new UserSession();
if (cookies[websiteKey]) {
randomSessionId = cookies[websiteKey];
session.setSessionId(randomSessionId);
session.setWebsiteKey(websiteKey);
return session;
} else {
randomSessionId = randomBytes(8);
}
const image = await pickRandomFile();
const imageTagId = 1; // TODO: Change later to be dynamic based on `image`
const session = new UserSession();
session.setSessionId(randomSessionId);
session.setWebsiteKey(websiteKey);
session.setImageTag(TAGS[imageTagId]);
/** @type {IDBSession} */
/** @type {import('../../models/IDBSession.js')} */
const idbPayload = {
sessionId: session.getSessionId(),
websiteKey: session.getWebsiteKey(),
image,
obj: imageTagId,
score: 0.5,
};

View File

@ -1,9 +1,13 @@
import fs from 'fs';
import path from 'path';
import {client} from '../../helpers/idb.js';
import {PROJECT_ROOT, MAX_SESSION_TIME} from '../../R.js';
import {PROJECT_ROOT, MAX_SESSION_TIME, TAGS} from '../../R.js';
import {argmaxThresh} from '../../helpers/utils.js';
import pickRandomFile from './pickRandomFile.js';
import UserSession from '../../models/UserSession.js';
/** @typedef {import('../../models/IDBSession.js')} IDBSession */
/**
* @typedef {Object} FileMat
@ -80,13 +84,12 @@ function updateMat(imageMat, userMat, image) {
*/
export default function(sessionId, userMat) {
return new Promise((resolve, reject)=>{
client.get(sessionId, (err, result)=>{
console.log(sessionId);
client.get(sessionId, (err, resp)=>{
if (err) return reject(err);
if (!result) return reject(new Error('Session expired'));
if (!resp) return reject(new Error('Session expired'));
/** @type {import('../../models/IDBSession.js').IDBSession} */
result = JSON.parse(result);
/** @type {IDBSession} */
const result = JSON.parse(resp);
const imageMat = getMat(result.image);
const trueArgmax = argmaxThresh(imageMat.mat, 0.6).join(',');
@ -109,13 +112,23 @@ export default function(sessionId, userMat) {
}
pickRandomFile().then((image)=>{
// TODO: Make me dynamic
const newImageTag = 1;
const update = JSON.stringify(
Object.assign(result, {image: image}));
Object.assign(result, {
image: image,
obj: newImageTag,
}));
client.setex(sessionId, MAX_SESSION_TIME,
update, (err)=>{
if (err) return reject(err);
resolve();
const session = new UserSession();
session.setSessionId(sessionId);
session.setWebsiteKey(result.websiteKey);
session.setImageTag(TAGS[newImageTag]);
resolve(session);
});
});
});