// ==UserScript==
// @name Arcalive Night Restaurant Secretary
// @namespace Violentmonkey Scripts
// @match https://arca.live/b/*
// @grant GM_xmlhttpRequest
// @grant GM_registerMenuCommand
// @version 1.0.1.2
// @author -
// @description 심야식당 이용 보조 유틸
// @noframes
// ==/UserScript==
// onload init
let loaded = false
window.addEventListener('load', e => {
if (!loaded) {
loaded = true
registerMenuCommand();
asideMake();
//easyFroalaInsertImageTrigger()
autoWriteEditor();
arcaconLinkInit();
clipboardPasteToBase64Init();
userInfoViewInit();
imageSaveContextMenuInit();
}
});
// 배열 자르기, 페이지네이션
Array.prototype.division = function(n) {
var arr = this;
var len = arr.length;
var cnt = Math.floor(len / n) + (Math.floor(len % n) > 0 ? 1 : 0);
var tmp = [];
for (var i = 0; i < cnt; i++) {
tmp.push(arr.splice(0, n));
}
return tmp;
}
// 바이트값 정리 함수
function formatBytes(bytes, decimals = 2) {
if (bytes === 0) return '-';
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
}
// 키 시뮬레이트
function simulateKey(keyCode, type, modifiers) {
var evtName = (typeof(type) === "string") ? "key" + type : "keydown";
var modifier = (typeof(modifiers) === "object") ? modifier : {};
var event = document.createEvent("HTMLEvents");
event.initEvent(evtName, true, false);
event.keyCode = keyCode;
for (var i in modifiers) {
event[i] = modifiers[i];
}
document.dispatchEvent(event);
}
// froalaeditor 이미지 삽입 감지 (미완성)
async function easyFroalaInsertImageTrigger() {
if (location.href.includes("/write") || location.href.includes("/edit")) {
let imageResizer = document.querySelector(".fr-image-resizer")
const editorTextarea = document.querySelector("#content")
let oldMatch = 0
while (true) {
await sleep(100)
let newMatch = editorTextarea.value.match(/<img.*?class="fr-fic fr-dii".*?>/g)
if (newMatch && oldMatch !== newMatch.length) {
oldMatch = newMatch.length
await sleep(500)
simulateKey(37);
await sleep(10)
simulateKey(37, "up");
simulateKey(40);
await sleep(10)
simulateKey(40, "up");
await sleep(10)
simulateKey(13, "up");
} else if (newMatch === null) {
oldMatch = 0
}
}
}
}
// fetch 함수
function doFetch(url, options = {
method: 'GET',
responseType: 'document'
}, silent = false) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
url: url,
method: options.method,
responseType: options.responseType,
headers: options.headers,
data: options.data,
onload: result => {
console.debug(result)
if (result.status == 200) {
resolve(result.response);
} else {
if (!silent) {
console.log(result)
alert("불러오기 에러 발생 : " + url)
reject(result.status);
} else {
console.debug(result)
reject(result.status);
}
}
}
});
});
}
// 사이드바 추가
function asideMake() {
const rightSidebar = document.querySelector(".right-sidebar")
const sidebarItem = document.createElement('div')
sidebarItem.setAttribute("class", "sidebar-item")
sidebarItem.setAttribute("id", "smtranslation")
const itemTitle = document.createElement('div')
itemTitle.setAttribute("class", "item-title")
itemTitle.innerText = "번역 소식"
var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = '.sm-translate-tab { padding-bottom: .25rem;border-bottom: 0 solid #00a495;margin:8.7%;cursor:pointer;transition-duration: 0.1s }';
style.innerHTML += '.sm-translate-tab-activated { padding-bottom: .25rem;border-bottom: 2px solid #00a495;margin:8.7%;cursor:pointer;transition-duration: 0.1s }';
style.innerHTML += '.sm-translate-arrow-left { margin: auto 5px;float:right;width: 0; height: 0; border-top: 5px solid transparent; border-bottom: 5px solid transparent; border-right: 7px solid black;cursor:pointer }';
style.innerHTML += '.sm-translate-arrow-right { margin: auto 5px;float:right;width: 0; height: 0; border-top: 5px solid transparent; border-bottom: 5px solid transparent; border-left: 7px solid black;cursor:pointer }';
document.getElementsByTagName('head')[0].appendChild(style);
const smallTitle = document.createElement('div')
smallTitle.setAttribute("style", "margin-bottom:12px")
const linkList = document.createElement('div')
linkList.setAttribute("class", "link-list clearfix")
const arrowDiv = document.createElement('div')
arrowDiv.setAttribute("class", "sm-translation-arrows")
const arrowRight = document.createElement("div")
arrowRight.setAttribute("class", "sm-translate-arrow-right")
const arrowLeft = document.createElement("div")
arrowLeft.setAttribute("class", "sm-translate-arrow-left")
arrowDiv.appendChild(arrowRight)
arrowDiv.appendChild(arrowLeft)
const translating = document.createElement('span')
translating.setAttribute("class", "sm-translate-tab-activated")
translating.innerText = "번역중"
translating.onclick = (e) => {
activateTab(e)
arrowDiv.setAttribute("style", "display: none")
translatingPageNum = 0
// 카테고리: 실황, 키워드: 번역 / 색인
getTranslatePageFetch([
`https://arca.live/b/smpeople?target=content&keyword=${srt}`,
"https://arca.live/b/smpeople?category=실황&target=title&keyword=번역"
], linkList, arrowRight, arrowLeft)
.then(t => {
if (t) {
arrowDiv.removeAttribute("style")
}
})
}
const notranslate = document.createElement('span')
notranslate.setAttribute("class", "sm-translate-tab")
notranslate.innerText = "미번역"
notranslate.onclick = (e) => {
activateTab(e)
arrowDiv.setAttribute("style", "display: none")
translatingPageNum = 0
// 키워드: 번역요청
getTranslatePageFetch([
"https://arca.live/b/smpeople?target=title&keyword=번역요청"
], linkList, arrowRight, arrowLeft)
.then(t => {
if (t) {
arrowDiv.removeAttribute("style")
}
})
}
const dotranslate = document.createElement('span')
dotranslate.setAttribute("class", "sm-translate-tab")
dotranslate.innerText = "찜하기"
dotranslate.onclick = (e) => {
activateTab(e)
arrowDiv.setAttribute("style", "display: none")
translatingPageNum = 0
makeReservation(linkList, arrowRight, arrowLeft)
.then(t => {
arrowDiv.removeAttribute("style")
})
}
smallTitle.appendChild(translating)
smallTitle.appendChild(notranslate)
smallTitle.appendChild(dotranslate)
sidebarItem.appendChild(itemTitle)
sidebarItem.appendChild(smallTitle)
sidebarItem.appendChild(linkList)
sidebarItem.appendChild(arrowDiv)
rightSidebar.prepend(sidebarItem)
translating.click()
}
// 사이드바 내용 채우기
function getTranslatePageFetch(urlList, element, arrowRight, arrowLeft, nonPage = false) {
return new Promise(async (resolve, reject) => {
if (typeof urlList === "string") {
urlList = [urlList]
}
let articleMetaArray = [];
let articleArray = [];
for (let url of urlList) {
await doFetch(url)
.then(dom => {
let articleList = dom.querySelector(".article-list").querySelectorAll(".vrow:not(.notice):not(.head)")
articleList.forEach(a => {
let article = new Object
article.href = a.href.split("?")[0]
let articleSplit = a.querySelector(".title").innerText.split(/(\[?.?번역요청.?\]|\(?.?번역요청.?\))/)
article.title = articleSplit[2] || articleSplit[0]
article.author = a.querySelector("span.vcol.col-author > span > span:nth-child(1)").innerText
let articleSpan = document.createElement('span')
articleSpan.setAttribute("class", "leaf-info float-right")
articleSpan.style.margin = 'auto 0 auto 10px'
articleSpan.innerText = article.author
let articleA = document.createElement('a')
articleA.setAttribute("href", article.href)
articleA.setAttribute("target", "_blank")
articleA.style.padding = '.15rem 0 .15rem 0'
articleA.appendChild(articleSpan)
articleA.append(article.title)
articleArray.push(articleA)
})
})
}
if (nonPage) {
articleMetaArray = [articleArray]
if (articleArray.length > 10) {
contentStretch()
}
} else {
articleMetaArray = articleArray.division(10)
}
arrowRight.onclick = (e) => {
translatingPage(element, articleMetaArray, 1)
}
arrowLeft.onclick = (e) => {
translatingPage(element, articleMetaArray, -1)
}
translatingPage(element, articleMetaArray, 0)
if (articleMetaArray[0].length > 0) {
resolve(true)
} else {
resolve(false)
}
})
}
// 본문 늘리기
function contentStretch() {
const contentWrapper = document.querySelector(".root-container > .content-wrapper")
contentWrapper.setAttribute("style", "padding-bottom:" + contentWrapper.offsetHeight / 2 + "px")
document.querySelectorAll(".ad").forEach(ad => {
ad.remove()
})
}
// 페이지 이동
let translatingPageNum = 0
function translatingPage(element, arr, delta) {
let tmpPage = translatingPageNum + delta
let pageContent = arr[tmpPage]
if (arr[0].length > 0 && arr[tmpPage]) {
translatingPageNum = tmpPage
element.innerHTML = ''
pageContent.forEach(content => {
element.appendChild(content)
})
}
}
// 번역찜
function makeReservation(element, arrowRight, arrowLeft) {
return new Promise((resolve, reject) => {
const inputGroup = document.createElement("div")
inputGroup.setAttribute("class", "input-group input-group-sm mb-3")
inputGroup.setAttribute("style", "border-radius: 5px;")
const inputGroupPrepend = document.createElement("div")
inputGroupPrepend.setAttribute("class", "input-group-prepend")
const inputGroupButton = document.createElement("div")
inputGroupButton.setAttribute("class", "btn btn-sm btn-arca")
inputGroupButton.setAttribute("data-toggle", "dropdown")
inputGroupButton.innerText = "플랫폼"
inputGroupPrepend.appendChild(inputGroupButton)
const inputGroupDropdown = document.createElement("div")
inputGroupDropdown.setAttribute("class", "dropdown-menu")
const inputCode = document.createElement("input")
inputCode.setAttribute("class", "form-control")
inputCode.setAttribute("placeholder", "플랫폼을 선택하세요")
inputCode.setAttribute("type", "text")
const dropdowns = ["dlsite", "hitomi", "e-hentai", "getchu", "fanza"]
dropdowns.forEach(str => {
const inputGroupDropdownMenu = document.createElement("div")
inputGroupDropdownMenu.setAttribute("class", "dropdown-item")
inputGroupDropdownMenu.setAttribute("style", "cursor:pointer")
inputGroupDropdownMenu.onclick = function(e) {
inputGroupButton.innerText = e.target.innerText
switch (e.target.innerText) {
case "dlsite":
inputCode.setAttribute("placeholder", "작품 코드 또는 주소")
break;
case "hitomi":
inputCode.setAttribute("placeholder", "자료 코드 또는 주소")
break;
case "e-hentai":
inputCode.setAttribute("placeholder", "자료 주소")
break;
case "getchu":
case "fanza":
inputCode.setAttribute("placeholder", "작품 주소")
break;
}
}
inputGroupDropdownMenu.innerText = str
inputGroupDropdown.appendChild(inputGroupDropdownMenu)
})
inputGroupPrepend.appendChild(inputGroupDropdown)
inputGroup.appendChild(inputGroupPrepend)
inputGroup.appendChild(inputCode)
const checkboxDiv = document.createElement("div")
const checkboxValues = [
["역자", "식자"],
["손번역", "기계번역", "이미지번역"],
["개인", "팀", "동료 모집"]
]
easyCheckbox(checkboxDiv, checkboxValues, "sm-translation-reservation-checkbox")
const inputDescription = document.createElement("textarea")
inputDescription.setAttribute("class", "form-control")
inputDescription.setAttribute("style", "border-color: #ccc;border-radius: 5px;margin-bottom:10px;resize:none;height:10rem")
inputDescription.setAttribute("placeholder", "덧붙이는 글 (선택)")
const submitButton = document.createElement("div")
submitButton.setAttribute("class", "btn btn-sm btn-arca float-right")
submitButton.setAttribute("style", "margin:5px")
submitButton.innerText = "번역찜"
submitButton.onclick = function(e) {
const optionList = []
let optionStr
document.querySelectorAll(".sm-translation-reservation-checkbox").forEach(chkb => {
if (chkb.checked) {
optionList.push(chkb.value)
}
})
if (optionList.length > 0) {
optionStr = arr2str(optionList)
} else {
optionStr = "선택되지 않음"
}
doReservation(inputGroupButton.innerText, inputCode.value, element, arrowRight, arrowLeft)
.then(async data => {
const dataJSON = data[1]
let title
if (optionStr.includes("모집")) {
title = `[번역찜] [모집] ${dataJSON.title[1]}`
} else {
title = `[번역찜] ${dataJSON.title[1]}`
}
if (title.length > 50) {
title = title.substring(0, 49) + "…"
}
dataJSON.sm_translation_option = ["번역찜 옵션", optionStr]
let content
await makeTable(dataJSON, true).then(table => {
content = (table.outerHTML + `<p><br></p><p>${inputDescription.value}</p>` + indexerHTML(data[2]))
})
const editorButton = document.createElement("div")
editorButton.setAttribute("class", "btn btn-sm btn-arca float-right")
editorButton.setAttribute("style", "margin:5px")
editorButton.innerText = "에디터에서 수정"
editorButton.onclick = function(e) {
localStorage.setItem('sm_translation_tmp_title', title);
localStorage.setItem('sm_translation_tmp_content', content.replace(/\n/g, "<br>"));
location.href = "/b/smpeople/write/"
}
const directButton = document.createElement("div")
directButton.setAttribute("class", "btn btn-sm btn-arca float-right")
directButton.setAttribute("style", "margin:5px")
directButton.innerText = "번역찜 제출"
directButton.onclick = function(e) {
submitReservation(title, content.replace(/\n/g, "<br>"))
}
element.appendChild(directButton)
element.appendChild(editorButton)
if (data[0]) {
alert("번통사고 예방을 위해 목록을 확인해 주십시오")
} else {
submitButton.remove()
}
})
}
element.innerHTML = ''
element.appendChild(inputGroup)
element.appendChild(checkboxDiv)
element.appendChild(inputDescription)
element.appendChild(submitButton)
})
}
// 체크박스 만들기
function easyCheckbox(element, valMetaList, className) {
valMetaList.forEach(valList => {
let checkbox
let checkboxLabel
let pTag = document.createElement("p")
valList.forEach(val => {
checkbox = document.createElement("input")
checkbox.setAttribute("class", className)
checkbox.setAttribute("type", "checkbox")
checkbox.setAttribute("value", val)
checkboxLabel = document.createElement("label")
checkboxLabel.setAttribute("style", "margin:auto 5px")
checkboxLabel.appendChild(checkbox)
checkboxLabel.append(` ${val}`)
pTag.appendChild(checkboxLabel)
})
element.appendChild(pTag)
})
}
// 색인
const srt = "Q4VR D8W2 9H5K 0PFK 3JM7 GX0M"
function indexerHTML(str) {
let a = `<p style="font-size:0;">${str} ${srt}</p>`
return a
}
// 번역찜 하기
function doReservation(platform, code, element, arrowRight, arrowLeft) {
return new Promise((resolve, reject) => {
let ProcessedCode
let parser
ProcessedCode = code.split("?")[0]
switch (platform) {
case "dlsite":
ProcessedCode = dlsiteAuto(code)
parser = dlInfo
break;
case "hitomi":
ProcessedCode = hitomiAuto(code)
parser = hitomiInfo
break;
case "e-hentai":
ProcessedCode = ehAuto(code)
parser = ehInfo
break;
case "getchu":
ProcessedCode = getchuAuto(code)
parser = getchuInfo
break;
case "fanza":
ProcessedCode = fanzaAuto(code)
parser = fanzaInfo
break;
default:
alert("플랫폼을 선택해 주십시오")
break;
}
parser(ProcessedCode).then(obj => {
let searchList = [
`https://arca.live/b/smpeople?target=content&keyword=${ProcessedCode} ${srt}`,
`https://arca.live/b/smpeople?category=번역&target=all&keyword=${obj.title[1]}`,
`https://arca.live/b/smpeople?category=실황&target=all&keyword=${obj.title[1]} 번역`
]
let numCode = ProcessedCode.match(/[A-Z]{2}\d+/g)
if (numCode && numCode.length === 1) {
searchList.push(`https://arca.live/b/smpeople?target=all&keyword=${numCode[0].replace(/[A-Z]{2}(\d+)/g, "$1")} 번역`)
}
getTranslatePageFetch(searchList, element, arrowRight, arrowLeft, true)
.then(t => {
resolve([t, obj, ProcessedCode])
})
})
})
}
// 번역찜 글 제출
function submitReservation(title, content) {
const url = "https://arca.live/b/smpeople/write"
getWriteToken()
.then(tokenList => {
fetch(url, {
method: 'POST',
body: new URLSearchParams({
'_csrf': tokenList[0],
'token': tokenList[1],
'contentType': 'html',
'category': '실황',
'title': String(title),
'content': String(content)
})
})
.then(response => {
if (response.ok) {
location.href = response.url
} else {
console.log(response)
alert("번역찜 글이 제출 과정에서 오류가 발생했습니다.")
}
})
})
}
// 에디터 자동 내용 채우기
async function autoWriteEditor() {
if (location.href.includes("/write")) {
const title = localStorage.getItem('sm_translation_tmp_title');
const content = localStorage.getItem('sm_translation_tmp_content');
if (title && content) {
localStorage.removeItem('sm_translation_tmp_title')
localStorage.removeItem('sm_translation_tmp_content')
let titleBox = document.querySelector("#inputTitle")
let editorBox = document.querySelector('.write-body .fr-element')
while (titleBox === null || editorBox === null) {
await sleep(10)
titleBox = document.querySelector("#inputTitle")
editorBox = document.querySelector('.write-body .fr-element')
}
titleBox.value = title
editorBox.innerHTML = content
document.querySelector("#category-실황").click()
}
}
}
// 글쓰기 토큰
function getWriteToken() {
return new Promise((resolve, reject) => {
if (location.href.includes("/write") || location.href.includes("/edit")) {
const csrf = document.querySelector("input[name=_csrf]")
const token = document.querySelector("input[name=token]")
if (csrf && token) {
resolve([csrf.value, token.value])
}
} else {
let url = "https://arca.live/b/smpeople/write"
doFetch(url)
.then(html => {
const csrf = html.querySelector("input[name=_csrf]")
const token = html.querySelector("input[name=token]")
if (csrf && token) {
resolve([csrf.value, token.value])
}
})
}
})
}
// 이미지(Blob) 업로드 함수
function UploadImageFile(Blob, pretoken = null, original = false) {
return new Promise(async (resolve, reject) => {
var xhr = new XMLHttpRequest();
var formData = new FormData();
if (!document.querySelector("#article_write_form > input[name=token]")) {
await getWriteToken()
.then(tokenList => {
pretoken = tokenList[1]
})
}
formData.append('upload', Blob, Math.random().toString(36).substring(7) + '.jpg');
formData.append('token', pretoken || document.querySelector("#article_write_form > input[name=token]").value);
formData.append('saveExif', original);
formData.append('saveFilename', false);
xhr.onload = function() {
responseJSON = JSON.parse(xhr.responseText)
if (responseJSON.uploaded === true) {
resolve(responseJSON.url)
} else {
alert("이미지 업로드 실패 : " + xhr.responseText)
console.error(xhr.responseText);
}
}
xhr.open("POST", "https://arca.live/b/upload");
xhr.send(formData);
});
}
// 탭 전환
function activateTab(e) {
const tab = document.querySelector(".sm-translate-tab-activated")
tab.setAttribute("class", "sm-translate-tab")
e.target.setAttribute("class", "sm-translate-tab-activated")
}
// 배열을 문자열로 정리
function arr2str(arr, opt = ", ") {
let idx = arr.indexOf("")
while (idx > -1) {
arr.splice(idx, 1)
idx = arr.indexOf("")
}
return String(arr).replace(/"/g, '').replace('[', "").replace(']', "").replace(/,/g, opt)
}
// 문자열 정리
function strTrim(str) {
return str.replace(/\n/g, '').replace(/^\s+|\s+$/g, '').replace(/ +/g, " ").trim()
}
// JSON을 글 양식을 표로
async function makeTable(obj, getTable = false) {
if (location.href.includes("/write") || location.href.includes("/edit") || getTable) {
const table = document.createElement("table")
table.setAttribute("style", "width:100%")
const tbody = document.createElement("tbody")
for (let [key, value] of Object.entries(obj)) {
if (value[1]) {
const tr = document.createElement("tr")
const tdA = document.createElement("td")
tdA.setAttribute("style", "width: 20%; text-align: center;")
tdA.innerText = value[0]
const tdB = document.createElement("td")
tdB.setAttribute("style", "width: 80%; text-align: center;")
tdB.innerHTML = value[1]
tr.appendChild(tdA)
tr.appendChild(tdB)
tbody.appendChild(tr)
}
}
table.appendChild(tbody)
if (!getTable) {
let editorBox = document.querySelector('.write-body .fr-element')
while (editorBox === null) {
await sleep(10)
editorBox = document.querySelector('.write-body .fr-element')
}
editorBox.appendChild(table)
} else {
return table
}
} else {
return obj
}
}
// dlsite api사용
function dlInfo(code, getTable = false) {
return new Promise((resolve, reject) => {
const url = `https://www.dlsite.com/maniax/api/=/product.json?workno=${code}&locale=ko-K`
doFetch(url, {
method: 'GET',
responseType: 'application/json'
})
.then(result => {
const dlJSON = JSON.parse(result)[0]
if (dlJSON) {
const dlProcessedJSON = Object()
dlProcessedJSON.title = ["제목", dlJSON["work_name"]]
doFetch(`https:${dlJSON["image_main"]["url"]}`, {
method: "GET",
responseType: "blob"
})
.then(blob => {
UploadImageFile(blob)
.then(imgsrc => {
dlProcessedJSON.image = ["이미지", `<img src='${imgsrc}' alt='이미지'>`]
dlProcessedJSON.work_type = ["분류", `${dlJSON["work_type"]} / ${dlJSON["work_type_string"]}`]
dlProcessedJSON.file_info = ["파일(용량)", `${dlJSON["file_type"]}(${formatBytes(dlJSON["contents_file_size"])})`]
if (dlJSON["creaters"]) {
dlProcessedJSON.creators = []
for (const [key, value] of Object.entries(dlJSON["creaters"])) {
let tmpArr = []
value.forEach(name => {
tmpArr.push(name.name)
})
dlProcessedJSON.creators.push(key + ': ' + arr2str(tmpArr, "|"))
}
dlProcessedJSON.creators = ["만든이", arr2str(dlProcessedJSON.creators, " / ").replace(/\|/g, ", ")]
}
dlProcessedJSON.maker = ["서클", dlJSON["maker_name"]]
if (dlJSON["author"]) {
dlProcessedJSON.authors = []
for (const [key, value] of Object.entries(dlJSON["author"])) {
dlProcessedJSON.authors.push(value.author_name)
}
dlProcessedJSON.authors = ["작가", arr2str(dlProcessedJSON.authors)]
}
resolve(dlProcessedJSON)
if (dlJSON["genres"]) {
dlProcessedJSON.genres = []
dlJSON["genres"].forEach(genre => {
dlProcessedJSON.genres.push(genre["name"])
})
dlProcessedJSON.genres = ["태그", arr2str(dlProcessedJSON.genres)]
}
if (dlJSON["trials"]) {
dlProcessedJSON.trial = ["체험판", `<a href='https:${dlJSON["trials"][0]["url"]}'>체험판 다운로드(${dlJSON["trials"][0]["file_size_unit"]})</a>`]
}
dlProcessedJSON.url = ["출처", `<a href='https://www.dlsite.com/maniax/work/=/product_id/${code}.html'>보러가기</a>`]
resolve(makeTable(dlProcessedJSON, getTable))
})
})
} else {
alert("유효하지 않은 코드입니다!")
}
})
})
}
// dlsite 정보 채우기
function dlsiteAuto(code = null) {
let result = code || window.prompt("dlsite 코드 또는 주소를 입력하세요")
if (result && result.match(/[A-Z]{2}\d+/g)) {
if (!code) {
dlInfo(result.match(/[A-Z]{2}\d+/g)[0])
} else {
return result.match(/[A-Z]{2}\d+/g)[0]
}
} else {
alert("dlsite 코드 또는 주소를 읽을 수 없습니다!")
}
}
// hitomi api사용
function hitomiInfo(code, getTable = false) {
return new Promise(async (resolve, reject) => {
const url = `https://hitomi.la/galleries/${code}.html`
await doFetch(url)
.then(async html => {
const redirect = html.querySelector("body > a")
await doFetch(redirect.href)
.then(async html => {
const hitomiProcessedJSON = Object()
const infoBox = html.querySelector(".content")
const author = html.querySelector(".gallery > h2 > ul")
const authors = []
author.querySelectorAll("li").forEach(tag => {
authors.push(tag.innerText)
})
const imgUrl = infoBox.querySelector(".cover > a > picture > img").srcset.split(" 2x")[0]
await doFetch(imgUrl, {
method: 'GET',
responseType: 'blob'
})
.then(blob => {
UploadImageFile(blob)
.then(imgsrc => {
hitomiProcessedJSON.title = ["제목", infoBox.querySelector(".gallery > h1").innerText]
hitomiProcessedJSON.image = ["표지", `<img src='${imgsrc}' alt='표지'>`]
hitomiProcessedJSON.author = ["작가", arr2str(authors)]
infoBox.querySelector(".gallery-info > table > tbody").querySelectorAll("tr").forEach(tr => {
let rtd1 = tr.querySelector("td:nth-child(1)")
let rtd2 = tr.querySelector("td:nth-child(2)")
if (rtd1 && rtd2) {
let td1 = strTrim(rtd1.innerText)
let td2 = strTrim(rtd2.innerText)
switch (td1) {
case "Group":
hitomiProcessedJSON.group = ["그룹", td2]
break;
case "Type":
hitomiProcessedJSON.type = ["분류", td2]
break;
case "Language":
hitomiProcessedJSON.language = ["언어", td2]
break;
case "Series":
hitomiProcessedJSON.series = ["시리즈", td2]
break;
case "Characters":
let charList = []
tr.querySelectorAll("li").forEach(tag => {
charList.push(strTrim(tag.innerText))
})
hitomiProcessedJSON.characters = ["캐릭터", arr2str(charList)]
break;
case "Tags":
let tagList = []
tr.querySelectorAll("li").forEach(tag => {
tagList.push(strTrim(tag.innerText))
})
hitomiProcessedJSON.tags = ["태그", arr2str(tagList)]
break;
}
}
})
hitomiProcessedJSON.url = ["출처", `<a href='${url}'>보러가기</a>`]
resolve(makeTable(hitomiProcessedJSON, getTable))
})
})
})
})
})
}
// hitomi 정보 채우기
function hitomiAuto(code = null) {
let result = code || window.prompt("hitomi 코드 또는 주소를 입력하세요");
if (result && result.match(/\d+\.htm/)) {
if (!code) {
hitomiInfo(result.match(/\d+\.htm/)[0].replace(".htm", ""))
} else {
return result.match(/\d+\.htm/)[0].replace(".htm", "")
}
} else {
if (result && result.match(/\d+/g) && result.match(/\d+/g).length === 1) {
if (!code) {
hitomiInfo(result.match(/\d+/g)[0])
} else {
return result.match(/\d+/g)[0]
}
} else {
alert("hitomi 코드 또는 주소를 읽을 수 없습니다!")
}
}
}
// hitomi 작품 전체 가져오기
async function hitomiGetImage(code) {
function subdomain_from_url(url, base) {
var retval = 'b';
if (base) {
retval = base;
}
var number_of_frontends = 3;
var b = 16;
var r = /\/[0-9a-f]\/([0-9a-f]{2})\//;
var m = r.exec(url);
if (!m) {
return 'a';
}
var g = parseInt(m[1], b);
if (!isNaN(g)) {
var o = 0;
if (g < 0x80) {
o = 1;
}
if (g < 0x40) {
o = 2;
}
retval = String.fromCharCode(97 + o) + retval;
}
return retval;
}
function url_from_url(url, base) {
return url.replace(/\/\/..?\.hitomi\.la\//, '//' + subdomain_from_url(url, base) + '.hitomi.la/');
}
function full_path_from_hash(hash) {
if (hash.length < 3) {
return hash;
}
return hash.replace(/^.*(..)(.)$/, '$2/$1/' + hash);
}
function url_from_hash(galleryid, image, dir, ext) {
ext = ext || dir || image.name.split('.').pop();
dir = dir || 'images';
return 'https://a.hitomi.la/' + dir + '/' + full_path_from_hash(image.hash) + '.' + ext;
}
function url_from_url_from_hash(galleryid, image, dir, ext, base) {
return url_from_url(url_from_hash(galleryid, image, dir, ext), base);
}
const ltnUrl = `https://ltn.hitomi.la/galleries/${code}.js`
const reqReferer = `https://hitomi.la/reader/${code}.html`
let editorBox
editorBox = document.querySelector('.write-body .fr-element')
while (editorBox === null) {
await sleep(10)
editorBox = document.querySelector('.write-body .fr-element')
}
await doFetch(ltnUrl, {
method: 'GET',
responseType: 'application/json'
})
.then(async str => {
const hitomiJSON = JSON.parse(str.replace("var galleryinfo = ", ""))
let fileProgress = document.createElement("progress");
fileProgress.setAttribute("value", "0")
fileProgress.setAttribute("max", "1")
fileProgress.setAttribute("style", "width:100%;")
fileProgress.setAttribute("id", "sm-translation-hitomiAuto-fileProgress")
const row = document.querySelector("#article_write_form > div.row > div")
row.appendChild(fileProgress)
let index = 0
for (let dict of hitomiJSON.files) {
let url = url_from_url_from_hash(code, dict)
var tmpPromise = await doFetch(url, {
method: "GET",
responseType: 'blob',
headers: {
referer: reqReferer
}
})
.then(async res => {
var tmpPromise = await UploadImageFile(res).then(url => {
let imgTag = document.createElement("img")
imgTag.setAttribute("src", url)
editorBox.appendChild(imgTag)
})
})
index += 1
fileProgress.value = index / hitomiJSON.files.length
}
fileProgress.remove()
})
}
// hitomi 전체 가져오기
function hitomiFetch() {
let result = window.prompt("hitomi코드를 입력하세요");
if (result && result.match(/\d+\.htm/)) {
hitomiGetImage(result.match(/\d+\.htm/)[0].replace(".htm", ""))
} else {
if (result && result.match(/\d+/g) && result.match(/\d+/g).length === 1) {
hitomiGetImage(result.match(/\d+/g)[0])
} else {
alert("hitomi 코드 또는 주소를 읽을 수 없습니다!")
}
}
}
// e-hentai 정보 가져오기
function ehInfo(url, getTable = false) {
return new Promise((resolve, reject) => {
const apiUrl = "https://api.e-hentai.org/api.php"
const gidlist = url.match(/\d+\/\w+/)[0].split("/")
doFetch(apiUrl, {
method: "POST",
responseType: 'json',
data: JSON.stringify({
"method": "gdata",
"gidlist": [gidlist],
"namespace": 1
})
})
.then(json => {
const ehJSON = json.gmetadata[0]
const ehProcessedJSON = Object()
doFetch(ehJSON.thumb.replace("_l", "_250"), {
method: 'GET',
responseType: 'blob'
})
.then(blob => {
UploadImageFile(blob)
.then(imgsrc => {
ehProcessedJSON.title = ["제목", ehJSON.title]
ehProcessedJSON.thumb = ["표지", `<img src='${imgsrc}' alt='표지'>`]
const ehTags = Object()
ehTags.artist = []
ehTags.group = []
ehTags.character = []
ehTags.language = []
ehTags.male = []
ehTags.female = []
ehTags.tags = []
ehJSON.tags.forEach(tag => {
let tagsl = tag.split(":")
switch (tagsl[0]) {
case "artist":
ehTags.artist.push(tagsl[1]);
break;
case "group":
ehTags.group.push(tagsl[1]);
break;
case "character":
ehTags.character.push(tagsl[1]);
break;
case "parody":
ehTags.character.push(tagsl[1]);
break;
case "language":
ehTags.language.push(tagsl[1]);
break;
case "male":
ehTags.male.push(tagsl[1] + " ♂");
break;
case "female":
ehTags.female.push(tagsl[1] + " ♀");
break;
default:
ehTags.tags.push(tagsl[1]);
break;
}
})
ehProcessedJSON.author = ["작가", arr2str(ehTags.artist)]
ehProcessedJSON.group = ["그룹", arr2str(ehTags.group)]
ehProcessedJSON.language = ["언어", arr2str(ehTags.language)]
ehProcessedJSON.character = ["캐릭터", arr2str(ehTags.character)]
ehProcessedJSON.tags = ["태그", arr2str(ehTags.tags.concat(ehTags.female.concat(ehTags.male)))]
ehProcessedJSON.filecount = ["분량", `${ehJSON.filecount}장(${formatBytes(ehJSON.filesize)})`]
ehProcessedJSON.url = ["출처", `<a href='${url}'>보러가기</a>`]
resolve(makeTable(ehProcessedJSON, getTable))
})
})
})
})
}
// e-hentai 정보 채우기
function ehAuto(code = null) {
let result = code || window.prompt("e-hentai 주소를 입력하세요");
if (result && result.includes("e-hentai")) {
if (!code) {
ehInfo(result)
} else {
return result
}
}
}
// getchu 파싱
function getchuInfo(url, getTable = false) {
return new Promise((resolve, reject) => {
doFetch(url)
.then(html => {
const getchuProcessedJSON = Object()
const infoBox = html.querySelector("#soft_table")
getchuProcessedJSON.title = ["제목", strTrim(infoBox.querySelector("#soft-title").innerText)]
const infoTable = infoBox.querySelectorAll("tbody > tr:nth-child(2) > th > table > tbody > tr")
doFetch(`http://www.getchu.com/brandnew${infoBox.querySelector(".highslide").href.split("brandnew")[1]}`, {
method: "GET",
responseType: "blob",
headers: {
referer: url
}
})
.then(async blob => {
var tmpPromise = UploadImageFile(blob)
.then(imgUrl => {
getchuProcessedJSON.image = ["이미지", `<img src='${imgUrl}' alt="이미지">`]
infoTable.forEach(tr => {
let infoTd = tr.querySelectorAll("td")
if (infoTd.length > 1) {
let td1 = strTrim(infoTd[0].innerText)
let td2 = strTrim(infoTd[1].innerText)
switch (td1) {
case "ブランド:":
getchuProcessedJSON.brand = ["브랜드", td2];
break;
case "定価:":
//getchuProcessedJSON.price = ["정가", td2];
break;
case "発売日:":
getchuProcessedJSON.o_date = ["발매일", td2];
break;
case "メディア:":
getchuProcessedJSON.media = ["미디어", td2];
break;
case "ジャンル:":
getchuProcessedJSON.genre = ["장르", dictionary(td2)];
break;
case "原画:":
getchuProcessedJSON.c_artist = ["원화", td2];
break;
case "シナリオ:":
getchuProcessedJSON.scenario = ["시나리오", td2];
break;
case "時間:":
getchuProcessedJSON.playtime = ["시간", td2];
break;
case "アーティスト:":
getchuProcessedJSON.artist = ["아티스트", td2];
break;
case "商品同梱特典:":
getchuProcessedJSON.special = ["상품 동봉 특전", td2];
break;
default:
break;
}
}
})
getchuProcessedJSON.url = ["출처", `<a href='${url}'>보러가기</a>`]
resolve(makeTable(getchuProcessedJSON, getTable))
})
})
})
})
}
// getchu 정보 채우기
function getchuAuto(code = null) {
let result = code || window.prompt("getchu 주소를 입력하세요");
if (result) {
if (!code) {
getchuInfo(result)
} else {
return result
}
}
}
// fanza 파싱
function fanzaInfo(url, getTable = false) {
return new Promise((resolve, reject) => {
doFetch(url)
.then(html => {
const fanzaProcessedJSON = Object()
if (url.includes("doujin")) {
const infoBox = html.querySelector("div.l-areaMainColumn")
doFetch(infoBox.querySelector("#fn-slides > li:nth-child(1) > a").href, {
method: 'GET',
responseType: 'blob'
})
.then(blob => {
UploadImageFile(blob)
.then(imgsrc => {
fanzaProcessedJSON.title = ["제목", strTrim(infoBox.querySelector("h1.productTitle__txt").innerText)]
fanzaProcessedJSON.image = ["이미지", `<img src='${imgsrc}' alt="이미지">`]
infoBox.querySelectorAll(".productAttribute-listItem > span").forEach(item => {
if (item.className.includes("productGenre")) {
let td1 = "분류"
let td2 = strTrim(item.innerText)
switch (td2) {
case "ボイス":
fanzaProcessedJSON.type = [td1, "보이스"];
break;
case "コミック":
fanzaProcessedJSON.type = [td1, "만화"];
break;
case "CG":
fanzaProcessedJSON.type = [td1, "CG"];
break;
case "ゲーム":
fanzaProcessedJSON.type = [td1, "게임"];
break;
case "コスプレ動画":
fanzaProcessedJSON.type = [td1, "코스프레 동영상"]
alert("실사 자료는 아카라이브 및 심야식당 규정에 어긋납니다.")
throw new Error("규정 위반 자료")
}
}
})
infoBox.querySelectorAll(".productInformation__item").forEach(child => {
let td1 = child.querySelector(".informationList__ttl").innerText
let td2
let tdd = child.querySelector(".informationList__txt")
if (tdd) {
td2 = tdd.innerText
}
switch (td1) {
case "シリーズ":
fanzaProcessedJSON.series = ["시리즈", td2]
break;
case "題材":
fanzaProcessedJSON.sanction = ["제재", dictionary(td2)]
break;
case "音声本数":
fanzaProcessedJSON.voice_count = ["음성 개수", td2]
break;
case "動画本数":
fanzaProcessedJSON.video_count = ["영상 개수", td2]
break;
case "ジャンル":
let tagList = []
infoBox.querySelectorAll(".genreTag__item").forEach(tag => {
tagList.push(dictionary(tag.innerText))
})
fanzaProcessedJSON.tags = ["태그", arr2str(tagList)]
break;
case "ファイル容量":
fanzaProcessedJSON.size = ["용량", td2]
break;
}
})
let sample = infoBox.querySelectorAll("div.sampleButton__item")
if (sample.length) {
const sampleUrlList = []
sample.forEach(tag => {
if (tag.querySelector(".sampleButton__btn") && tag.querySelector(".sampleButton__txt")) {
sampleUrlList.push(`<a href='${tag.querySelector(".sampleButton__btn").href}'>${tag.querySelector(".sampleButton__txt").innerText}</a>`)
}
})
fanzaProcessedJSON.sample = ["샘플", arr2str(sampleUrlList)]
}
fanzaProcessedJSON.url = ["출처", `<a href='${url}'>보러가기</a>`]
resolve(makeTable(fanzaProcessedJSON, getTable))
})
})
}
if (url.includes("dlsoft")) {
const infoBox = html.querySelector("#mu > div.page-detail")
doFetch(infoBox.querySelector("div.slider-area > ul > li:nth-child(1) > img").src, {
method: 'GET',
responseType: 'blob'
})
.then(blob => {
UploadImageFile(blob)
.then(imgsrc => {
fanzaProcessedJSON.title = ["제목", strTrim(infoBox.querySelector("#title").innerText)]
fanzaProcessedJSON.image = ["이미지", `<img src='${imgsrc}' alt="이미지">`]
if (infoBox.querySelector("tr.brand > td.content")) {
fanzaProcessedJSON.brand = ["브랜드", infoBox.querySelector("tr.brand > td.content").innerText]
}
infoBox.querySelector("div.container02").querySelectorAll("tr").forEach(tr => {
let rtd1 = tr.querySelector("td.type-left")
let rtd2 = tr.querySelector("td.type-right")
if (rtd1 && rtd2) {
let td1 = strTrim(rtd1.innerText)
let td2 = strTrim(rtd2.innerText)
switch (td1) {
case "シリーズ":
fanzaProcessedJSON.sanction = ["시리즈", td2]
break;
case "ゲームジャンル":
fanzaProcessedJSON.genre = ["게임 장르", dictionary(td2)]
break;
case "ボイス":
switch (td2) {
case "あり":
case "有り":
fanzaProcessedJSON.voice = ["음성", "있음"]
break;
case "なし":
case "無し":
fanzaProcessedJSON.voice = ["음성", "있음"]
break;
default:
fanzaProcessedJSON.voice = ["음성", td2]
break;
}
break;
case "原画":
fanzaProcessedJSON.c_artist = ["원화", td2]
break;
case "シナリオ":
fanzaProcessedJSON.scenario = ["시나리오", td2]
break;
case "ジャンル":
let tagList = []
tr.querySelectorAll("li").forEach(tag => {
tagList.push(dictionary(tag.innerText))
})
fanzaProcessedJSON.tags = ["태그", arr2str(tagList)]
break;
}
}
})
let sampleBx = infoBox.querySelector(".bx-detail-dlsample")
if (sampleBx) {
let sample = sampleBx.querySelectorAll("li")
const sampleUrlList = []
sample.forEach(tag => {
if (tag.querySelector("a") && tag.querySelector(".download-txt")) {
sampleUrlList.push(`<a href='${tag.querySelector("a").href}'>${tag.querySelector(".download-txt").innerText}</a>`)
}
})
fanzaProcessedJSON.sample = ["샘플", arr2str(sampleUrlList)]
}
fanzaProcessedJSON.url = ["출처", `<a href='${url}'>보러가기</a>`]
resolve(makeTable(fanzaProcessedJSON, getTable))
})
})
}
if (url.includes("book.")) {
const infoBox = html.querySelector("div.m-boxDetailProduct")
doFetch(infoBox.querySelector("span.m-boxDetailProduct__pack__item > a").href, {
method: 'GET',
responseType: 'blob'
})
.then(blob => {
UploadImageFile(blob)
.then(imgsrc => {
fanzaProcessedJSON.title = ["제목", strTrim(infoBox.querySelector("#title").innerText)]
fanzaProcessedJSON.image = ["이미지", `<img src='${imgsrc}' alt="이미지">`]
let index = 0
for (let list of infoBox.querySelectorAll(".m-boxDetailProductInfoMainList__description__list")) {
let tmpList = []
list.querySelectorAll("li").forEach(tag => {
tmpList.push(strTrim(tag.innerText))
})
if (index === 0) {
fanzaProcessedJSON.artist = ["작가", arr2str(tmpList)]
}
if (index === 1) {
fanzaProcessedJSON.series = ["시리즈", arr2str(tmpList)]
}
index += 1
}
fanzaProcessedJSON.summary = ["줄거리", infoBox.querySelector(".m-boxDetailProduct__info__story").innerText]
fanzaProcessedJSON.url = ["출처", `<a href='${url}'>보러가기</a>`]
resolve(makeTable(fanzaProcessedJSON, getTable))
})
})
}
if (url.includes("anime")) {
const infoBox = html.querySelector("div.page-detail")
doFetch(infoBox.querySelector("#sample-video > a").href, {
method: 'GET',
responseType: 'blob'
})
.then(blob => {
UploadImageFile(blob)
.then(imgsrc => {
fanzaProcessedJSON.title = ["제목", strTrim(infoBox.querySelector("#title").innerText)]
fanzaProcessedJSON.image = ["이미지", `<img src='${imgsrc}' alt="이미지">`]
infoBox.querySelector("table.mg-b20").querySelectorAll("tr").forEach(tag => {
let rtd1 = tag.querySelector("td:nth-child(1)")
let rtd2 = tag.querySelector("td:nth-child(2)")
if (rtd1 && rtd2) {
let td1 = strTrim(rtd1.innerText)
let td2 = strTrim(rtd2.innerText)
switch (td1) {
case "収録時間:":
fanzaProcessedJSON.playtime = ["러닝타임", td2]
break;
case "シリーズ:":
fanzaProcessedJSON.series = ["시리즈", td2]
break;
case "メーカー:":
fanzaProcessedJSON.maker = ["제조사", td2]
break;
case "レーベル:":
fanzaProcessedJSON.label = ["레이블", td2]
break;
case "ジャンル:":
fanzaProcessedJSON.genre = ["장르", dictionary(td2)]
break;
default:
break;
}
}
})
const sampleVideo = infoBox.querySelector("#detail-sample-movie > div > a.d-btn")
if (sampleVideo) {
sampleVideo.getAttribute("onclick").match(/\/digital.*\//)
fanzaProcessedJSON.sample = ["샘플", `<a href='https://www.dmm.co.jp${sampleVideo}'>샘플 영상 보기</a>`]
}
fanzaProcessedJSON.url = ["출처", `<a href='${url}'>보러가기</a>`]
resolve(makeTable(fanzaProcessedJSON, getTable))
})
})
}
})
})
}
// fanza 정보 채우기
function fanzaAuto(code = null) {
let result = code || window.prompt("fanza 주소를 입력하세요");
if (result) {
if (!code) {
fanzaInfo(result)
} else {
return result
}
}
}
// pixiv 파싱 (미완성)
function pixivInfo(url) {
doFetch(url)
.then(html => {
console.log(html)
})
}
// blob을 이미지 객체로
const blobToImage = (blob) => {
return new Promise(resolve => {
const url = URL.createObjectURL(blob)
let img = new Image()
img.onload = () => {
URL.revokeObjectURL(url)
resolve(img)
}
img.src = url
})
}
// 미방짤 제너레이터
function deviantTaste(optList, custom) {
return new Promise((resolve, reject) => {
doFetch("https://p-ac.namu.la/20210402/312ddf2b6cd9d18672d8818b8545c4c6dde0e92b80da025f9b52eecae181455f.png?type=orig", {
method: "GET",
responseType: "blob"
})
.then(blob => {
blobToImage(blob)
.then(image => {
const canvas = document.createElement("canvas")
canvas.setAttribute("id", "sm-translation-image-process-canvas")
canvas.setAttribute("width", "1200")
canvas.setAttribute("height", "800")
const ctx = canvas.getContext("2d")
ctx.drawImage(image, 0, 0)
ctx.lineWidth = 5;
ctx.strokeStyle = 'red';
optList.forEach(taste => {
deviantTasteCanvasRect(ctx, taste)
})
if (custom) {
ctx.font = "28pt 나눔고딕 extraBold";
ctx.fillStyle = "white";
ctx.fillText(custom, 130, 708);
ctx.lineWidth = 5;
ctx.strokeStyle = 'red';
ctx.strokeRect(125, 670, 10 + ctx.measureText(custom).width, 53);
ctx.lineWidth = 2.5;
ctx.rotate(45 * Math.PI / 180);
ctx.strokeStyle = 'white';
ctx.strokeRect(555, 412, 16.5, 16.5);
}
canvas.toBlob(function(blob) {
UploadImageFile(blob)
.then(url => {
resolve(`<img src="${url}" alt="미방짤 : ${arr2str(optList)}">`)
})
}, "image/png")
})
})
})
}
// 미방짤 모달
function deviantTasteMenu() {
let smtModalStyle = document.createElement("style")
smtModalStyle.append(`
.smtmodal {
display: none;
position: fixed;
z-index: 100;
padding-top: 100px;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.4);
}
.smtmodal-content {
position: relative;
background-color: #fefefe;
margin: auto;
padding: 0;
border: 1px solid #888;
width: 40%;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
-webkit-animation-name: animatetop;
-webkit-animation-duration: 0.4s;
animation-name: animatetop;
animation-duration: 0.4s
}
@-webkit-keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
@keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
.smtclose {
color: white;
float: right;
font-size: 28px;
font-weight: bold;
}
.smtclose:hover,
.smtclose:focus {
color: white;
text-decoration: none;
cursor: pointer;
}
.smtmodal-header {
padding: 0 20px 45px 20px;
background-color: lightgray;
color: white;
}
.smtmodal-body {
padding: 2px 30px;
}
.smtmodal-footer {
padding: 0 0 45px 0;
background-color: lightgray;
color: white;
}
.sexy-color-fresh {
background: #0fb8ad;
background: -webkit-linear-gradient(135deg, #0fb8ad 0%, #1fc8db 51%, #2cb5e8 75%);
background: -o-linear-gradient(135deg, #0fb8ad 0%, #1fc8db 51%, #2cb5e8 75%);
background: -moz-linear-gradient(135deg, #0fb8ad 0%, #1fc8db 51%, #2cb5e8 75%);
background: linear-gradientlinear-gradient(135deg, #0fb8ad 0%, #1fc8db 51%, #2cb5e8 75%);
background: linear-gradient(135deg, #0fb8ad 0%, #1fc8db 51%, #2cb5e8 75%);
}
`)
let smtModal = document.createElement("div")
smtModal.setAttribute("id", "sm-taste-modal")
smtModal.setAttribute("class", "smtmodal")
smtModal.innerHTML = `
<div class="smtmodal-content">
<div class="smtmodal-header sexy-color-fresh">
<span id ="sm-taste-modal-close" class="smtclose">×</span>
</div>
<div id="sm-taste-modal-body" class="smtmodal-body clearfix">
<h2 style="margin:30px 0">미방짤 메이커</h2>
</div>
<div class="smtmodal-footer sexy-color-fresh">
</div>
</div>`
document.head.appendChild(smtModalStyle)
document.body.appendChild(smtModal)
let modal = document.getElementById("sm-taste-modal");
let span = document.getElementById("sm-taste-modal-close")
let body = document.getElementById("sm-taste-modal-body");
let checkList = [
["게이", "보추", "후타"],
["청아", "중노년여성"],
["스캇", "방뇨"],
["수간", "충간", "이종간"],
["고어", "료나", "보어(식인)"],
["보태", "임신", "출산"]
]
const customTaste = document.createElement("input")
customTaste.setAttribute("id", "sm-taste-modal-cutom-input")
customTaste.setAttribute("class", "form-control form-control-sm float-right")
const customTasteLabel = document.createElement("p")
customTasteLabel.innerText = "또다른 취향 태그"
const customTasteP = document.createElement("p")
customTasteP.setAttribute("class", "clearfix")
customTasteP.setAttribute("style", "margin-bottom:20px")
customTasteP.appendChild(customTaste)
const confirmBtn = document.createElement("div")
confirmBtn.setAttribute("id", "sm-taste-modal-confirm-btn")
confirmBtn.setAttribute("class", "btn btn-arca float-right")
confirmBtn.setAttribute("style", "margin-bottom:20px")
confirmBtn.innerText = "미방짤 만들기"
confirmBtn.onclick = function(e) {
e.target.innerText = "파일 처리중..."
const optionList = []
document.querySelectorAll(".sm-taste-modal-taste-checkbox").forEach(chkb => {
if (chkb.checked) {
optionList.push(chkb.value)
}
})
deviantTaste(optionList, customTaste.value)
.then(img => {
let editorBox = document.querySelector('.write-body .fr-element')
editorBox.innerHTML = img + editorBox.innerHTML
modal.style.display = "none"
})
}
easyCheckbox(body, checkList, "sm-taste-modal-taste-checkbox")
body.appendChild(customTasteLabel)
body.appendChild(customTasteP)
body.appendChild(confirmBtn)
span.onclick = function() {
modal.style.display = "none";
}
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
}
modal.style.display = "block";
}
// 미방짤 표시 좌표
function deviantTasteCanvasRect(ctx, opt) {
switch (opt) {
case "게이":
ctx.strokeRect(125, 355, 85, 50);
break;
case "보추":
ctx.strokeRect(225, 355, 85, 50);
break;
case "후타":
ctx.strokeRect(325, 355, 85, 50);
break;
case "청아":
ctx.strokeRect(125, 407, 85, 50);
break;
case "중노년여성":
ctx.strokeRect(225, 407, 332, 50);
break;
case "스캇":
ctx.strokeRect(125, 460, 85, 50);
break;
case "방뇨":
ctx.strokeRect(225, 460, 85, 50);
break;
case "수간":
ctx.strokeRect(125, 513, 85, 50);
break;
case "충간":
ctx.strokeRect(225, 513, 85, 50);
break;
case "이종간":
ctx.strokeRect(325, 513, 120, 50);
break;
case "고어":
ctx.strokeRect(125, 566, 85, 50);
break;
case "료나":
ctx.strokeRect(225, 566, 85, 50);
break;
case "보어(식인)":
ctx.strokeRect(325, 566, 183, 50);
break;
case "보태":
ctx.strokeRect(125, 619, 85, 50);
break;
case "임신":
ctx.strokeRect(225, 619, 85, 50);
break;
case "출산":
ctx.strokeRect(325, 619, 85, 50);
break;
}
}
// 미방짤 init
function deviantTasteMenuInit() {
let modal = document.getElementById("sm-taste-modal");
if (modal === null && (location.href.includes("/write") || location.href.includes("/edit"))) {
deviantTasteMenu()
} else {
if (modal !== null) {
modal.style.display = "block"
}
}
}
// 파일 -> 어레이버퍼
function fileToArrayBuffer(myFile) {
return new Promise((resolve, reject) => {
var reader = new FileReader();
reader.readAsArrayBuffer(myFile);
reader.onloadend = function(evt) {
if (evt.target.readyState == FileReader.DONE) {
var arrayBuffer = evt.target.result
resolve(arrayBuffer)
}
reject(new Error("ArrayBuffer 변환 실패"));
}
})
}
// 어레이버퍼 덧셈 -> Uint8Array
function appendBufferToArray(buffer1, buffer2) {
var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
tmp.set(new Uint8Array(buffer1), 0);
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
return tmp;
};
// 이미지 첨부파일 제너레이터
function imagezipBind(image, file) {
return new Promise((resolve, reject) => {
fileToArrayBuffer(image)
.then(imageBuffer => {
fileToArrayBuffer(file)
.then(fileBuffer => {
let blob = new Blob([appendBufferToArray(imageBuffer, fileBuffer)])
UploadImageFile(blob, null, true)
.then(url => {
resolve(`<img src="${url}" alt="이미지+파일">`)
})
}, "image/jpeg")
})
})
}
// 이미지 jpg 컨버터
function convert() {
let c = document.createElement("canvas")
let ctx = c.getContext("2d");
c.width = this.width;
c.height = this.height;
ctx.drawImage(this, 0, 0);
c.toBlob(function(blob) {
resolve(blob)
}, "image/jpeg");
}
// 이미지 첨부파일 모달
function imagezipMaker() {
let smiModalStyle = document.createElement("style")
smiModalStyle.append(`
.smimodal {
display: none;
position: fixed;
z-index: 100;
padding-top: 100px;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgb(0,0,0);
background-color: rgba(0,0,0,0.4);
}
.smimodal-content {
position: relative;
background-color: #fefefe;
margin: auto;
padding: 0;
border: 1px solid #888;
width: 40%;
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19);
-webkit-animation-name: animatetop;
-webkit-animation-duration: 0.4s;
animation-name: animatetop;
animation-duration: 0.4s
}
@-webkit-keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
@keyframes animatetop {
from {top:-300px; opacity:0}
to {top:0; opacity:1}
}
.smiclose {
color: white;
float: right;
font-size: 28px;
font-weight: bold;
}
.smiclose:hover,
.smiclose:focus {
color: white;
text-decoration: none;
cursor: pointer;
}
.smimodal-header {
padding: 0 20px 45px 20px;
background-color: lightgray;
color: white;
}
.smimodal-body {
padding: 2px 30px;
}
.smimodal-footer {
padding: 0 0 45px 0;
background-color: lightgray;
color: white;
}
.sexy-color-gold {
background: linear-gradient(to right, gold 0%, goldenrod 100%);
}
`)
let smiModal = document.createElement("div")
smiModal.setAttribute("id", "sm-imagezip-modal")
smiModal.setAttribute("class", "smimodal")
smiModal.innerHTML = `
<div class="smimodal-content">
<div class="smimodal-header sexy-color-gold">
<span id ="sm-imagezip-modal-close" class="smiclose">×</span>
</div>
<div id="sm-imagezip-modal-body" class="smimodal-body clearfix">
<h2 style="margin:30px 0">이미지+파일 바인더</h2>
</div>
<div class="smimodal-footer sexy-color-gold">
</div>
</div>`
document.head.appendChild(smiModalStyle)
document.body.appendChild(smiModal)
let modal = document.getElementById("sm-imagezip-modal");
let span = document.getElementById("sm-imagezip-modal-close")
let body = document.getElementById("sm-imagezip-modal-body");
const imageFileH5 = document.createElement("h5")
imageFileH5.setAttribute("style", "margin-bottom:20px")
imageFileH5.innerText = "이미지 선택"
const imageFileInput = document.createElement("input")
imageFileInput.setAttribute("id", "sm-imagezip-modal-file-input")
imageFileInput.setAttribute("type", "file")
imageFileInput.setAttribute("style", "margin-bottom:20px")
const imageUrl = document.createElement("input")
imageUrl.setAttribute("id", "sm-imagezip-modal-url-input")
imageUrl.setAttribute("class", "form-control form-control-sm float-right")
const imageUrlLabel = document.createElement("p")
imageUrlLabel.innerText = "또는 링크로 이미지 가져오기"
const imageUrlP = document.createElement("p")
imageUrlP.setAttribute("class", "clearfix")
imageUrlP.setAttribute("style", "margin-bottom:20px")
imageUrlP.appendChild(imageUrl)
const fileH5 = document.createElement("h5")
fileH5.innerText = "파일 선택"
fileH5.setAttribute("style", "margin-bottom:20px")
const fileInput = document.createElement("input")
fileInput.setAttribute("id", "sm-imagezip-modal-file-input")
fileInput.setAttribute("type", "file")
fileInput.setAttribute("style", "margin-bottom:20px")
const confirmBtn = document.createElement("div")
confirmBtn.setAttribute("id", "sm-imagezip-modal-confirm-btn")
confirmBtn.setAttribute("class", "btn btn-arca float-right")
confirmBtn.setAttribute("style", "margin:60px 0 20px 0")
confirmBtn.innerText = "이미지+파일 합치기"
confirmBtn.onclick = function(e) {
new Promise((resolve, reject) => {
e.target.innerText = "파일 처리중..."
if (imageUrl.value) {
doFetch(imageUrl.value, {
method: "GET",
responseType: "blob"
})
.then(blob => {
console.log(blob)
resolve(imagezipBind(blob, fileInput.files[0]))
})
} else {
resolve(imagezipBind(imageFileInput.files[0], fileInput.files[0]))
}
})
.then(img => {
let editorBox = document.querySelector('.write-body .fr-element')
editorBox.innerHTML = img + editorBox.innerHTML
modal.style.display = "none"
})
}
body.appendChild(imageFileH5)
body.appendChild(imageFileInput)
body.appendChild(imageUrlLabel)
body.appendChild(imageUrlP)
body.appendChild(fileH5)
body.appendChild(fileInput)
body.appendChild(confirmBtn)
span.onclick = function() {
modal.style.display = "none";
}
window.onclick = function(event) {
if (event.target == modal) {
modal.style.display = "none";
}
}
modal.style.display = "block";
}
// 이미지 첨부파일 init
function imagezipMakerInit() {
let modal = document.getElementById("sm-imagezip-modal");
if (modal === null && (location.href.includes("/write") || location.href.includes("/edit"))) {
imagezipMaker()
} else {
if (modal !== null) {
modal.style.display = "block"
}
}
}
// 글 댓글 수 찾기 함수
function userInfoParse(url) {
return new Promise((resolve, reject) => {
doFetch(url, {
method: 'GET',
responseType: 'document'
}, true)
.then(html => {
const infoBox = html.querySelector(".card-block")
let filtered = Array.from(infoBox.children).filter(function(value, index, arr) {
if (value.localName === "div") {
return value
}
})
const point = filtered.findIndex(function(e) {
if (e.className === "clearfix") {
return true
}
})
const articleNum = point
const replyNum = filtered.length - point - 1
resolve(String(articleNum) + " / " + String(replyNum))
})
.catch(e => {
resolve("삭제됨")
})
})
}
// 글 댓글 수 표시 함수
function userInfoView() {
const smtTooltipStyle = document.createElement("style")
smtTooltipStyle.append(`
.smttooltip {
position: relative;
display: inline-block;
}
.smttooltip .smttooltiptext {
visibility: hidden;
width: 60px;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 5px 0;
position: absolute;
z-index: 50;
bottom: 125%;
left: 50%;
margin-left: -30px;
opacity: 0;
transition: opacity 0.3s;
}
.smttooltip .smttooltiptext::after {
content: "";
position: absolute;
top: 100%;
left: 50%;
margin-left: -5px;
border-width: 5px;
border-style: solid;
border-color: #555 transparent transparent transparent;
}
.smttooltip:hover .smttooltiptext {
visibility: visible;
opacity: 1;
}`)
document.head.appendChild(smtTooltipStyle)
document.querySelectorAll("div.list-area").forEach(tag => {
tag.setAttribute("style", "overflow:visible")
})
document.querySelectorAll("div.info-row").forEach(tag => {
tag.setAttribute("style", "overflow:visible")
})
document.querySelector(".article-wrapper").querySelectorAll("span.user-info").forEach(tag => {
const smtTooltip = document.createElement("span")
smtTooltip.setAttribute("class", "smttooltiptext")
smtTooltip.innerText = " "
tag.querySelector("a").classList.add("smttooltip")
tag.querySelector("a").appendChild(smtTooltip)
tag.querySelector("a").onmouseover = function(e) {
if (e.target.href) {
userInfoParse(e.target.href)
.then(str => {
smtTooltip.innerText = str
})
}
}
})
}
// 글 댓글 수 표시 init
function userInfoViewInit() {
if (location.href.match(/\/b\/.*\/\d+/) && !location.href.includes("/edit") && window.innerWidth > 580) {
userInfoView()
}
}
// 클래스 변경 감지
function addClassNameListener(element, callback) {
var lastClassName = element.className;
window.setInterval(function() {
var className = element.className;
if (className !== lastClassName) {
callback();
lastClassName = className;
}
}, 10);
}
// 아카콘 링크하기
function arcaconLink() {
const arcaconSrc = new Set()
let arcaconId
document.querySelector(".emoticons").querySelectorAll("img").forEach(tag => {
arcaconSrc.add(tag.src)
if (tag.getAttribute("data-emoticonid")) {
arcaconId = tag.getAttribute("data-emoticonid")
}
})
const editorBox = document.querySelector("#article_write_form > div.write-body > div > div.fr-wrapper > div")
editorBox.querySelectorAll("img").forEach(tag => {
if (arcaconSrc.has(tag.src) && arcaconId !== "0" && !tag.closest("a")) {
const cloneImg = tag.cloneNode(true)
const aTag = document.createElement("a")
aTag.appendChild(cloneImg)
aTag.href = `https://arca.live/e/${arcaconId}`
tag.replaceWith(aTag)
}
})
}
// 아카콘 링크된 주소 글에서 바꾸기
async function arcaconLinkInArticle() {
await sleep(1000)
document.querySelector(".article-content").childNodes.forEach(tag => {
const ta = tag.querySelectorAll("a").forEach(pta => {
if (pta) {
const taa = pta.querySelector("a")
if (taa) {
taa.href = pta.href
}
}
})
})
}
// 아카콘 링크하기 init
async function arcaconLinkInit() {
if (location.href.includes("/write") || location.href.includes("/edit")) {
let arcaconButton = document.querySelector(".btn-namlacon")
while (arcaconButton === null) {
await sleep(10)
arcaconButton = document.querySelector(".btn-namlacon")
}
arcaconButton.onclick = function(e) {
let arcaconPopup = document.querySelector(".write-body").querySelector(".fr-popup")
addClassNameListener(arcaconPopup, arcaconLink)
}
} else if (location.href.match(/b\/.*?\/\d+/)) {
arcaconLinkInArticle()
}
}
// 클립보드 base64
async function clipboardPasteToBase64() {
let editorBox = document.querySelector('.write-body .fr-element')
while (editorBox === null) {
await sleep(10)
editorBox = document.querySelector('.write-body .fr-element')
}
editorBox.addEventListener('paste', (event) => {
const expression = /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)?/gi;
const regex = new RegExp(expression);
let paste = (event.clipboardData || window.clipboardData).getData('text');
let matched = false
const matchUrl = [
"mega.nz",
"drive.google.com",
"photos.google.com",
"ecchi.iwara.tv"
]
matchUrl.forEach(url => {
if(paste.includes(url)) {
matched = true
}
})
if (matched && paste.match(regex)) {
paste = btoa(paste)
const selection = window.getSelection();
if (!selection.rangeCount) return false;
selection.deleteFromDocument();
selection.getRangeAt(0).insertNode(document.createTextNode(paste));
event.preventDefault();
}
});
}
// 클립보드 base64 init
function clipboardPasteToBase64Init() {
if (location.href.includes("/write") || location.href.includes("/edit")) {
clipboardPasteToBase64()
}
}
// 이미지 다른 확장자로 저장 컨텍스트 메뉴
function imageSaveContextMenu() {
const contextmenuStyle = document.createElement("style")
contextmenuStyle.append(`
.smc-menu {
display: none;
position: absolute;
width: 200px;
margin: 0;
padding: 0;
background: #FFFFFF;
list-style: none;
border: 1px solid gray
box-shadow:
0 15px 35px rgba(50,50,90,0.2),
0 5px 15px rgba(0,0,0,0.14);
overflow: hidden;
z-index: 1000;
}
.smc-menu-option {
border-left: 3px solid transparent;
transition: ease .2s;
display: block;
padding: 10px;
color: #737373;
text-decoration: none;
transition: ease .2s;
}
.smc-menu-option:hover {
background: #00a495c4;
border-left: 3px solid #00a495db;
color: #FFFFFF;
cursor:pointer
}`)
document.head.appendChild(contextmenuStyle)
const contextmenuUl = document.createElement("ul")
contextmenuUl.setAttribute("class", "smc-menu")
const contextmenuOptionList = [
"커스텀",
"rar",
"7z",
"zip"
]
contextmenuOptionList.forEach(opt => {
const contextmenuOption = document.createElement("li")
contextmenuOption.innerText = `${opt} 확장자로 저장`
contextmenuOption.setAttribute("class", "smc-menu-option")
contextmenuOption.onclick = function (e) {
if (e.target.innerText.includes("커스텀")) {
opt = prompt("확장자를 입력하세요");
}
const url = `${imageSaveContextMenuTargetImage.src}?type=orig`
doFetch(url, {method:"GET", responseType:"blob"})
.then(blob => {
const blobUrl = URL.createObjectURL(blob)
const tempLink = document.createElement("a")
tempLink.href = blobUrl;
tempLink.download = `${url.split("/")[url.split("/").length-1].split(".")[0]}.${opt}`
tempLink.click()
URL.revokeObjectURL(blobUrl);
e.target.innerText = "다운로드 완료"
})
}
contextmenuUl.appendChild(contextmenuOption)
})
document.body.appendChild(contextmenuUl)
imageSaveContextMenuOpen(contextmenuUl)
}
let imageSaveContextMenuTargetImage
// 이미지 다른 확장자로 저장 컨텍스트 메뉴 열기
function imageSaveContextMenuOpen(menu) {
let menuVisible = false;
const toggleMenu = command => {
menu.style.display = command === "show" ? "block" : "none";
menuVisible = !menuVisible;
};
const setPosition = ({
top,
left
}) => {
menu.style.left = `${left}px`;
menu.style.top = `${top}px`;
toggleMenu("show");
};
document.querySelector(".article-content").querySelectorAll("img").forEach(tag => {
document.addEventListener("click", e => {
if (menuVisible && e.target.className !== "smc-menu-option") {
toggleMenu("hide")
} else if (e.target.className === "smc-menu-option") {
e.target.innerText = "다운로드 중..."
}
});
tag.addEventListener("contextmenu", e => {
e.preventDefault();
const origin = {
left: e.pageX - 200,
top: e.pageY - 170
};
imageSaveContextMenuTargetImage = e.target
setPosition(origin);
return false;
});
})
}
// 이미지 다른 확장자로 저장 컨텍스트 메뉴 init
function imageSaveContextMenuInit() {
if (location.href.match(/\/b\/.*?\/\d+/)) {
imageSaveContextMenu()
}
}
// 메뉴 커맨드
function registerMenuCommand() {
GM_registerMenuCommand("< 미방짤 만들기 >", deviantTasteMenuInit, "T");
GM_registerMenuCommand("< 이미지+파일 합치기 >", imagezipMakerInit, "Z");
GM_registerMenuCommand("DLsite 정보 채우기", dlsiteAuto, "D");
GM_registerMenuCommand("hitomi 정보 채우기", hitomiAuto, "H");
GM_registerMenuCommand("hitomi 전체 가져오기", hitomiFetch, "I");
GM_registerMenuCommand("e-hentai 정보 채우기", ehAuto, "E");
GM_registerMenuCommand("getchu 정보 채우기", getchuAuto, "G");
GM_registerMenuCommand("fanza 정보 채우기", fanzaAuto, "F");
};
// 기다리기 함수
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 사전 from https://arca.live/b/smpeople/30169768, 일부 수정 및 추가함
function dictionary(str) {
let trList = []
str.split(/[\s\t\r\n\v\f\/・]/).forEach(tok => {
tok = strTrim(tok)
switch (tok) {
case "萌え":
trList.push("모에")
break;
case "燃え":
trList.push("열혈")
break;
case "感動":
trList.push("감동")
break;
case "癒し":
trList.push("힐링")
break;
case "鬱":
trList.push("우울")
break;
case "淡白":
case "あっさり":
trList.push("담백")
break;
case "オールハッピー":
trList.push("올 해피")
break;
case "着衣":
trList.push("착의")
break;
case "チラリズム":
trList.push("치라리즘")
break;
case "フェチ":
trList.push("페티즘")
break;
case "女性視点":
trList.push("여성시점")
break;
case "女主人公":
trList.push("여주인공")
break;
case "男主人公":
trList.push("남주인공")
break;
case "逆転無し":
trList.push("역전 없음")
break;
case "マニアック":
case "変態":
trList.push("마니악/변태")
break;
case "おさわり":
trList.push("만지기")
break;
case "きせかえ":
trList.push("옷갈아입히기")
break;
case "脚":
trList.push("다리")
break;
case "お尻":
case "ヒップ":
trList.push("엉덩이")
break;
case "おっぱい":
trList.push("가슴")
break;
case "淫語":
trList.push("음어")
break;
case "汁":
case "液大量":
trList.push("체액 대량")
break;
case "連続絶頂":
trList.push("연속 절정")
break;
case "断面図":
trList.push("단면도")
break;
case "ドット":
case "ドット制作":
trList.push("도트")
break;
case "アニメ":
trList.push("애니메이션")
break;
case "総集編":
trList.push("총집편")
break;
case "バイノーラル":
case "ダミヘ":
trList.push("바이노럴/더미헤드폰")
break;
case "催眠音声":
trList.push("최면음성")
break;
case "アンソロジー":
trList.push("앤솔로지")
break;
case "技術書":
trList.push("기술서")
break;
case "ツクール":
trList.push("알만툴")
break;
case "3D作品":
trList.push("3D 작품")
break;
case "東方Project":
trList.push("동방Project")
break;
case "ピアス":
trList.push("피어스")
break;
case "装飾品":
trList.push("장식품")
break;
case "首輪":
trList.push("목줄")
break;
case "鎖":
trList.push("사슬")
break;
case "拘束具":
trList.push("구속구")
break;
case "ムチ":
trList.push("채찍")
break;
case "縄":
trList.push("밧줄")
break;
case "蝋燭":
trList.push("촛농")
break;
case "薬物":
trList.push("약물")
break;
case "ローション":
trList.push("로션")
break;
case "おむつ":
trList.push("기저귀")
break;
case "おもちゃ":
trList.push("장난감")
break;
case "道具":
trList.push("도구")
break;
case "異物":
trList.push("이물질")
break;
case "メガネ":
case "めがね":
trList.push("안경")
break;
case "靴下":
trList.push("양말")
break;
case "少女":
trList.push("소녀")
break;
case "ロリ":
trList.push("로리")
break;
case "ぷに":
trList.push("뿌니")
break;
case "少年":
trList.push("소년")
break;
case "ショタ":
trList.push("쇼타")
break;
case "年上":
trList.push("연상")
break;
case "妹":
trList.push("여동생")
break;
case "母親":
trList.push("모친")
break;
case "義妹":
trList.push("의여동생")
break;
case "娘":
trList.push("딸")
break;
case "義母":
trList.push("의어머니")
break;
case "実姉":
trList.push("친누나/친언니")
break;
case "義姉":
trList.push("의누나/의언니")
break;
case "おやじ":
trList.push("아버지")
break;
case "熟女":
trList.push("숙녀")
break;
case "人妻":
trList.push("유부녀")
break;
case "お姉さん":
trList.push("누나/언니")
break;
case "未亡人":
trList.push("미망인")
break;
case "既婚者":
trList.push("기혼자")
break;
case "幼なじみ":
trList.push("소꿉친구")
break;
case "双子":
trList.push("쌍둥이")
break;
case "姉妹":
trList.push("자매")
break;
case "保健医":
trList.push("보건의")
break;
case "女医":
trList.push("여의사")
break;
case "女教師":
trList.push("여교사")
break;
case "教師":
trList.push("교사")
break;
case "学生":
trList.push("학생")
break;
case "同級生":
trList.push("동급생")
break;
case "同僚":
trList.push("동료")
break;
case "先輩":
trList.push("선배")
break;
case "後輩":
trList.push("후배")
break;
case "お嬢様":
trList.push("아가씨")
break;
case "ギャル":
trList.push("갸루")
break;
case "ビッチ":
trList.push("빗치")
break;
case "天然":
trList.push("천연")
break;
case "主従":
trList.push("주종")
break;
case "主婦":
trList.push("주부")
break;
case "女王様":
trList.push("여왕님")
break;
case "お姫様":
trList.push("공주님")
break;
case "エルフ":
trList.push("엘프")
break;
case "妖精":
trList.push("요정")
break;
case "天使":
trList.push("천사")
break;
case "悪魔":
trList.push("악마")
break;
case "変身ヒロイン":
trList.push("변신 히로인")
break;
case "魔法少女":
trList.push("마법 소녀")
break;
case "魔法使い":
trList.push("마법사")
break;
case "魔女":
trList.push("마녀")
break;
case "男の娘":
trList.push("오토코노코")
break;
case "妖怪":
trList.push("요괴")
break;
case "擬人化":
trList.push("의인화")
break;
case "ヤンデレ":
trList.push("얀데레")
break;
case "人外娘":
trList.push("인외")
break;
case "モンスター娘":
trList.push("몬스터 소녀")
break;
case "ロボット":
trList.push("로봇")
break;
case "アンドロイド":
trList.push("안드로이드")
break;
case "芸能人":
trList.push("예능인")
break;
case "アイドル":
trList.push("아이돌")
break;
case "モデル":
trList.push("모델")
break;
case "警察":
trList.push("경찰")
break;
case "刑事":
trList.push("형사")
break;
case "ヤクザ":
case "裏社会":
trList.push("야쿠자")
break;
case "不良":
trList.push("불량")
break;
case "ヤンキー":
trList.push("일진")
break;
case "レスラー":
trList.push("레슬러")
break;
case "格闘家":
trList.push("격투가")
break;
case "幽霊":
trList.push("유령")
break;
case "ゾンビ":
trList.push("좀비")
break;
case "けもの":
trList.push("짐승")
break;
case "獣化":
trList.push("동물화")
break;
case "外国人":
trList.push("외국인")
break;
case "体育会系":
trList.push("체육")
break;
case "スポーツ選手":
trList.push("스포츠 선수")
break;
case "ニューハーフ":
trList.push("뉴하프")
break;
case "戦士":
trList.push("전사")
break;
case "くノ一":
trList.push("쿠노이치")
break;
case "サキュバス":
case "淫魔":
trList.push("서큐버스/음마")
break;
case "制服":
case "제복":
trList.push("교복")
break;
case "セーラー服":
trList.push("세일러복")
break;
case "体操着":
trList.push("체조복")
break;
case "水着":
trList.push("수영복")
break;
case "メイド":
trList.push("메이드")
break;
case "看護婦":
case "ナース":
trList.push("간호사")
break;
case "巫女":
trList.push("무녀")
break;
case "軍服":
trList.push("군복")
break;
case "下着":
trList.push("속옷")
break;
case "パンツ":
trList.push("팬티")
break;
case "ゴスロリ":
trList.push("고스로리")
break;
case "コスプレ":
trList.push("코스프레")
break;
case "ボンデージ":
case "ボンテージ":
trList.push("본디지")
break;
case "ブルマ":
trList.push("브루마")
break;
case "ミニスカ":
trList.push("미니 스커트")
break;
case "着物":
case "和服":
trList.push("기모노")
break;
case "エプロン":
trList.push("앞치마")
break;
case "ラバー":
trList.push("고무재질")
break;
case "レオタード":
trList.push("레오타드")
break;
case "シスター":
trList.push("수녀")
break;
case "ウェイトレス":
trList.push("웨이트리스")
break;
case "バニーガール":
case "버니걸":
trList.push("바니걸")
break;
case "スパッツ":
trList.push("스판츠")
break;
case "ニーソックス":
trList.push("니삭스")
break;
case "ストッキング":
trList.push("스타킹")
break;
case "スクール水着":
trList.push("학교 수영복")
break;
case "スーツ":
trList.push("정장")
break;
case "ガーター":
trList.push("가터")
break;
case "女装":
trList.push("여장")
break;
case "学校":
case "学園":
case "学園もの":
trList.push("학원")
break;
case "オフィス":
case "職場":
trList.push("오피스/직장")
break;
case "ラブコメ":
trList.push("러브 코메디")
break;
case "耳かき":
trList.push("귀청소")
break;
case "屋外":
trList.push("옥외")
break;
case "ギャグ":
trList.push("개그")
break;
case "ラブラブ":
case "あまあま":
trList.push("러브러브/달콤달콤")
break;
case "退廃":
case "背徳":
case "インモラル":
trList.push("퇴폐/배덕/인모럴")
break;
case "憑依":
trList.push("빙의")
break;
case "石化":
trList.push("석화")
break;
case "コメディ":
trList.push("코메디")
break;
case "日常":
case "生活":
trList.push("일상/생활")
break;
case "時間停止":
trList.push("시간 정지")
break;
case "ミリタリー":
trList.push("밀리터리")
break;
case "スポーツ":
trList.push("스포츠")
break;
case "格闘":
trList.push("격투")
break;
case "ほのぼの":
trList.push("푸근함")
break;
case "同棲":
trList.push("동거(애인끼리)")
break;
case "恋人同士":
trList.push("연인끼리")
break;
case "初体験":
case "첫체험":
trList.push("첫 체험")
break;
case "色仕掛け":
trList.push("미인계")
break;
case "女体化":
trList.push("여체화")
break;
case "性転換(TS)":
trList.push("성전환(TS)")
break;
case "浮気":
trList.push("바람")
break;
case "売春":
case "援交":
trList.push("매춘/원교")
break;
case "風俗":
case "ソープ":
trList.push("풍속/소프")
break;
case "シリアス":
trList.push("시리어스")
break;
case "ファンタジー":
trList.push("판타지")
break;
case "歴史":
case "時代物":
trList.push("역사/시대물")
break;
case "ホラー":
trList.push("호러")
break;
case "キャットファイト":
trList.push("캣 파이트")
break;
case "サスペンス":
trList.push("서스펜스")
break;
case "バイオレンス":
trList.push("바이올런스")
break;
case "ノンフィクション":
case "体験談":
trList.push("논픽션/체험담")
break;
case "オカルト":
trList.push("오컬트")
break;
case "歳の差":
trList.push("나이차")
break;
case "魔法":
trList.push("마법")
break;
case "同居":
trList.push("동거")
break;
case "純愛":
trList.push("순애")
break;
case "戦場":
trList.push("전장")
break;
case "おもらし":
trList.push("실금")
break;
case "ハーレム":
trList.push("할렘")
break;
case "寝取られ":
trList.push("NTR")
break;
case "女子校生":
trList.push("여고생")
break;
case "百合":
trList.push("백합")
break;
case "ミステリー":
trList.push("미스터리")
break;
case "丸呑み":
trList.push("통째 삼키기")
break;
case "のぞき":
trList.push("엿보기")
break;
case "電車":
trList.push("전차")
break;
case "寝取り":
case "네토리":
trList.push("NTL")
break;
case "おねショタ":
trList.push("오네쇼타")
break;
case "睡眠姦":
trList.push("수면간")
break;
case "ツンデレ":
trList.push("츤데레")
break;
case "アヘ顔":
trList.push("아헤가오")
break;
case "ソフトエッチ":
trList.push("소프트 엣찌")
break;
case "手コキ":
trList.push("손으로")
break;
case "足コキ":
trList.push("발로")
break;
case "ぶっかけ":
trList.push("붓카케")
break;
case "顔射":
trList.push("안면 사정")
break;
case "中出し":
trList.push("질내 사정")
break;
case "妊娠":
case "孕ませ":
trList.push("임신")
break;
case "パイズリ":
trList.push("파이즈리")
break;
case "レズ":
case "女同士":
trList.push("레즈/여자끼리")
break;
case "ゲイ":
case "男同士":
trList.push("게이/남자끼리")
break;
case "母乳":
trList.push("모유")
break;
case "搾乳":
trList.push("착유")
break;
case "出産":
trList.push("출산")
break;
case "産卵":
trList.push("산란")
break;
case "陵辱":
trList.push("능욕")
break;
case "オナニー":
trList.push("자위")
break;
case "オナサポ":
trList.push("자위 서포트")
break;
case "緊縛":
trList.push("묶기/긴박")
break;
case "フェラ":
case "フェラチオ":
trList.push("펠라치오")
break;
case "痴漢":
trList.push("치한")
break;
case "調教":
trList.push("조교")
break;
case "淫乱":
trList.push("음란")
break;
case "露出":
trList.push("노출")
break;
case "言葉責め":
trList.push("언어고문/음어")
break;
case "青姦":
trList.push("야외 플레이")
break;
case "拘束":
trList.push("구속")
break;
case "奴隷":
trList.push("노예")
break;
case "浣腸":
trList.push("관장")
break;
case "羞恥":
case "恥辱":
case "辱め":
trList.push("수치/치욕")
break;
case "監禁":
trList.push("감금")
break;
case "焦らし":
trList.push("애태우기")
break;
case "くすぐり":
trList.push("간지럼")
break;
case "鬼畜":
trList.push("귀축")
break;
case "ノーマルプレイ":
trList.push("노멀 플레이")
break;
case "放置プレイ":
trList.push("방치 플레이")
break;
case "複数プレイ":
trList.push("복수 플레이")
break;
case "乱交":
trList.push("난교")
break;
case "強制":
case "無理矢理":
trList.push("강제")
break;
case "レイプ":
trList.push("레이프")
break;
case "輪姦":
trList.push("윤간")
break;
case "和姦":
trList.push("화간")
break;
case "近親相姦":
trList.push("근친상간")
break;
case "逆レイプ":
trList.push("역 레이프")
break;
case "盗撮":
trList.push("도촬")
break;
case "男性受け":
trList.push("수비남")
break;
case "催眠":
trList.push("최면")
break;
case "放尿":
case "おしっこ":
trList.push("방뇨/오줌")
break;
case "アナル":
trList.push("애널")
break;
case "スカトロ":
trList.push("스캇물")
break;
case "尿道":
trList.push("요도")
break;
case "触手":
trList.push("촉수")
break;
case "獣姦":
trList.push("수간")
break;
case "機械姦":
trList.push("기계간")
break;
case "下克上":
trList.push("하극상")
break;
case "モブ姦":
trList.push("몹 캐릭 간")
break;
case "異種姦":
trList.push("이종간")
break;
case "悪堕ち":
trList.push("타락")
break;
case "洗脳":
trList.push("세뇌")
break;
case "ごっくん":
case "食ザー":
trList.push("정액 삼킴")
break;
case "口内射精":
trList.push("구내 사정")
break;
case "イラマチオ":
trList.push("이라마치오")
break;
case "スパンキング":
trList.push("스파킹")
break;
case "耳舐め":
trList.push("귀햝기")
break;
case "潮吹き":
trList.push("시오후키")
break;
case "ささやき":
trList.push("속삭임")
break;
case "拡張":
trList.push("확장")
break;
case "ボクっ娘":
trList.push("보쿠코")
break;
case "ショートカット":
trList.push("짧은 머리")
break;
case "ロングヘア":
trList.push("긴 머리")
break;
case "金髪":
trList.push("금발")
break;
case "黒髪":
trList.push("흑발")
break;
case "ポニーテール":
trList.push("포니테일")
break;
case "ツインテール":
trList.push("트윈테일")
break;
case "ネコミミ":
trList.push("고양이귀")
break;
case "獣耳":
trList.push("짐승귀")
break;
case "長身":
trList.push("장신")
break;
case "筋肉":
trList.push("근육")
break;
case "巨乳":
case "爆乳":
trList.push("거유/폭유")
break;
case "貧乳":
case "微乳":
case "つるぺた":
trList.push("빈유/미유")
break;
case "複乳":
case "怪乳":
case "超乳":
trList.push("복유/괴유/초유")
break;
case "乳首":
case "乳輪":
trList.push("유두/유륜")
break;
case "ぼて腹":
case "妊婦":
trList.push("볼록 배/임산부")
break;
case "スレンダー":
trList.push("슬렌더")
break;
case "ツルペタ":
trList.push("평평한 가슴")
break;
case "パイパン":
trList.push("음모 없음")
break;
case "陰毛":
trList.push("음모")
break;
case "腋毛":
trList.push("겨드랑이 털")
break;
case "フタナリ":
case "ふたなり":
trList.push("후타나리")
break;
case "巨根":
trList.push("거근")
break;
case "童貞":
trList.push("동정")
break;
case "処女":
trList.push("처녀")
break;
case "巨大化":
trList.push("거대화")
break;
case "方言":
trList.push("사투리")
break;
case "無表情":
trList.push("무표정")
break;
case "褐色":
case "日焼け":
trList.push("갈색 피부")
break;
case "包茎":
trList.push("포경")
break;
case "ムチムチ":
trList.push("쭉쭉빵빵")
break;
case "太め":
trList.push("통통한")
break;
case "デブ":
trList.push("뚱뚱한")
break;
case "蟲姦":
trList.push("충간")
break;
case "腹パン":
trList.push("배빵")
break;
case "猟奇":
trList.push("엽기")
break;
case "人体改造":
trList.push("인체 개조")
break;
case "拷問":
trList.push("고문")
break;
case "フィストファック":
trList.push("Fist fuck")
break;
case "ニプルファック":
trList.push("Nipple fuck")
break;
case "血液":
case "流血":
case "スプラッター":
trList.push("혈액/유혈")
break;
case "狂気":
trList.push("광기")
break;
case "リョナ":
trList.push("료나")
break;
case "料理":
case "グルメ":
trList.push("요리/미식가")
break;
case "評論":
trList.push("평론")
break;
case "シリーズもの":
trList.push("시리즈물")
break;
case "遠距離恋愛":
trList.push("원거리 연애")
break;
case "家族":
trList.push("가족")
break;
case "ギャンブル":
trList.push("도박")
break;
case "劇画":
trList.push("극화")
break;
case "耽美":
trList.push("탐미")
break;
case "ティーンズラブ":
trList.push("어린 사랑")
break;
case "伝奇":
trList.push("전기")
break;
case "ハードボイルド":
trList.push("비장한")
break;
case "パラレル":
trList.push("평행세계")
break;
case "パンチラ":
trList.push("팬티보임")
break;
case "ブラチラ":
trList.push("브라보임")
break;
case "ボーイズラブ":
trList.push("BL")
break;
case "恋愛":
trList.push("연애")
break;
case "委員長":
trList.push("위원장")
break;
case "叔父":
trList.push("숙부")
break;
case "義父":
trList.push("시아버지")
break;
case "ガテン系":
trList.push("가텐계")
break;
case "サラリーマン":
trList.push("직장인")
break;
case "爺":
trList.push("할아버지")
break;
case "実妹":
trList.push("친동생")
break;
case "秘書":
trList.push("비서")
break;
case "ヤリチン":
case "プレイボーイ":
trList.push("야리칭/플레이 보이")
break;
case "インテリ":
trList.push("지식인")
break;
case "おかっぱ":
trList.push("단발")
break;
case "タトゥー":
case "刺青":
trList.push("타투/문신")
break;
case "ハード系":
trList.push("하드계")
break;
case "痴女":
trList.push("치녀")
break;
case "茶髪":
trList.push("갈색 머리")
break;
case "ドジっ娘":
trList.push("도짓코/덜렁이")
break;
case "ぽっちゃり":
trList.push("풍만")
break;
case "三つ編み":
trList.push("땋은 머리")
break;
case "ミニ系":
trList.push("작은체형/어린")
break;
case "ガードル":
trList.push("거들")
break;
case "カチューシャ":
trList.push("머리띠")
break;
case "しっぽ":
trList.push("꼬리")
break;
case "スタンガン":
trList.push("전기 충격")
break;
case "スポユニ":
trList.push("운동복/스포츠 유니폼")
break;
case "男装":
trList.push("남장")
break;
case "チャイナ":
trList.push("차이나/중국풍")
break;
case "道着":
trList.push("도복")
break;
case "ドラッグ":
trList.push("마약")
break;
case "バイブ":
trList.push("바이브/진동")
break;
case "白衣":
trList.push("백의")
break;
case "半ズボン":
trList.push("반바지")
break;
case "ブレザー":
trList.push("재킷/블레이저")
break;
case "ふんどし":
trList.push("훈도시")
break;
case "包帯":
trList.push("붕대")
break;
case "注射器":
trList.push("주사기")
break;
case "リボン":
trList.push("리본")
break;
case "ローター":
trList.push("로터")
break;
case "ローレグ":
trList.push("로우 레그 컷/속옷")
break;
case "ワイシャツ":
trList.push("와이셔츠")
break;
case "乙女受け":
trList.push("처녀역할")
break;
case "オヤジ受け":
trList.push("아버지역할")
break;
case "俺様攻め":
trList.push("오레사마역할")
break;
case "クール受け":
case "クール攻め":
trList.push("쿨 플레이")
break;
case "クンニ":
trList.push("쿤닐링구스/애무")
break;
case "健気受け":
trList.push("씩씩한")
break;
case "誘い受け":
trList.push("권유받은")
break;
case "強気受け":
trList.push("강하게 받는")
break;
case "ヘタレ攻め":
trList.push("엉터리공격역할")
break;
case "やんちゃ受け":
trList.push("응석받이")
break;
case "アクション":
trList.push("액션")
break;
case "アドベンチャー":
trList.push("어드벤처")
break;
case "クイズ":
trList.push("퀴즈")
break;
case "シミュレーション":
trList.push("시뮬레이션")
break;
case "シューティング":
trList.push("슈팅")
break;
case "その他ゲーム":
trList.push("기타 게임")
break;
case "タイピング":
trList.push("타이핑")
break;
case "テーブルゲーム":
trList.push("테이블 게임")
break;
case "デジタルノベル":
trList.push("디지털 노벨")
break;
case "パズル":
trList.push("퍼즐")
break;
case "ロールプレイング":
trList.push("롤 플래잉")
break;
case "オリジナル":
trList.push("오리지널")
break;
case "二次創作":
trList.push("2차 창작")
break;
case "漫画":
trList.push("만화")
break;
case "アニメ":
trList.push("애니메이션")
break;
case "二次創作":
trList.push("2차 창작")
break;
case "ゲーム系":
trList.push("게임 계열")
break;
case "パロディ":
trList.push("패러디")
break;
case "その他":
trList.push("기타")
break;
case "成人向け":
trList.push("성인용")
break;
case "アクセサリー":
trList.push("악세서리")
break;
case "イラスト":
trList.push("일러스트")
break;
case "CG集":
trList.push("CG집")
break;
case "男無":
trList.push("남자 없음")
break;
case "音声付き":
trList.push("음성 첨부")
break;
case "女主人公のみ":
trList.push("여주인공만")
break;
case "擬人化":
trList.push("의인화")
break;
case "逆転無し":
trList.push("역전없음")
break;
case "作家複数":
trList.push("여러작가")
break;
case "残虐表現":
trList.push("잔학 표현")
break;
case "新作":
trList.push("신작")
break;
case "準新作":
trList.push("준신작")
break;
case "旧作":
trList.push("구작")
break;
case "女性視点":
trList.push("여성시점")
break;
case "シリーズもの":
trList.push("시리즈물")
break;
case "全年齢向け":
trList.push("전연령용")
break;
case "断面図あり":
trList.push("단면도 있음")
break;
case "デモ":
trList.push("데모")
break;
case "体験版あり":
trList.push("체험판 있음")
break;
case "動画":
trList.push("동영상")
break;
case "アニメーション":
trList.push("애니메이션")
break;
case "ノベル":
trList.push("노벨")
break;
case "ベスト":
trList.push("베스트")
break;
case "総集編":
trList.push("총집편")
break;
case "男性向け":
trList.push("남성용")
break;
case "女性向け":
trList.push("여성용")
break;
case "DL版独占販売":
trList.push("DL독점")
break;
case "FANZA専売":
trList.push("FANZA독점")
break;
case "がんばろう同人!":
trList.push("힘내자 동인!")
break;
case "ゲームCP":
trList.push("게임CP")
break;
default:
trList.push(tok)
break;
}
})
return arr2str(Array.from(new Set(trList)))
}