Files
wordpress-theme-aksrt/assets/js/weixin-share.js
T

482 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* AKSRT Weixin Share
*
* Bridge-first WeChat share helper with QR fallback.
*
* @package AKSRT
*/
(function () {
'use strict';
/**
* Local QR code data URL generator — no third-party services.
* Uses canvas rendering with a minimal QR matrix encoder.
*/
function buildQRCodeDataUrl(text, size) {
size = size || 160;
try {
var matrix = generateQRMatrix(text || ' ');
if (!matrix) return '';
var canvas = document.createElement('canvas');
var moduleCount = matrix.length;
var scale = Math.floor(size / (moduleCount + 8));
var margin = Math.floor((size - scale * moduleCount) / 2);
canvas.width = size;
canvas.height = size;
var ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, size, size);
ctx.fillStyle = '#000000';
for (var r = 0; r < moduleCount; r++) {
for (var c = 0; c < moduleCount; c++) {
if (matrix[r][c]) {
ctx.fillRect(margin + c * scale, margin + r * scale, scale, scale);
}
}
}
return canvas.toDataURL('image/png');
} catch (e) {
return '';
}
}
/**
* Minimal QR Code matrix generator (Version 1-6, Byte mode, Error Correction L).
* Returns a 2D boolean array or null on failure.
*/
function generateQRMatrix(text) {
var data = encodeUTF8(text);
// Choose version based on data length (EC level L, Byte mode)
var version = 1;
var capacities = [0, 17, 32, 53, 78, 106, 134];
for (var v = 1; v <= 6; v++) {
if (data.length <= capacities[v]) { version = v; break; }
if (v === 6) return null; // data too long for small versions
}
var moduleCount = version * 4 + 17;
var matrix = createMatrix(moduleCount);
var reserved = createMatrix(moduleCount);
// Place finder patterns
placeFinder(matrix, reserved, 0, 0);
placeFinder(matrix, reserved, moduleCount - 7, 0);
placeFinder(matrix, reserved, 0, moduleCount - 7);
// Place alignment patterns (version >= 2)
if (version >= 2) {
var alignPos = getAlignmentPositions(version);
for (var i = 0; i < alignPos.length; i++) {
for (var j = 0; j < alignPos.length; j++) {
if (isInFinder(alignPos[i], alignPos[j], moduleCount)) continue;
placeAlignment(matrix, reserved, alignPos[i], alignPos[j]);
}
}
}
// Place timing patterns
for (var i = 8; i < moduleCount - 8; i++) {
if (!reserved[6][i]) { matrix[6][i] = (i % 2 === 0); reserved[6][i] = true; }
if (!reserved[i][6]) { matrix[i][6] = (i % 2 === 0); reserved[i][6] = true; }
}
// Reserve format info areas
for (var i = 0; i < 8; i++) {
reserved[8][i] = true; reserved[i][8] = true;
reserved[8][moduleCount - 1 - i] = true; reserved[moduleCount - 1 - i][8] = true;
}
reserved[8][8] = true;
if (version >= 2) {
for (var i = moduleCount - 11; i < moduleCount - 8; i++) {
for (var j = 0; j < 6; j++) { reserved[j][i] = true; reserved[i][j] = true; }
}
}
// Dark module
matrix[moduleCount - 8][8] = true;
reserved[moduleCount - 8][8] = true;
// Encode data
var dataBits = encodeData(data, version);
placeData(matrix, reserved, dataBits, moduleCount);
// Apply best mask
var bestMask = 0;
var bestPenalty = Infinity;
for (var m = 0; m < 8; m++) {
var masked = applyMask(matrix, reserved, m, moduleCount);
var penalty = calcPenalty(masked, moduleCount);
if (penalty < bestPenalty) { bestPenalty = penalty; bestMask = m; }
}
matrix = applyMask(matrix, reserved, bestMask, moduleCount);
// Place format info
placeFormatInfo(matrix, bestMask, moduleCount);
return matrix;
}
function createMatrix(n) {
var m = [];
for (var i = 0; i < n; i++) { m[i] = []; for (var j = 0; j < n; j++) m[i][j] = false; }
return m;
}
function encodeUTF8(str) {
var bytes = [];
for (var i = 0; i < str.length; i++) {
var c = str.charCodeAt(i);
if (c < 0x80) bytes.push(c);
else if (c < 0x800) { bytes.push(0xc0 | (c >> 6)); bytes.push(0x80 | (c & 0x3f)); }
else { bytes.push(0xe0 | (c >> 12)); bytes.push(0x80 | ((c >> 6) & 0x3f)); bytes.push(0x80 | (c & 0x3f)); }
}
return bytes;
}
function placeFinder(m, r, row, col) {
for (var dr = -1; dr <= 7; dr++) {
for (var dc = -1; dc <= 7; dc++) {
var rr = row + dr, cc = col + dc;
if (rr < 0 || rr >= m.length || cc < 0 || cc >= m.length) continue;
var val = (dr >= 0 && dr <= 6 && (dc === 0 || dc === 6)) || (dc >= 0 && dc <= 6 && (dr === 0 || dr === 6)) || (dr >= 2 && dr <= 4 && dc >= 2 && dc <= 4);
m[rr][cc] = val;
r[rr][cc] = true;
}
}
}
function placeAlignment(m, r, row, col) {
for (var dr = -2; dr <= 2; dr++) {
for (var dc = -2; dc <= 2; dc++) {
var val = Math.abs(dr) === 2 || Math.abs(dc) === 2 || (dr === 0 && dc === 0);
m[row + dr][col + dc] = val;
r[row + dr][col + dc] = true;
}
}
}
function getAlignmentPositions(v) {
if (v <= 1) return [];
if (v === 2) return [6, 18];
if (v === 3) return [6, 22];
if (v === 4) return [6, 26];
if (v === 5) return [6, 30];
return [6, 34];
}
function isInFinder(row, col, n) {
return (row < 9 && col < 9) || (row < 9 && col >= n - 8) || (row >= n - 8 && col < 9);
}
function encodeData(data, version) {
var bits = [];
// Mode indicator: Byte = 0100
bits.push(0, 1, 0, 0);
// Character count
var ccBits = version <= 9 ? 8 : 16;
for (var i = ccBits - 1; i >= 0; i--) bits.push((data.length >> i) & 1);
// Data
for (var i = 0; i < data.length; i++) {
for (var j = 7; j >= 0; j--) bits.push((data[i] >> j) & 1);
}
// Terminator
var capacities = [0, 152, 272, 440, 640, 864, 1088];
var cap = capacities[version] || 152;
for (var i = 0; i < 4 && bits.length < cap; i++) bits.push(0);
// Pad to byte boundary
while (bits.length % 8 !== 0) bits.push(0);
// Pad bytes
var padBytes = [0xEC, 0x11];
var pi = 0;
while (bits.length < cap) {
var pb = padBytes[pi % 2];
for (var j = 7; j >= 0; j--) bits.push((pb >> j) & 1);
pi++;
}
return bits;
}
function placeData(matrix, reserved, bits, n) {
var bitIndex = 0;
for (var right = n - 1; right >= 1; right -= 2) {
if (right === 6) right = 5;
for (var vert = 0; vert < n; vert++) {
for (var j = 0; j < 2; j++) {
var col = right - j;
var upward = ((right + 1) & 2) === 0;
var row = upward ? n - 1 - vert : vert;
if (!reserved[row][col] && bitIndex < bits.length) {
matrix[row][col] = bits[bitIndex] === 1;
bitIndex++;
}
}
}
}
}
function applyMask(matrix, reserved, maskNum, n) {
var result = createMatrix(n);
for (var r = 0; r < n; r++) {
for (var c = 0; c < n; c++) {
result[r][c] = matrix[r][c];
if (!reserved[r][c]) {
var invert = false;
switch (maskNum) {
case 0: invert = (r + c) % 2 === 0; break;
case 1: invert = r % 2 === 0; break;
case 2: invert = c % 3 === 0; break;
case 3: invert = (r + c) % 3 === 0; break;
case 4: invert = (Math.floor(r / 2) + Math.floor(c / 3)) % 2 === 0; break;
case 5: invert = (r * c) % 2 + (r * c) % 3 === 0; break;
case 6: invert = ((r * c) % 2 + (r * c) % 3) % 2 === 0; break;
case 7: invert = ((r + c) % 2 + (r * c) % 3) % 2 === 0; break;
}
if (invert) result[r][c] = !result[r][c];
}
}
}
return result;
}
function calcPenalty(matrix, n) {
var penalty = 0;
// Rule 1: consecutive same-color modules in row/column
for (var r = 0; r < n; r++) {
var run = 1;
for (var c = 1; c < n; c++) {
if (matrix[r][c] === matrix[r][c - 1]) { run++; } else { run = 1; }
if (run === 5) penalty += 3;
else if (run > 5) penalty += 1;
}
}
for (var c = 0; c < n; c++) {
var run = 1;
for (var r = 1; r < n; r++) {
if (matrix[r][c] === matrix[r - 1][c]) { run++; } else { run = 1; }
if (run === 5) penalty += 3;
else if (run > 5) penalty += 1;
}
}
return penalty;
}
function placeFormatInfo(matrix, maskNum, n) {
var formatInfo = getFormatInfo(maskNum);
// Place around top-left finder
var positions = [
[8, 0], [8, 1], [8, 2], [8, 3], [8, 4], [8, 5], [8, 7], [8, 8],
[7, 8], [5, 8], [4, 8], [3, 8], [2, 8], [1, 8], [0, 8]
];
for (var i = 0; i < 15; i++) {
var r = positions[i][0], c = positions[i][1];
matrix[r][c] = formatInfo[i] === 1;
}
// Place around bottom-left and top-right finders
var positions2 = [
[n - 1, 8], [n - 2, 8], [n - 3, 8], [n - 4, 8], [n - 5, 8], [n - 6, 8], [n - 7, 8],
[8, n - 8], [8, n - 7], [8, n - 6], [8, n - 5], [8, n - 4], [8, n - 3], [8, n - 2], [8, n - 1]
];
for (var i = 0; i < 15; i++) {
var r = positions2[i][0], c = positions2[i][1];
matrix[r][c] = formatInfo[i] === 1;
}
}
function getFormatInfo(maskNum) {
// EC level L = 01, mask number = maskNum
var data = (0x01 << 3) | maskNum; // 5 bits
var bits = data << 10;
// BCH(15,5) error correction
var gen = 0x537; // generator polynomial
for (var i = 14; i >= 10; i--) {
if (bits & (1 << i)) bits ^= gen << (i - 10);
}
var formatBits = (data << 10) | bits;
// XOR with mask pattern
formatBits ^= 0x5412;
var result = [];
for (var i = 14; i >= 0; i--) result.push((formatBits >> i) & 1);
return result;
}
function isWeixinEnv() {
return /micromessenger/i.test(window.navigator.userAgent || '') || !!window.WeixinJSBridge;
}
function normalizePayload(payload) {
payload = payload || {};
return {
title: payload.title || document.title,
url: payload.url || window.location.href,
desc: payload.desc || '',
image: payload.image || ''
};
}
function hideQrFallback(qrBox) {
if (!qrBox) return;
qrBox.classList.remove('is-visible');
qrBox.setAttribute('aria-hidden', 'true');
}
function showQrFallback(payload) {
var qrBox = payload.qrBox;
var qrImage = payload.qrImage;
if (!qrBox || !qrImage || !payload.url) {
return Promise.resolve({
mode: 'error',
message: '当前页面缺少微信二维码容器'
});
}
if (qrImage.dataset.qrValue !== payload.url || !qrImage.getAttribute('src')) {
qrImage.dataset.qrValue = payload.url;
var dataUrl = buildQRCodeDataUrl(payload.url, 160);
if (dataUrl) {
qrImage.src = dataUrl;
} else {
qrImage.alt = '二维码生成失败,请直接复制链接分享';
}
}
qrBox.classList.add('is-visible');
qrBox.setAttribute('aria-hidden', 'false');
return Promise.resolve({
mode: 'qr',
message: '请使用微信扫一扫分享'
});
}
function bridgeWithWxSdk(payload) {
return new Promise(function (resolve, reject) {
if (!window.wx || typeof window.wx.ready !== 'function') {
reject(new Error('wx sdk unavailable'));
return;
}
var done = false;
var timer = window.setTimeout(function () {
if (done) return;
done = true;
reject(new Error('微信分享桥接未就绪'));
}, 1200);
function finishSuccess() {
if (done) return;
done = true;
window.clearTimeout(timer);
resolve({
mode: 'bridge',
message: '已同步微信分享信息,请使用右上角菜单继续分享'
});
}
function finishError(error) {
if (done) return;
done = true;
window.clearTimeout(timer);
reject(error instanceof Error ? error : new Error('微信分享桥接失败'));
}
try {
if (typeof window.wx.error === 'function') {
window.wx.error(function (error) {
finishError(new Error((error && error.errMsg) || '微信签名校验失败'));
});
}
window.wx.ready(function () {
try {
if (typeof window.wx.updateAppMessageShareData === 'function') {
window.wx.updateAppMessageShareData({
title: payload.title,
desc: payload.desc,
link: payload.url,
imgUrl: payload.image
});
}
if (typeof window.wx.updateTimelineShareData === 'function') {
window.wx.updateTimelineShareData({
title: payload.title,
link: payload.url,
imgUrl: payload.image
});
}
finishSuccess();
} catch (error) {
finishError(error);
}
});
} catch (error) {
finishError(error);
}
});
}
function bridgeWithWeixinJsBridge(payload) {
return new Promise(function (resolve, reject) {
var bridge = window.WeixinJSBridge;
if (!bridge || typeof bridge.invoke !== 'function') {
reject(new Error('WeixinJSBridge unavailable'));
return;
}
try {
bridge.invoke('sendAppMessage', {
title: payload.title,
desc: payload.desc,
link: payload.url,
img_url: payload.image
}, function (response) {
var errMsg = response && response.err_msg ? response.err_msg : '';
if (!errMsg || /:ok$/i.test(errMsg)) {
resolve({
mode: 'bridge',
message: '已调起微信分享面板'
});
return;
}
reject(new Error(errMsg));
});
} catch (error) {
reject(error);
}
});
}
function openWeixinShare(payload) {
var data = normalizePayload(payload);
data.qrBox = payload && payload.qrBox ? payload.qrBox : null;
data.qrImage = payload && payload.qrImage ? payload.qrImage : null;
if (!isWeixinEnv()) {
return showQrFallback(data);
}
return bridgeWithWxSdk(data)
.catch(function () {
return bridgeWithWeixinJsBridge(data);
})
.catch(function (error) {
return showQrFallback(data).then(function (result) {
result.message = (error && error.message ? error.message + ',' : '') + '已切换为二维码分享';
return result;
});
});
}
window.AksrtShare = Object.assign({}, window.AksrtShare || {}, {
isWeixinEnv: isWeixinEnv,
hideQrFallback: hideQrFallback,
showQrFallback: showQrFallback,
openWeixinShare: openWeixinShare,
// Exposed so the unified share-data-builder can render poster QR codes
// without a second encoder.
buildQrDataUrl: buildQRCodeDataUrl
});
})();