코드 ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼ ▼
// ==UserScript==
// @name 채팅로그서식 roll20.net
// @namespace Violentmonkey Scripts
// @match https://app.roll20.net/campaigns/chatarchive/*
// @grant none
// @version 1.0
// @author -
// @description 2024. 10. 26. 오후 6:26:58
// ==/UserScript==
function chatStyle(theme = '', color = ''){
var addStyle = [];
if(theme == ''){
var font = 'serif';
var pointfont = 'monospace';
}
if(theme == 'gothic'){
addStyle[0] = new FontFace('NanumsquareNeo', 'url(https://hangeul.pstatic.net/hangeul_static/webfont/NanumSquareNeo/NanumSquareNeoTTF-bRg.woff)');
addStyle[1] = new FontFace('chosungulim', 'url(https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_20-04@1.0/ChosunGu.woff)');
var font = 'NanumsquareNeo';
var pointfont = 'chosungulim';
}
else if(theme == 'serif'){
addStyle[0] = new FontFace('Chosunilbo_myungjo', 'url(https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_one@1.0/Chosunilbo_myungjo.woff)');
addStyle[1] = new FontFace('BookkMyungjo', 'url(https://fastly.jsdelivr.net/gh/projectnoonnu/noonfonts_2302@1.0/BookkMyungjo-Bd.woff2)');
var font = 'Chosunilbo_myungjo';
var pointfont = 'BookkMyungjo';
}
addStyle.map(function(font){
font.load().then(function(loadedFont)
{
document.fonts.add(loadedFont);
})
});
if(color == 'white' || color == ''){
var backcolor = 'white';
var textcolor = 'black';
var textbackcolor = 'rgb(215, 215, 215)';
var desccolor = 'lightgray';
} else if (color == 'black'){
var backcolor = 'rgb(35,35,35)';
var textcolor = 'white';
var textbackcolor = 'rgb(80, 80, 80)';
var desccolor = 'rgb(50, 50, 50)';
}
// 이미지가 포함된 .avatar 요소의 다음 .by 요소 숨기기
document.querySelectorAll(".avatar img").forEach((avatar) => {
const byElement = avatar.closest(".avatar").nextElementSibling.nextElementSibling;
if (byElement && byElement.classList.contains("by")) {
byElement.style.display = "none";
}
});
// 특정 클래스 요소의 스타일 초기화
document.querySelectorAll(".message.whisper .by").forEach((byElement) => {
byElement.style.display = "";
});
// 인장 스타일 지정
document.querySelectorAll('.message .avatar').forEach((avatar) => {
avatar.style.borderRadius = '10px';
avatar.style.overflow = 'hidden';
});
let allMessage = Array.from(document.querySelectorAll(".message:not(:has(table)):not(:has(.inlinerollresult))"));
allMessage.forEach((content) => {
// 기존 HTML 구조에서 텍스트만 추출하여 처리
let nodetext = '';
content.childNodes.forEach((node) => {
if (node.nodeType === Node.TEXT_NODE) {
nodetext = nodetext + node.textContent + '§§'; // innerHTML을 textContent로 수정
} else if (node.nodeName === 'EM') {
nodetext = nodetext + node.textContent + '§§'; // innerHTML을 textContent로 수정
}
});
let modifiedText = nodetext.replace(/(["'])(?=\S*(?:[가-힣a-zA-Z0-9!@#$%^&*()_+{}|:"<>?~`=\[\]\\;',./-]))(\S{2,}|[^"']+?)(\1)/g, (match) => {
return `<span style="color: ${textcolor}; background: ${textbackcolor}; padding: 2px 9px; font-size: 13px; font-family: ${pointfont};">${match.replace(/(\.{3})/g, '…')}</span>`;
});
if(modifiedText) nodelist = modifiedText.split('§§');
else nodelist = nodetext.split('§§');
let newhtml = '';
let num = 0;
content.childNodes.forEach((node) => {
if (node.nodeType === Node.TEXT_NODE) {
newhtml += nodelist[num];
num++;
} else if(node.nodeName === 'EM') {
newhtml += '<em>' + nodelist[num] + '</em>';
num++;
}
else{
newhtml += node.outerHTML;
}
});
content.innerHTML = newhtml;
});
document.querySelectorAll('.message.you').forEach((message) => {
message.classList.remove("you");
});
document.querySelectorAll(".message").forEach((message) => {
message.style.lineHeight = '1.5em';
message.style.fontSize = '12px';
message.style.fontFamily = font;
message.style.color = textcolor;
});
document.querySelectorAll('.message.desc').forEach((desc) => {
desc.style.fontFamily = pointfont;
desc.style.fontStyle = 'normal';
desc.style.fontSize = '13px';
desc.style.backgroundColor = desccolor;
desc.innerHTML = desc.innerHTML.replace(/(\.{3})/g, '…');
});
document.querySelectorAll(".message.desc + .message.desc .spacer").forEach((spacer) => {
spacer.style.display = "none";
});
document.querySelectorAll('.message.general').forEach((general) => {
general.style.backgroundColor = backcolor;
});
document.querySelectorAll('.spacer').forEach((spacer) => {
spacer.style.backgroundColor = backcolor;
});
document.querySelectorAll('table').forEach((table) => {
table.style.width = '300px';
table.style.overflow = 'hidden';
});
}
var paginateToggleButton = document.getElementById("paginateToggle");
// '채팅 서식' 버튼 생성
var chatFormatButton = document.createElement("button");
chatFormatButton.classList = 'btn btn-primary';
chatFormatButton.textContent = "채팅 로그 서식 적용";
chatFormatButton.id = "chatFormatButton";
chatFormatButton.style.marginLeft = "10px";
chatFormatButton.addEventListener("click", function() {
chatStyle('', '');
});
paginateToggleButton.insertAdjacentElement("afterend", chatFormatButton);
https://violentmonkey.github.io/
유저 스크립트 추가해주는 확.프면 뭐든 상관 X Greasemonkey나 어쩌고 monkey는 다 비슷할 것... 이미 쓰고 계신 게 있다면 거기다가 넣어주세요 맨 위 userscript 부분만 적당히 수정해주면 될듯!!
get started 눌러서 맞는 확장 프로그램을 받아요
응용프로그램 눌러서 + 누르고 위에 있는 코드를 싹 복사해서 넣어주세요
저장하고 챗로그 펼치면 버튼이 나옵니다!!
그 외 스타일 >
아래에서 세번째 줄의 chatStyle('', ''); 부분에 조건을 넣어줄 수 있어요~
테마 / 색상 순으로 넣어줍니다
●첫번째 변수: gothic, serif 로 수정 가능
1. 빈칸일 때는 브라우저 글꼴 설정의 serif (기본 폰트) 와 고정폭 글꼴 (대사, desc) 이 적용돼요
브라우저 글꼴 맞춤 설정에서 직접 수정해주시면 ok
2. gothic 혹은 serif는 웹페이지에 폰트를 적용시키지만 드래그해서 pdf 인쇄하는 경우에 함께 출력되지 않음을 감안해주세요
(pdf 인쇄 하실 거면 빈칸 추천!!)
●두번째 변수: black 으로 수정 가능
1. 빈칸일 때는 밝은 톤으로 들어갑니다 ~_~
2. black으로 수정하면 챗로그가 다크모드가 돼요
ex > chatStyle('gothic', 'black'); 방식으로 수정 / 저장해 바꿔쓰세요
내가 사담~~~ 선언 롤플 "말" 형식으로 놀아서... "이렇게" 혹은 '이렇게' 되어있는 부분만 강조되는데
적당히 수정해서 써주셔도 ok입니다하 ㅡ " ㅡ 나 ㅍ" ㅍ 처럼 자/모음 사이에 끼워진 건 인식 안 하도록 되어있다네요 (임티써야돼...)