frist commit

This commit is contained in:
Kai Ton 2024-05-27 03:04:11 +00:00
commit 545575605e
14 changed files with 5966 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

BIN
eng.traineddata Normal file

Binary file not shown.

3
index.js Normal file
View File

@ -0,0 +1,3 @@
const robot = require('./robot/robot');
robot('hello', 'edu.vn')

2861
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

5
package.json Normal file
View File

@ -0,0 +1,5 @@
{
"private": true,
"name": "index",
"workspaces": ["queue", "robot"]
}

2
queue/.gitignore vendored Executable file
View File

@ -0,0 +1,2 @@
node_modules
.pnpm-store

10
queue/package.json Executable file
View File

@ -0,0 +1,10 @@
{
"name": "queue",
"dependencies": {
"amqplib": "^0.10.3",
"shelljs": "^0.8.5",
"socket.io-client": "^4.7.5"
},
"license": "MIT"
}

330
queue/pnpm-lock.yaml Executable file
View File

@ -0,0 +1,330 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
amqplib:
specifier: ^0.10.3
version: 0.10.4
shelljs:
specifier: ^0.8.5
version: 0.8.5
socket.io-client:
specifier: ^4.7.5
version: 4.7.5
packages:
'@acuminous/bitsyntax@0.1.2':
resolution: {integrity: sha512-29lUK80d1muEQqiUsSo+3A0yP6CdspgC95EnKBMi22Xlwt79i/En4Vr67+cXhU+cZjbti3TgGGC5wy1stIywVQ==}
engines: {node: '>=0.8'}
'@socket.io/component-emitter@3.1.2':
resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
amqplib@0.10.4:
resolution: {integrity: sha512-DMZ4eCEjAVdX1II2TfIUpJhfKAuoCeDIo/YyETbfAqehHTXxxs7WOOd+N1Xxr4cKhx12y23zk8/os98FxlZHrw==}
engines: {node: '>=10'}
balanced-match@1.0.2:
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
brace-expansion@1.1.11:
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
buffer-more-ints@1.0.0:
resolution: {integrity: sha512-EMetuGFz5SLsT0QTnXzINh4Ksr+oo4i+UGTXEshiGCQWnsgSs7ZhJ8fzlwQ+OzEMs0MpDAMr1hxnblp5a4vcHg==}
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
core-util-is@1.0.3:
resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==}
debug@4.3.4:
resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==}
engines: {node: '>=6.0'}
peerDependencies:
supports-color: '*'
peerDependenciesMeta:
supports-color:
optional: true
engine.io-client@6.5.3:
resolution: {integrity: sha512-9Z0qLB0NIisTRt1DZ/8U2k12RJn8yls/nXMZLn+/N8hANT3TcYjKFKcwbw5zFQiN4NTde3TSY9zb79e1ij6j9Q==}
engine.io-parser@5.2.2:
resolution: {integrity: sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==}
engines: {node: '>=10.0.0'}
fs.realpath@1.0.0:
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
function-bind@1.1.2:
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
hasown@2.0.2:
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
engines: {node: '>= 0.4'}
inflight@1.0.6:
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
inherits@2.0.4:
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
interpret@1.4.0:
resolution: {integrity: sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==}
engines: {node: '>= 0.10'}
is-core-module@2.13.1:
resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
isarray@0.0.1:
resolution: {integrity: sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==}
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
ms@2.1.2:
resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
path-is-absolute@1.0.1:
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
engines: {node: '>=0.10.0'}
path-parse@1.0.7:
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
querystringify@2.2.0:
resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==}
readable-stream@1.1.14:
resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==}
rechoir@0.6.2:
resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==}
engines: {node: '>= 0.10'}
requires-port@1.0.0:
resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==}
resolve@1.22.8:
resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==}
hasBin: true
safe-buffer@5.1.2:
resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==}
shelljs@0.8.5:
resolution: {integrity: sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==}
engines: {node: '>=4'}
hasBin: true
socket.io-client@4.7.5:
resolution: {integrity: sha512-sJ/tqHOCe7Z50JCBCXrsY3I2k03iOiUe+tj1OmKeD2lXPiGH/RUCdTZFoqVyN7l1MnpIzPrGtLcijffmeouNlQ==}
engines: {node: '>=10.0.0'}
socket.io-parser@4.2.4:
resolution: {integrity: sha512-/GbIKmo8ioc+NIWIhwdecY0ge+qVBSMdgxGygevmdHj24bsfgtCmcUUcQ5ZzcylGFHsN3k4HB4Cgkl96KVnuew==}
engines: {node: '>=10.0.0'}
string_decoder@0.10.31:
resolution: {integrity: sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==}
supports-preserve-symlinks-flag@1.0.0:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
url-parse@1.5.10:
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
ws@8.11.0:
resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
utf-8-validate: ^5.0.2
peerDependenciesMeta:
bufferutil:
optional: true
utf-8-validate:
optional: true
xmlhttprequest-ssl@2.0.0:
resolution: {integrity: sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==}
engines: {node: '>=0.4.0'}
snapshots:
'@acuminous/bitsyntax@0.1.2':
dependencies:
buffer-more-ints: 1.0.0
debug: 4.3.4
safe-buffer: 5.1.2
transitivePeerDependencies:
- supports-color
'@socket.io/component-emitter@3.1.2': {}
amqplib@0.10.4:
dependencies:
'@acuminous/bitsyntax': 0.1.2
buffer-more-ints: 1.0.0
readable-stream: 1.1.14
url-parse: 1.5.10
transitivePeerDependencies:
- supports-color
balanced-match@1.0.2: {}
brace-expansion@1.1.11:
dependencies:
balanced-match: 1.0.2
concat-map: 0.0.1
buffer-more-ints@1.0.0: {}
concat-map@0.0.1: {}
core-util-is@1.0.3: {}
debug@4.3.4:
dependencies:
ms: 2.1.2
engine.io-client@6.5.3:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.3.4
engine.io-parser: 5.2.2
ws: 8.11.0
xmlhttprequest-ssl: 2.0.0
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
engine.io-parser@5.2.2: {}
fs.realpath@1.0.0: {}
function-bind@1.1.2: {}
glob@7.2.3:
dependencies:
fs.realpath: 1.0.0
inflight: 1.0.6
inherits: 2.0.4
minimatch: 3.1.2
once: 1.4.0
path-is-absolute: 1.0.1
hasown@2.0.2:
dependencies:
function-bind: 1.1.2
inflight@1.0.6:
dependencies:
once: 1.4.0
wrappy: 1.0.2
inherits@2.0.4: {}
interpret@1.4.0: {}
is-core-module@2.13.1:
dependencies:
hasown: 2.0.2
isarray@0.0.1: {}
minimatch@3.1.2:
dependencies:
brace-expansion: 1.1.11
ms@2.1.2: {}
once@1.4.0:
dependencies:
wrappy: 1.0.2
path-is-absolute@1.0.1: {}
path-parse@1.0.7: {}
querystringify@2.2.0: {}
readable-stream@1.1.14:
dependencies:
core-util-is: 1.0.3
inherits: 2.0.4
isarray: 0.0.1
string_decoder: 0.10.31
rechoir@0.6.2:
dependencies:
resolve: 1.22.8
requires-port@1.0.0: {}
resolve@1.22.8:
dependencies:
is-core-module: 2.13.1
path-parse: 1.0.7
supports-preserve-symlinks-flag: 1.0.0
safe-buffer@5.1.2: {}
shelljs@0.8.5:
dependencies:
glob: 7.2.3
interpret: 1.4.0
rechoir: 0.6.2
socket.io-client@4.7.5:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.3.4
engine.io-client: 6.5.3
socket.io-parser: 4.2.4
transitivePeerDependencies:
- bufferutil
- supports-color
- utf-8-validate
socket.io-parser@4.2.4:
dependencies:
'@socket.io/component-emitter': 3.1.2
debug: 4.3.4
transitivePeerDependencies:
- supports-color
string_decoder@0.10.31: {}
supports-preserve-symlinks-flag@1.0.0: {}
url-parse@1.5.10:
dependencies:
querystringify: 2.2.0
requires-port: 1.0.0
wrappy@1.0.2: {}
ws@8.11.0: {}
xmlhttprequest-ssl@2.0.0: {}

74
queue/queue.js Executable file
View File

@ -0,0 +1,74 @@
const {exec} = require("shelljs");
const amqplib = require("amqplib");
const io = require("socket.io-client");
const CONNECT_SOCKET_STR = "ws://socketio:3000";
const CONNECT_RABBIT_STR = "amqp://rabbit:rabbit123@rabbitmq";
const QUEUE = "browser";
const QUEUE_NOTIFY = "browser-notify";
async function processMessage(channel, message) {
const timestamp = Date.now();
const response = JSON.parse(message.content.toString());
const socketioStart = io.connect(CONNECT_SOCKET_STR, {
transports: ["websocket"],
forceNew: true,
});
await new Promise((resolve) => {
socketioStart.on("connect", () => {
console.log("Socket.IO start was connected");
resolve();
});
});
console.log("[x] RECEIVED", response);
socketioStart.emit("message", {
to: QUEUE_NOTIFY,
payload: {
browser_name: process.env.BROWSER_NAME,
id: timestamp,
time_process: Date.now(),
finished: false,
},
}).close();
// TODO run script
// do something
// !TODO
const socketioEnd = io.connect(CONNECT_SOCKET_STR, {
transports: ["websocket"],
forceNew: true,
});
await new Promise((resolve) => {
socketioEnd.on("connect", () => {
console.log("Socket.IO end was connected");
resolve();
});
});
socketioEnd.emit("message", {
to: QUEUE_NOTIFY,
payload: {
browser_name: process.env.BROWSER_NAME,
id: timestamp,
time_process: Date.now(),
finished: true,
},
}).close();
// wait see result finished 10s
await new Promise(resolve => setTimeout(resolve, 10000));
channel.ack(message);
}
module.exports = async function() {
const conn = await amqplib.connect(CONNECT_RABBIT_STR);
const channelProcess = await conn.createChannel();
console.log(`[${process.env.BROWSER_NAME}] Running...`);
await channelProcess.assertQueue(QUEUE);
await channelProcess.prefetch(1);
await channelProcess.consume(QUEUE,
async (message) => {
await processMessage(channelProcess, message);
},
{noAck: false}
);
}

1
robot/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules

2552
robot/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

11
robot/package.json Normal file
View File

@ -0,0 +1,11 @@
{
"name": "robot",
"dependencies": {
"jimp": "^0.22.12",
"jsdom": "^24.0.0",
"robotjs": "^0.6.0",
"screenshot-desktop": "^1.15.0",
"sharp": "^0.33.4",
"tesseract.js": "^4.1.1"
}
}

116
robot/robot.js Normal file
View File

@ -0,0 +1,116 @@
/**
* NOTE: install "dark mode" on browser because tesseract need contract to detect text
* NOTE: zoom 120% on browser because below
*/
const robotjs = require('robotjs')
const tesseract = require('tesseract.js')
const jsdom = require('jsdom')
const Jimp = require('jimp')
module.exports = async function(keywordSearch, keywordToClick, scale = 1.5, fileScreenshot = 'screenshot.jpg', waitSearch = 2000) {
if(!scale) {
scale = 1.5
}
if(!fileScreenshot) {
fileScreenshot = 'screenshot.jpg'
}
if(!waitSearch) {
waitSearch = 2000
}
const $_randomInteger = (min, max) => {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
async function handleToClick() {
let _horc = '';
let _text = '';
let _coorStr = '';
const _captureImage = async () => {
const capture = robotjs.screen.capture()
return new Promise(res => {
new Jimp(
{data: capture.image, width: capture.width, height: capture.height},
(err, image) => {
image.greyscale((err, image) => {
image.scale(scale, (err, image) => {
image.writeAsync(fileScreenshot).then(() => {
res(true)
})
})
});
});
})
}
const _convertImageToDomString = async () => {
await tesseract
.recognize(fileScreenshot)
.then(async ({data: {hocr, text}}) => {
_horc = hocr
_text = text
})
}
const _detectHTML = async () => {
console.log({keywordToClick, _text})
return new Promise(async (resToFinished, rejToAgain) => {
if(_text.includes(keywordToClick)) {
const dom = new jsdom.JSDOM(_horc)
const spanEls = dom.window.document.querySelectorAll('span')
for await(let span of spanEls) {
const textContent = span.textContent.split(' ').join('')
if(textContent.includes(keywordToClick)) {
_coorStr = span.title
return resToFinished(true)
}
}
}
robotjs.keyTap('down')
robotjs.keyTap('down')
return rejToAgain(false)
})
}
async function _handleClick() {
const [type, left, top, right, bottom] = _coorStr.split(' ')
const x = parseInt((parseInt(left) + $_randomInteger(20, 40)) / scale)
const y = parseInt((parseInt(top) - $_randomInteger(0, parseInt(top) - parseInt(bottom))) / scale)
robotjs.moveMouse(x, y)
robotjs.mouseClick('left')
console.log('moveMouse:', {x, y}, {
left, bottom, right, top
})
}
return new Promise(async (res, rej) => {
await _captureImage();
await _convertImageToDomString();
await _detectHTML().then(_handleClick).catch(rej);
})
}
await (async function search() {
robotjs.keyTap('home', ['alt']);
robotjs.typeString(keywordSearch);
robotjs.keyTap('enter')
return new Promise(res => setTimeout(res, waitSearch))
}())
await (async function recusiveScroll(countScroll = 1, limitStopScroll = 20) {
if(countScroll > limitStopScroll) {
return;
}
await handleToClick(keywordToClick, scale)
.catch(async () => {
await recusiveScroll(++countScroll, limitStopScroll)
})
}())
}

BIN
screenshot.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB