First Commit

This commit is contained in:
2022-09-02 20:56:31 +03:00
parent 9745407c31
commit e2201ce3d4
9 changed files with 442 additions and 1 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules

1
.todo
View File

@@ -1 +0,0 @@
☐ Add button next to message

48
extension/background.js Normal file
View File

@@ -0,0 +1,48 @@
const activeTabs = {};
let contentScriptRerunner = null;
function runContentScript(tabId) {
chrome.tabs.executeScript(tabId, {
allFrames: true,
file: 'run.js',
});
}
function rerunContentScripts() {
// unfortunately we need to rerun periodically to handle iframes changing..
// eventually fixed when we require the new host permission or chrome releases a content script api
for (const tabId of Object.keys(activeTabs)) {
runContentScript(parseInt(tabId, 10));
}
}
function registerDynamicContentScript() {
chrome.tabs.onUpdated.addListener((tabId, {status}, {url}) => {
if (!status || !url) {
return;
}
runContentScript(tabId);
activeTabs[tabId] = true;
if (contentScriptRerunner == null) {
contentScriptRerunner = setInterval(rerunContentScripts, 5000);
}
});
chrome.tabs.onRemoved.addListener((tabId) => {
delete activeTabs[tabId];
if (contentScriptRerunner != null && Object.keys(activeTabs).length === 0) {
clearInterval(contentScriptRerunner);
contentScriptRerunner = null;
}
});
}
chrome.runtime.onInstalled.addListener(({reason}) => {
if (reason !== 'install') {
return;
}
});
registerDynamicContentScript();

BIN
extension/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

34
extension/manifest.json Normal file
View File

@@ -0,0 +1,34 @@
{
"background": {
"scripts": ["background.js"]
},
"browser_action": {
"default_popup": "settings.html"
},
"content_scripts": [
{
"all_frames": true,
"exclude_matches": [
"*://*.twitch.tv/*.html",
"*://*.twitch.tv/*.html?*",
"*://*.twitch.tv/*.htm",
"*://*.twitch.tv/*.htm?*"
],
"js": ["run.js"],
"matches": ["*://*.twitch.tv/*"]
}
],
"description": "Add notes to twitch users",
"externally_connectable": {
"matches": ["*://*.twitch.tv/*"]
},
"homepage_url": "https://rdarius.lt/projects/chrome-extensions/twitch-notes",
"icons": {
"128": "icon.png"
},
"manifest_version": 2,
"name": "Twitch Notes",
"permissions": ["*://*.twitch.tv/*"],
"version": "0.0.1",
"web_accessible_resources": ["twitchnotes.js", "twitchnotes.css"]
}

View File

@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="iso-8859-1"?>
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 508.1 508.1" style="enable-background:new 0 0 508.1 508.1;" xml:space="preserve">
<g>
<g>
<path d="M440.85,55.3h-168.3v-0.7c0-30.1-24.5-54.6-54.6-54.6c-30.1,0-54.6,24.5-54.6,54.6c0,0.2,0.1,0.5,0.1,0.7h-31.7
c-7.8,0-14.1,6.3-14.1,14.1v43.4h-50.4c-7.8,0-14.1,6.3-14.1,14.1v262.4c0,3.7,1.5,7.3,4.1,10L161.85,504c2.6,2.6,6.2,4.1,10,4.1
h204.5c7.8,0,14.1-6.3,14.1-14.1v-37.5h50.4c7.8,0,14.1-6.3,14.1-14.1v-373C454.95,61.6,448.65,55.3,440.85,55.3z M191.55,54.6
c0-14.5,11.8-26.4,26.4-26.4c14.6,0,26.4,11.8,26.4,26.4v0.7h-52.8C191.55,55.1,191.55,54.8,191.55,54.6z M145.85,83.5h98.5v29.3
h-98.5V83.5z M244.35,141v31c0,14.5-11.8,26.4-26.4,26.4s-26.4-11.8-26.4-26.4v-31H244.35z M157.75,459.8l-56.5-56.5h56.5V459.8z
M362.25,479.8h-176.2v-90.5c0-7.8-6.3-14.1-14.1-14.1h-90.6V141h82v31c0,30.1,24.5,54.6,54.6,54.6c30.1,0,54.6-24.5,54.6-54.6
v-31h89.7V479.8z M426.75,428.1h-36.3V126.9c0-7.8-6.3-14.1-14.1-14.1h-103.8V83.5h154.2V428.1z"/>
</g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
<g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

110
extension/run.js Normal file
View File

@@ -0,0 +1,110 @@
(function twitchnotes() {
if (document.querySelector("script#twitchnotes")) {
return;
}
const script = document.createElement("script");
script.type = "text/javascript";
script.src = chrome.runtime.getURL("twitchnotes.js");
script.id = "twitchnotes";
const head = document.getElementsByTagName("head")[0];
if (!head) {
return;
}
head.appendChild(script);
const style = document.createElement("style");
style.innerHTML = `
:root {
--window-close: #f77669;
--window-minify: #ffcb6b;
--window-expand: #c3e88d;
--background-header: #393d3e;
--background-code: #1f1e1e;
}
.twitchnote img {
height: 18px;
padding: 0 4px 0;
}
.twitch-note-container {
position: fixed;
z-index: 1000000;
min-width: 420px;
max-width: 95vw;
min-height: 240px;
max-height: 50vh;
background: var(--color-background-base);
border: var(--border-width-default) solid var(--color-border-base) !important;
}
.twitch-note-header {
width: 100%;
height: 32px;
line-height: 32px;
border-bottom: var(--border-width-default) solid var(--color-border-base) !important;
position: relative;
cursor: move;
}
.twitch-note-title {
color: var(--color-text-alt) !important;
font-size: var(--font-size-6) !important;
font-weight: var(--font-weight-semibold) !important;
text-transform: uppercase !important;
padding-left: calc((32px - var(--font-size-6)) / 2);
}
.twitch-note-close-button {
position: absolute;
right: 5px;
top: 5px;
width: 22px;
height: 22px;
line-height: 22px;
text-align: center;
aspect-ration: 1;
color: var(--color-text-alt) !important;
font-size: var(--font-size-6) !important;
font-weight: var(--font-weight-semibold) !important;
text-transform: uppercase !important;
border: var(--border-width-default) solid var(--color-border-base) !important;
cursor: pointer;
}
.twitch-note-content {
position: absolute;
top: 32px;
left: 0;
right: 0;
bottom: 32px;
background: transparent;
color: var(--color-text-base) !important;
font-family: var(--font-base);
vertical-align: baseline;
outline: none;
overflow: auto;
padding: calc((32px - var(--font-size-6)) / 2);
}
.twitch-note-save-button {
position: absolute;
bottom: 0;
left: 0;
right: 0;
height: 32px;
border-top: var(--border-width-default) solid var(--color-border-base) !important;
cursor: pointer;
color: var(--color-text-alt) !important;
font-size: var(--font-size-6) !important;
font-weight: var(--font-weight-semibold) !important;
text-transform: uppercase !important;
text-align: center;
line-height: 32px;
}
`;
head.appendChild(style);
})();

3
extension/settings.html Normal file
View File

@@ -0,0 +1,3 @@
<div style="width: 250px">
Exporting, Importing and Data clearing will be available in the future
</div>

200
extension/twitchnotes.js Normal file
View File

@@ -0,0 +1,200 @@
let mousePosition = { x: 0, y: 0 };
let lastMousePosition = { x: 0, y: 0 };
let openContainers = {};
let isMouseDown = false;
let activeContainer = null;
document.addEventListener("mousemove", handleMouseMove);
document.addEventListener("mouseup", handleMouseUp);
function saveNote(username, note) {
let userList = JSON.parse(
localStorage.getItem("twitch-note-all-users-list") || "[]"
);
if (!userList.includes(username)) {
userList.push(username);
localStorage.setItem(
"twitch-note-all-users-list",
JSON.stringify(userList)
);
}
localStorage.setItem("twitch-note-" + username, note);
}
function getNote(username) {
return localStorage.getItem("twitch-note-" + username) || "";
}
function handleMouseMove(event) {
event = event || window.event;
if (isMouseDown) {
if (activeContainer) {
const dx = mousePosition.x - lastMousePosition.x;
const dy = mousePosition.y - lastMousePosition.y;
openContainers[activeContainer].style.top =
parseInt(openContainers[activeContainer].style.top) + dy + "px";
openContainers[activeContainer].style.left =
parseInt(openContainers[activeContainer].style.left) + dx + "px";
}
}
updateMousePosition(event.clientX, event.clientY);
}
function handleMouseUp(event) {
activeContainer = null;
isMouseDown = false;
}
function updateMousePosition(x, y) {
lastMousePosition = { ...mousePosition };
mousePosition.x = x;
mousePosition.y = y;
}
function removeContainer(username) {
if (!openContainers[username]) return;
openContainers[username].remove();
delete openContainers[username];
}
function addContainer(username, container) {
if (openContainers[username]) return;
openContainers[username] = container;
document.body.appendChild(container);
}
function openTwitchNote(username) {
// CONTAINER
const container = document.createElement("div");
container.style.left = mousePosition.x + "px";
container.style.top = mousePosition.y + 10 + "px";
container.classList.add("twitch-note-container");
// HEADER
const header = document.createElement("div");
header.classList.add("twitch-note-header");
header.addEventListener("mousedown", function close() {
activeContainer = username;
isMouseDown = true;
});
const closeButton = document.createElement("span");
closeButton.classList.add("twitch-note-close-button");
closeButton.innerText = "X";
closeButton.addEventListener("click", function close() {
removeContainer(username);
});
header.appendChild(closeButton);
const title = document.createElement("span");
title.classList.add("twitch-note-title");
title.innerHTML = username;
header.appendChild(title);
container.appendChild(header);
// BODY
const content = document.createElement("div");
content.classList.add("twitch-note-content");
content.setAttribute("contentEditable", true);
content.innerHTML = getNote(username);
container.appendChild(content);
const saveButton = document.createElement("div");
saveButton.classList.add("twitch-note-save-button");
saveButton.innerText = "SAVE";
saveButton.addEventListener("click", function () {
saveNote(username, content.innerHTML);
removeContainer(username);
});
container.appendChild(saveButton);
addContainer(username, container);
}
(function runTwitchNotes() {
// Select the node that will be observed for mutations
let giveUpAt = Date.now() + 20000;
let changeInProgress = false;
let targetNode;
let buttonContainer;
do {
targetNode = document.getElementsByClassName(
"chat-scrollable-area__message-container"
)[0];
} while (!targetNode || giveUpAt < Date.now());
do {
buttonContainer = document.getElementsByClassName(
"chat-input__buttons-container"
)[0];
} while (!buttonContainer || giveUpAt < Date.now());
const settingsButton = document.createElement("div");
settingsButton.innerHTML = "<b>Notes</b>";
buttonContainer.lastChild.insertBefore(
settingsButton,
buttonContainer.lastChild.firstChild
);
// Options for the observer (which mutations to observe)
const config = { attributes: true, childList: true, subtree: true };
// Callback function to execute when mutations are observed
const callback = (mutationList, observer) => {
if (!changeInProgress) {
for (const mutation of mutationList) {
if (mutation.type === "childList") {
changeInProgress = true;
let nodes = document.getElementsByClassName("chat-line__message");
let users = [];
for (let node of nodes) {
const n = node.querySelector(".chat-author__display-name");
if (!n) continue;
const usernameContainer = node.querySelector(
".chat-line__username-container"
);
const username = n.attributes["data-a-user"].value;
if (username) {
let noteButton = usernameContainer.querySelector(".twitchnote");
if (!noteButton) {
const twitchNote = document.createElement("span");
twitchNote.classList.add("twitchnote");
twitchNote.style.cursor = "pointer";
twitchNote.addEventListener("click", () => {
openTwitchNote(username);
});
const img = document.createElement("img");
img.src = "https://cdn.rdarius.lt/icons/32-id-card.png";
twitchNote.appendChild(img);
usernameContainer.insertBefore(
twitchNote,
usernameContainer.firstChild
);
}
if (!users[username]) {
users[username] = 0;
}
users[username]++;
}
}
changeInProgress = false;
}
}
}
};
// Create an observer instance linked to the callback function
const observer = new MutationObserver(callback);
// Start observing the target node for configured mutations
observer.observe(targetNode, config);
})();