mirror of
https://git.boykissers.com/pawkey/pawkey-sk.git
synced 2025-12-20 12:14:18 +00:00
feat: improve fingerprinting
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { $i } from "@/i.js";
|
||||
import { computed, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { computed, onMounted, ref } from "vue";
|
||||
|
||||
const generateRandomClass = () => {
|
||||
const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
let result = '';
|
||||
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
let result = "";
|
||||
for (let i = 0; i < 8; i++) {
|
||||
result += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
@@ -12,39 +12,113 @@ const generateRandomClass = () => {
|
||||
};
|
||||
|
||||
const gridClass = ref(generateRandomClass());
|
||||
const itemClass = ref(generateRandomClass());
|
||||
const itemClasses = ref<string[]>([]);
|
||||
const recreationKey = ref(0);
|
||||
let regenerationInterval: ReturnType<typeof setInterval> | null = null;
|
||||
let styleElement: HTMLStyleElement | null = null;
|
||||
let allCreatedClasses: string[] = [];
|
||||
|
||||
const encodeTextToPath = (text: string) => {
|
||||
const charPaths: { [key: string]: string } = {
|
||||
a: "M2,8 L4,2 L6,2 L8,8 M3,6 L7,6",
|
||||
b: "M2,2 L2,8 L6,8 C7,8 8,7 8,6 C8,5 7,4 6,4 L6,4 C7,4 8,3 8,2 C8,1 7,2 6,2 Z",
|
||||
c: "M8,3 C7,2 6,2 5,2 C3,2 2,3 2,5 C2,7 3,8 5,8 C6,8 7,8 8,7",
|
||||
d: "M2,2 L2,8 L5,8 C7,8 8,7 8,5 C8,3 7,2 5,2 Z",
|
||||
e: "M2,2 L2,8 L8,8 M2,2 L8,2 M2,5 L6,5",
|
||||
f: "M2,2 L2,8 M2,2 L8,2 M2,5 L6,5",
|
||||
g: "M8,3 C7,2 6,2 5,2 C3,2 2,3 2,5 C2,7 3,8 5,8 L8,8 L8,5 L5,5",
|
||||
h: "M2,2 L2,8 M8,2 L8,8 M2,5 L8,5",
|
||||
i: "M3,2 L7,2 M5,2 L5,8 M3,8 L7,8",
|
||||
j: "M8,2 L8,7 C8,8 7,8 6,8 C5,8 4,8 4,7",
|
||||
k: "M2,2 L2,8 M8,2 L2,5 M4,5 L8,8",
|
||||
l: "M2,2 L2,8 L8,8",
|
||||
m: "M2,8 L2,2 L5,5 L8,2 L8,8",
|
||||
n: "M2,8 L2,2 L8,8 L8,2",
|
||||
o: "M2,3 C2,2 3,2 5,2 C7,2 8,2 8,3 L8,7 C8,8 7,8 5,8 C3,8 2,8 2,7 Z",
|
||||
p: "M2,2 L2,8 M2,2 L6,2 C7,2 8,3 8,4 C8,5 7,6 6,6 L2,6",
|
||||
q: "M2,3 C2,2 3,2 5,2 C7,2 8,2 8,3 L8,7 C8,8 7,8 5,8 C3,8 2,8 2,7 Z M6,6 L8,8",
|
||||
r: "M2,2 L2,8 M2,2 L6,2 C7,2 8,3 8,4 C8,5 7,6 6,6 L2,6 M6,6 L8,8",
|
||||
s: "M8,3 C7,2 5,2 5,2 C3,2 2,3 2,4 C2,5 3,5 5,5 C7,5 8,6 8,7 C8,8 7,8 5,8 C3,8 2,7 2,7",
|
||||
t: "M2,2 L8,2 M5,2 L5,8",
|
||||
u: "M2,2 L2,7 C2,8 3,8 5,8 C7,8 8,8 8,7 L8,2",
|
||||
v: "M2,2 L5,8 L8,2",
|
||||
w: "M2,2 L3,8 L5,5 L7,8 L8,2",
|
||||
x: "M2,2 L8,8 M8,2 L2,8",
|
||||
y: "M2,2 L5,5 L8,2 M5,5 L5,8",
|
||||
z: "M2,2 L8,2 L2,8 L8,8",
|
||||
" ": "",
|
||||
"0": "M2,3 C2,2 3,2 5,2 C7,2 8,2 8,3 L8,7 C8,8 7,8 5,8 C3,8 2,8 2,7 Z M2,2 L8,8",
|
||||
"1": "M4,2 L5,2 L5,8 M3,8 L7,8",
|
||||
"2": "M2,3 C2,2 3,2 5,2 C7,2 8,2 8,3 C8,4 7,5 5,5 L2,8 L8,8",
|
||||
"3": "M2,2 L8,2 C8,2 8,3 8,4 C8,5 7,5 5,5 C7,5 8,5 8,6 C8,7 8,8 8,8 L2,8",
|
||||
"4": "M2,2 L2,5 L8,5 M6,2 L6,8",
|
||||
"5": "M8,2 L2,2 L2,5 L6,5 C7,5 8,6 8,7 C8,8 7,8 5,8 C3,8 2,7 2,7",
|
||||
"6": "M8,2 C7,2 5,2 5,2 C3,2 2,3 2,5 L2,7 C2,8 3,8 5,8 C7,8 8,7 8,6 C8,5 7,5 5,5 L2,5",
|
||||
"7": "M2,2 L8,2 L5,8",
|
||||
"8": "M5,2 C3,2 2,3 2,4 C2,5 3,5 5,5 C7,5 8,5 8,4 C8,3 7,2 5,2 Z M5,5 C3,5 2,6 2,7 C2,8 3,8 5,8 C7,8 8,7 8,6 C8,5 7,5 5,5 Z",
|
||||
"9": "M8,5 C8,3 7,2 5,2 C3,2 2,3 2,4 C2,5 3,5 5,5 L8,5 L8,7 C8,8 7,8 5,8 C3,8 2,7 2,7",
|
||||
};
|
||||
|
||||
let path = "";
|
||||
const chars = text.toLowerCase().split("");
|
||||
let xOffset = 0;
|
||||
|
||||
chars.forEach((char, index) => {
|
||||
if (charPaths[char]) {
|
||||
const translatedPath = charPaths[char]
|
||||
.replace(/([ML])\s*(\d+),(\d+)/g, (match, command, x, y) => {
|
||||
return `${command}${parseInt(x) + xOffset},${y}`;
|
||||
})
|
||||
.replace(
|
||||
/([C])\s*(\d+),(\d+)\s+(\d+),(\d+)\s+(\d+),(\d+)/g,
|
||||
(match, command, x1, y1, x2, y2, x3, y3) => {
|
||||
return `${command}${parseInt(x1) + xOffset},${y1} ${parseInt(x2) + xOffset},${y2} ${parseInt(x3) + xOffset},${y3}`;
|
||||
},
|
||||
);
|
||||
path += translatedPath + " ";
|
||||
}
|
||||
xOffset += 10;
|
||||
});
|
||||
|
||||
return path.trim();
|
||||
};
|
||||
|
||||
const generateItemClasses = (count: number) => {
|
||||
const classes: string[] = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
classes.push(generateRandomClass());
|
||||
}
|
||||
return classes;
|
||||
};
|
||||
|
||||
const regenerateWatermark = () => {
|
||||
cleanupOldElements();
|
||||
|
||||
gridClass.value = generateRandomClass();
|
||||
itemClass.value = generateRandomClass();
|
||||
itemClasses.value = generateItemClasses(600);
|
||||
recreationKey.value++;
|
||||
|
||||
allCreatedClasses.push(gridClass.value, itemClass.value);
|
||||
allCreatedClasses.push(gridClass.value, ...itemClasses.value);
|
||||
|
||||
createStyleElement();
|
||||
};
|
||||
|
||||
const cleanupOldElements = () => {
|
||||
const allStyles = document.head.querySelectorAll('style');
|
||||
allStyles.forEach(style => {
|
||||
const content = style.textContent || '';
|
||||
const hasOldClasses = allCreatedClasses.some(className =>
|
||||
content.includes(className) &&
|
||||
className !== gridClass.value &&
|
||||
className !== itemClass.value
|
||||
const allStyles = document.head.querySelectorAll("style");
|
||||
allStyles.forEach((style) => {
|
||||
const content = style.textContent || "";
|
||||
const hasOldClasses = allCreatedClasses.some(
|
||||
(className) =>
|
||||
content.includes(className) &&
|
||||
className !== gridClass.value &&
|
||||
!itemClasses.value.includes(className),
|
||||
);
|
||||
if (hasOldClasses) {
|
||||
style.remove();
|
||||
}
|
||||
});
|
||||
|
||||
allCreatedClasses = [gridClass.value, itemClass.value];
|
||||
allCreatedClasses = [gridClass.value, ...itemClasses.value];
|
||||
};
|
||||
|
||||
const createStyleElement = () => {
|
||||
@@ -52,63 +126,78 @@ const createStyleElement = () => {
|
||||
styleElement.parentNode.removeChild(styleElement);
|
||||
}
|
||||
|
||||
styleElement = document.createElement('style');
|
||||
const styles = `
|
||||
.${gridClass.value} {
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)) !important;
|
||||
grid-template-rows: repeat(auto-fill, minmax(20px, 1fr)) !important;
|
||||
gap: 20px !important;
|
||||
padding: 20px !important;
|
||||
pointer-events: none !important;
|
||||
z-index: 999999999 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
styleElement = document.createElement("style");
|
||||
|
||||
.${itemClass.value} {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
color: color-mix(
|
||||
in srgb,
|
||||
var(--MI_THEME-fg) 1.2%,
|
||||
transparent
|
||||
) !important;
|
||||
font-size: 12px !important;
|
||||
font-family: monospace !important;
|
||||
word-break: break-all !important;
|
||||
text-align: center !important;
|
||||
user-select: none !important;
|
||||
}
|
||||
`;
|
||||
const itemStyles = itemClasses.value
|
||||
.map(
|
||||
(className) => `
|
||||
.${className} {
|
||||
display: flex !important;
|
||||
align-items: center !important;
|
||||
justify-content: center !important;
|
||||
}
|
||||
|
||||
.${className} svg {
|
||||
opacity: 0.02 !important;
|
||||
width: auto !important;
|
||||
height: 12px !important;
|
||||
}
|
||||
|
||||
.${className} svg path {
|
||||
stroke: var(--MI_THEME-fg) !important;
|
||||
fill: none !important;
|
||||
stroke-width: 1 !important;
|
||||
}
|
||||
`,
|
||||
)
|
||||
.join("");
|
||||
|
||||
const styles = `
|
||||
.${gridClass.value} {
|
||||
position: fixed !important;
|
||||
top: 0 !important;
|
||||
left: 0 !important;
|
||||
width: 100vw !important;
|
||||
height: 100vh !important;
|
||||
display: grid !important;
|
||||
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr)) !important;
|
||||
grid-template-rows: repeat(auto-fill, minmax(20px, 1fr)) !important;
|
||||
gap: 20px !important;
|
||||
padding: 20px !important;
|
||||
pointer-events: none !important;
|
||||
z-index: 999999999 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
||||
${itemStyles}
|
||||
`;
|
||||
|
||||
styleElement.textContent = styles;
|
||||
document.head.appendChild(styleElement);
|
||||
};
|
||||
|
||||
const watermarkText = computed(() => $i?.id || "not signed in");
|
||||
const pathData = computed(() => encodeTextToPath(watermarkText.value));
|
||||
const textWidth = computed(() => watermarkText.value.length * 10 + 10);
|
||||
|
||||
onMounted(() => {
|
||||
itemClasses.value = generateItemClasses(600);
|
||||
allCreatedClasses.push(gridClass.value, ...itemClasses.value);
|
||||
createStyleElement();
|
||||
|
||||
regenerationInterval = setInterval(() => {
|
||||
itemClasses.value = generateItemClasses(600);
|
||||
allCreatedClasses.push(gridClass.value, ...itemClasses.value);
|
||||
regenerateWatermark();
|
||||
}, 5000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div v-if="$i" :key="`signed-${recreationKey}`" :class="gridClass">
|
||||
<div v-for="n in 600" :key="n" :class="itemClass">
|
||||
{{ $i.id }}
|
||||
</div>
|
||||
</div>
|
||||
<div v-else :key="`unsigned-${recreationKey}`" :class="gridClass">
|
||||
<div v-for="n in 600" :key="n" :class="itemClass">
|
||||
not signed in
|
||||
<div :key="`watermark-${recreationKey}`" :class="gridClass">
|
||||
<div v-for="n in 600" :key="n" :class="itemClasses[n]">
|
||||
<svg :viewBox="`0 0 ${textWidth} 10`" xmlns="http://www.w3.org/2000/svg">
|
||||
<path :d="pathData" />
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user