Skip to content

Instantly share code, notes, and snippets.

@stackia
Last active March 20, 2019 06:18
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save stackia/5e6fc0a3f1196042c1075109dd149825 to your computer and use it in GitHub Desktop.
Save stackia/5e6fc0a3f1196042c1075109dd149825 to your computer and use it in GitHub Desktop.
giveaway.su 验证码辅助输入脚本
// ==UserScript==
// @name giveaway.su 验证码输入辅助
// @namespace http://tampermonkey.net/
// @version 0.2
// @description 自动识别验证码+验证码输入键盘,必须安装 Liana 字体方可正常使用
// @author Stackia
// @require https://cdn.bootcss.com/jquery/3.2.1/jquery.min.js
// @match https://giveaway.su/giveaway/view/*
// @updateURL https://gist.githubusercontent.com/stackia/5e6fc0a3f1196042c1075109dd149825/raw/giveaway_su_captcha_helper.user.js
// @downloadURL https://gist.githubusercontent.com/stackia/5e6fc0a3f1196042c1075109dd149825/raw/giveaway_su_captcha_helper.user.js
// @supportURL https://steamcn.com/t279555-1-1
// ==/UserScript==
(function () {
let text = 'абвгдеёжзийклмнопрстуфхцчшщъыьэюя'
let textGroups = ['аийлмшя', 'бвъыь', 'гч', 'дзруф', 'еёосэю', 'жкх', 'нпт', 'цщ']
let knownChars = []
for (let i = 0; i < text.length; ++i) {
let charCanvas = document.createElement('canvas')
charCanvas.width = 60
charCanvas.height = 60
const charCtx = charCanvas.getContext('2d')
charCtx.fillStyle = 'white'
charCtx.fillRect(0, 0, charCanvas.width, charCanvas.height)
charCtx.fillStyle = 'black'
charCtx.font = '46px Liana'
charCtx.fillText(text[i], 30, 30)
let charImageData = charCtx.getImageData(0, 0, charCanvas.width, charCanvas.height)
let charBoundary = getImageBoundaryTrim(charImageData)
charCanvas.width = charBoundary.width
charCanvas.height = charBoundary.height
charCtx.putImageData(charImageData, -charBoundary.x, -charBoundary.y)
knownChars.push({
char: text[i],
imageData: charCtx.getImageData(0, 0, charCanvas.width, charCanvas.height)
})
}
function getPointGrey(imageData, x, y) {
let idx = (x + y * imageData.width) * 4
return (x < 0 || x >= imageData.width || y < 0 || y >= imageData.height) ? 255 : imageData.data[idx]
}
function greyscale(imageData) {
const threshold = 220
for (let x = 0; x < imageData.width; x++) {
for (let y = 0; y < imageData.height; y++) {
let idx = (x + y * imageData.width) * 4
let r = imageData.data[idx]
let g = imageData.data[idx + 1]
let b = imageData.data[idx + 2]
let grey = 0.299 * r + 0.587 * g + 0.114 * b
grey = grey < threshold ? 0 : 255
imageData.data[idx] = grey
imageData.data[idx + 1] = grey
imageData.data[idx + 2] = grey
imageData.data[idx + 3] = 255
}
}
}
function scaleImageData(imageData, scale) {
let canvas1 = document.createElement('canvas')
canvas1.width = imageData.width
canvas1.height = imageData.height
canvas1.getContext('2d').putImageData(imageData, 0, 0)
let canvas2 = document.createElement('canvas')
canvas2.width = Math.floor(imageData.width * scale)
canvas2.height = Math.floor(imageData.height * scale)
let ctx = canvas2.getContext('2d')
ctx.scale(scale, scale)
ctx.drawImage(canvas1, 0, 0)
return ctx.getImageData(0, 0, canvas2.width, canvas2.height)
}
function getImageBoundaryTrim(imageData) {
let startX = imageData.width - 1, startY = imageData.height - 1, endX = 0, endY = 0
for (let x = 0; x < imageData.width; x++) {
for (let y = 0; y < imageData.height; y++) {
let idx = (x + y * imageData.width) * 4
let grey = imageData.data[idx]
if (grey === 255) continue
if (x < startX) startX = x
if (x > endX) endX = x
if (y < startY) startY = y
if (y > endY) endY = y
}
}
return {
x: startX,
y: startY,
width: endX - startX + 1,
height: endY - startY + 1
}
}
function execute() {
let input = $('#form-captcha input')
$('#modal-captcha .modal-dialog').css('width', '574px')
$('img[data-captcha]').css('width', 'auto')
let detectCaptchaBtn = $('<button type="button" class="btn btn-sm btn-success">尝试识别</button>')
input.css({
'font-family': 'Liana',
'font-size': '60px',
'text-align': 'center',
'min-height': '90px',
'padding-bottom': '26px'
})
$('#form-captcha').append(detectCaptchaBtn)
let keyboard = $('<div></div>')
keyboard.css({
'margin-top': '33px',
'font-family': 'Liana',
'display': 'flex',
'flex-wrap': 'wrap',
'align-items': 'center',
'justify-content': 'center'
})
let btnTypes = ['info', 'success', 'warning', 'danger', 'default']
for (let i = 0; i < textGroups.length; ++i) {
for (let j = 0; j < textGroups[i].length; ++j) {
let key = $(`<div class="btn btn-sm btn-${btnTypes[i % 5]}">${textGroups[i][j]}</div>`)
key.css({
'width': '60px',
'height': '60px',
'line-height': '52px',
'font-size': '46px',
'margin': '8px'
})
key.click(() => {
let caretPos = input[0].selectionStart
let currentText = input.val()
input.val(currentText.substring(0, caretPos) + textGroups[i][j] + currentText.substring(caretPos))
input[0].selectionStart = caretPos + 1
})
keyboard.append(key)
}
}
$('#form-captcha').append(keyboard)
detectCaptchaBtn.click(() => {
detectCaptchaBtn.html('<i class="fa fa-cog fa-spin"></i> 识别中')
window.setTimeout(() => {
let canvas = document.createElement('canvas')
let ctx = canvas.getContext('2d')
let captchaImg = $('img[data-captcha]')[0]
canvas.width = captchaImg.width
canvas.height = captchaImg.height
ctx.fillStyle = 'white'
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.drawImage(captchaImg, 0, 0)
let imageData = ctx.getImageData(1, 1, canvas.width - 2, canvas.height - 2) // 去黑边
// 灰度化 二值化
greyscale(imageData)
// 裁切
let boundary = getImageBoundaryTrim(imageData)
canvas.width = boundary.width
canvas.height = boundary.height
ctx.putImageData(imageData, -boundary.x, -boundary.y)
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height)
// 投影统计
let stats = new Array(imageData.height).fill(0)
for (let y = 0; y < imageData.height; y++) {
for (let x = 0; x < imageData.width; x++) {
let idx = (x + y * imageData.width) * 4
stats[y] += imageData.data[idx] === 255 ? 0 : 1
}
}
// 曲线平滑
for (let y = 0; y < stats.length; y++) {
let previous = y - 1 < 0 ? 0 : stats[y - 1]
let next = y + 1 >= stats.length ? 0 : stats[y + 1]
stats[y] = (previous + next + stats[y]) / 3
}
// 确认基线
let midTop = 0
let midBottom = imageData.height
const statsThreshold = 15
for (let y = 0; y < stats.length; y++) {
if (stats[y] < statsThreshold) continue
midTop = y
break
}
for (let y = stats.length - 1; y >= 0; y--) {
if (stats[y] < statsThreshold) continue
midBottom = y
break
}
// 重合度检测
let firstResult = []
const overlapThreshold = 0.85
for (let k = 0; k < knownChars.length; ++k) {
let testChar = knownChars[k]
let scaledImageData = scaleImageData(testChar.imageData, (midBottom - midTop) / 12)
// let scaledImageData = testChar.imageData
greyscale(scaledImageData)
let scaledImageHistogramX = new Array(scaledImageData.width).fill(0)
let scaledImageHistogramY = new Array(scaledImageData.height).fill(0)
let scaledImageCenter = { x: 0, y: 0 }
let scaledImageTotalGrey = 0
for (let x = 0; x < scaledImageData.width; ++x) {
for (let y = 0; y < scaledImageData.height; ++y) {
let grey = getPointGrey(scaledImageData, x, y)
if (grey === 0) {
scaledImageHistogramX[x]++
scaledImageHistogramY[y]++
}
scaledImageTotalGrey += grey
scaledImageCenter.x += x * grey
scaledImageCenter.y += y * grey
}
}
scaledImageCenter.x = scaledImageCenter.x / scaledImageTotalGrey
scaledImageCenter.y = scaledImageCenter.y / scaledImageTotalGrey
let scaledImageDiagonal = Math.sqrt(scaledImageData.width * scaledImageData.width + scaledImageData.height * scaledImageData.height)
let possibilities = []
for (let left = 0; left < imageData.width - scaledImageData.width; ++left) {
for (let top = 0; top < imageData.height - scaledImageData.height; ++top) {
let count = 0
let histogramX = new Array(scaledImageData.width).fill(0)
let histogramY = new Array(scaledImageData.height).fill(0)
let center = { x: 0, y: 0 }
let totalGrey = 0
for (let x = left; x < left + scaledImageData.width; ++x) {
for (let y = top; y < top + scaledImageData.height; ++y) {
let grey = getPointGrey(imageData, x, y)
if (grey === getPointGrey(scaledImageData, x - left, y - top)) count++
if (grey === 0) {
histogramX[x - left]++
histogramY[y - top]++
}
totalGrey += grey
center.x += (x - left) * grey
center.y += (y - top) * grey
}
}
let distenceX = 0, distenceY = 0
for (let x = 0; x < histogramX.length; ++x) {
distenceX += (histogramX[x] - scaledImageHistogramX[x]) * (histogramX[x] - scaledImageHistogramX[x])
}
for (let y = 0; y < histogramY.length; ++y) {
distenceY += (histogramY[y] - scaledImageHistogramY[y]) * (histogramY[y] - scaledImageHistogramY[y])
}
center.x = center.x / totalGrey
center.y = center.y / totalGrey
let o1 = count / (scaledImageData.width * scaledImageData.height)
let o2 = 1 - Math.sqrt(distenceX) / Math.sqrt(((scaledImageData.height * scaledImageData.height) * histogramX.length))
let o3 = 1 - Math.sqrt(distenceY) / Math.sqrt(((scaledImageData.width * scaledImageData.width) * histogramY.length))
let distenceCenter = Math.sqrt((center.x - scaledImageCenter.x) * (center.x - scaledImageCenter.x) + (center.y - scaledImageCenter.y) * (center.y - scaledImageCenter.y))
let o4 = 1 - distenceCenter / (scaledImageDiagonal / 2)
possibilities.push({
overlap: 0.55 * o1 + 0.2 * o2 + 0.2 * o3 + 0.05 * o4,
x: left,
y: top,
position: left + scaledImageData.width,
width: scaledImageData.width,
height: scaledImageData.height,
char: testChar.char
})
}
}
possibilities.sort((a, b) => b.overlap - a.overlap)
for (let i = 0; i < possibilities.length; ++i) {
if (possibilities[i].overlap < overlapThreshold) break
if (possibilities[i].y > imageData.height / 2) continue
firstResult.push(possibilities[i])
}
}
firstResult.sort((a, b) => a.position - b.position)
let secondResult = [], lastX = -3
const minCharWidth = 4
for (let i = 0; i < firstResult.length; ++i) {
if (secondResult.length === 0 || firstResult[i].position - lastX > minCharWidth) {
secondResult.push([firstResult[i]])
} else {
secondResult[secondResult.length - 1].push(firstResult[i])
}
lastX = firstResult[i].position
}
let finalResult = []
for (let i = 0; i < secondResult.length; ++i) {
secondResult[i].sort((a, b) => b.overlap - a.overlap)
finalResult.push(secondResult[i][0])
}
input.val(finalResult.map(r => r.char).join(''))
detectCaptchaBtn.text('尝试识别')
}, 300)
})
}
let intervalHandler = window.setInterval(() => {
if ($('#form-captcha')[0]) {
window.clearInterval(intervalHandler)
execute()
}
}, 500)
})()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment