// ==UserScript== // @name Arcalive Night Restaurant Secretary // @namespace Violentmonkey Scripts // @match https://arca.live/b/* // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @require https://cdnjs.cloudflare.com/ajax/libs/jszip/3.6.0/jszip.min.js // @version 1.0.1.13 // @author - // @description 심야식당 이용 보조 유틸 // @noframes // @downloadURL https://update.greasyfork.cloud/scripts/429689/Arcalive%20Night%20Restaurant%20Secretary.user.js // @updateURL https://update.greasyfork.cloud/scripts/429689/Arcalive%20Night%20Restaurant%20Secretary.meta.js // ==/UserScript== // onload init let loaded = false window.addEventListener('load', e => { if (!loaded) { loaded = true registerMenuCommand(); asideMake(); contentStretch(); 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")) { const editorTextarea = document.querySelector("#content") let oldMatch = 0 while (true) { await sleep(100) let newMatch = editorTextarea.value.match(/src="\/\/ac\.namu\.la/g) if (newMatch && oldMatch !== newMatch.length) { oldMatch = newMatch.length editorTextarea.setAttribute("style", "height: 0; opacity: 0;") editorTextarea.focus() } 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/simya?target=content&keyword=${srt}`, "https://arca.live/b/simya?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/simya?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, { method: 'GET', responseType: 'document' }, true) .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: 200px") } // 페이지 이동 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 + `


${inputDescription.value}

` + 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, "
")); location.href = "/b/simya/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, "
")) } 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 = `

${str} ${srt}

` 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/simya?target=content&keyword=${ProcessedCode} ${srt}`, `https://arca.live/b/simya?category=번역&target=all&keyword=${obj.title[1]}`, `https://arca.live/b/simya?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/simya?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/simya/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/simya/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 = ["이미지", `이미지`] 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 = ["체험판", `체험판 다운로드(${dlJSON["trials"][0]["file_size_unit"]})`] } dlProcessedJSON.url = ["출처", `보러가기`] 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, {headers: { origin: 'https://hitomi.la', referer: 'https://hitomi.la' }}) .then(async html => { console.log(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 = ["표지", `표지`] 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 = ["출처", `보러가기`] 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', headers: { origin: 'https://hitomi.la', referer: 'https://hitomi.la' } }) .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: { origin: 'https://hitomi.la', 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 = ["표지", `표지`] 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 = ["출처", `보러가기`] 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 = ["이미지", `이미지`] 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 = ["출처", `보러가기`] 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 = ["이미지", `이미지`] 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(`${tag.querySelector(".sampleButton__txt").innerText}`) } }) fanzaProcessedJSON.sample = ["샘플", arr2str(sampleUrlList)] } fanzaProcessedJSON.url = ["출처", `보러가기`] 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 = ["이미지", `이미지`] 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(`${tag.querySelector(".download-txt").innerText}`) } }) fanzaProcessedJSON.sample = ["샘플", arr2str(sampleUrlList)] } fanzaProcessedJSON.url = ["출처", `보러가기`] 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 = ["이미지", `이미지`] 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 = ["출처", `보러가기`] 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 = ["이미지", `이미지`] 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 = ["샘플", `샘플 영상 보기`] } fanzaProcessedJSON.url = ["출처", `보러가기`] 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://ac-p2.namu.la/20210518/be8da1e1ec2913b1560967e71d37276e8886d6b9ab40da3f9b16d51cbb1e848d.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, 1200, 800) 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(`미방짤 : ${arr2str(optList)}`) }) }, "image/png") }) }) }) } // 미방짤 표시 좌표 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; } } // 미방짤 제너레이터 v2 function deviantTasteV2(optList, none) { return new Promise((resolve, reject) => { doFetch("https://ac-p2.namu.la/20210807/4ff556913b2a6547c8d424442db74bb6a72cf910dae083b829e46040994bd021.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", "1000") canvas.setAttribute("height", "665") const ctx = canvas.getContext("2d") ctx.drawImage(image, 0, 0, 1000, 665) ctx.lineWidth = 5; ctx.strokeStyle = 'red'; optList.forEach(taste => { deviantTasteCanvasRectV2(ctx, taste) }) canvas.toBlob(function(blob) { UploadImageFile(blob) .then(url => { resolve(`미방짤 : ${arr2str(optList)}`) }) }, "image/png") }) }) }) } // 미방짤 표시 좌표 v2 function deviantTasteCanvasRectV2(ctx, opt) { switch (opt) { case "게이": ctx.strokeRect(135, 273, 80, 50); break; case "보추": ctx.strokeRect(222, 273, 80, 50); break; case "후타": ctx.strokeRect(310, 273, 80, 50); break; case "청아": ctx.strokeRect(135, 325, 200, 50); break; case "중노년여성": ctx.strokeRect(343, 325, 115, 50); break; case "스캇": ctx.strokeRect(135, 383, 80, 50); break; case "방뇨": ctx.strokeRect(222, 383, 80, 50); break; case "수간": ctx.strokeRect(623, 270, 80, 50); break; case "충간": ctx.strokeRect(710, 270, 80, 50); break; case "이종간": ctx.strokeRect(797, 270, 116, 50); break; case "고어": ctx.strokeRect(623, 323, 80, 50); break; case "료나": ctx.strokeRect(710, 323, 80, 50); break; case "보어(식인)": ctx.strokeRect(797, 323, 80, 50); break; case "보태": ctx.strokeRect(623, 381, 80, 50); break; case "임신": ctx.strokeRect(710, 381, 80, 50); break; case "출산": ctx.strokeRect(797, 381, 80, 50); break; } } // 미방짤 제너레이터 v3 function deviantTasteV3(optList, none) { return new Promise((resolve, reject) => { resolve(`미방짤 : ${optList[0]}`) }) } deviantTasteDictionaryV3 = { '게이':'https://ac-p.namu.la/20210909s1/651903aefbd954eac8ab7093f6fb1974aa477ec07f4530a0ab0efe6b6b3b23f9.png', '보추':'https://ac-p.namu.la/20210909s1/f443778aac0c2efe6891c9a4a70ff9c79c4aa07d7bf11e9c10951cf6363eebfd.png', '후타':'https://ac2-p.namu.la/20210909s2/04d67121a4bc3cb224e24150835c13fa2a203fcba6332c75cf6619da99e39694.png', '청아':'https://ac-p.namu.la/20210909s1/e9eccb512840ead124fb3fabf2bf293cf41d669c74819e9196340cd1c351ee5e.png', '중노년여성':'https://ac2-p.namu.la/20210909s2/26496f0827eb9e5aec9febc8405273be530e43b6c47abba9fd4db056f2a6a1dc.png', '스캇':'https://ac-p.namu.la/20210909s1/b46b21623c9972d71cacc325b042cce5803854b2b374b29ffdf65d3a406598d7.png', '방뇨':'https://ac2-p.namu.la/20210909s2/674493f8a293ccba3aa7f50654357a6109fdf76d3d802af5f2ab28a9dca0e616.png', '수간':'https://ac-p.namu.la/20210909s1/9386bd44dc0e953f9ff4e55a640c24cc5ca09a2dfaa9a23b1447990b52a36e70.png', '충간':'https://ac2-p.namu.la/20210909s2/f437785af97cb79d3f190ea80c2af45af7fe3ee368e4095973f998b3b13f1310.png', '이종간':'https://ac-p.namu.la/20210909s1/4616e6f0839d60d04250f5e982e17222b28359d13893fcf84af6489cf78580a4.png', '고어':'https://ac2-p.namu.la/20210909s2/d302d617966c45a98de5f44cbf0fdfd9c45466cf3c06c32beef892310e6362ce.png', '료나':'https://ac2-p.namu.la/20210909s2/a387023dcd6c4e80a0aaeadd57ebdd970ca858510261dbb5b465b12e725eef04.png', '보어(식인)':'https://ac2-p.namu.la/20210909s2/dd1dba41aa7e1d54319bd37561025123a02d6472003fda36ce7b14b9541f732f.png', '임신':'https://ac-p.namu.la/20210909s1/eab0e5dc093aa4e9ee5b71158bc3459f3722f7a79b7efac094b4e8129dec0ca2.png', '보테':'https://ac-p.namu.la/20210909s1/ae03617f8bc99733c4200e519629a69cf2140b06150db07f8b52449ddbaf2ce8.png', '출산':'https://ac2-p.namu.la/20210909s2/2fcc8a2e97e1e917eeecb11a673db9a939b49fdd7c6b30f6f6b1c2255cf77e05.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 = `
×

미방짤 메이커

` 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) } }) let deviantTasteversion if (optionList.length === 1) { deviantTasteversion = deviantTasteV3 } else if (customTaste.value) { deviantTasteversion = deviantTaste } else { deviantTasteversion = deviantTasteV2 } deviantTasteversion(optionList, customTaste.value) .then(img => { let editorBox = document.querySelector('.write-body .fr-element') editorBox.innerHTML = img + editorBox.innerHTML e.target.innerText = "미방짤 만들기" 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"; } // 미방짤 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(`이미지+파일`) }) }, "image/jpeg") }) }) } // 이미지 jpg 컨버터 function imageToJpgConvert() { 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 = `
×

이미지+파일 바인더

` 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-image-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("multiple", "") fileInput.setAttribute("style", "margin-bottom:20px") const webkitdirectoryH5 = document.createElement("h5") webkitdirectoryH5.innerText = "폴더 추가" webkitdirectoryH5.setAttribute("style", "margin-bottom:20px") const webkitdirectoryInput = document.createElement("input") webkitdirectoryInput.setAttribute("id", "sm-imagezip-modal-webkitdirectory-input") webkitdirectoryInput.setAttribute("type", "file") webkitdirectoryInput.setAttribute("webkitdirectory", "") webkitdirectoryInput.setAttribute("directory", "") webkitdirectoryInput.setAttribute("multiple", "") webkitdirectoryInput.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 = "파일 처리중..." new Promise(async (resolve, reject) => { const fileList = fileInput.files const directoryList = webkitdirectoryInput.files const allFileList = [...fileList, ...directoryList] if (allFileList.length === 1 && directoryList.length === 0) { resolve(fileList[0]) } else { const zip = new JSZip(); for (let userFile of allFileList) { await fileToArrayBuffer(userFile).then(buffer => { let blob = new Blob([buffer]) zip.file(userFile.webkitRelativePath || userFile.name, blob); }) } e.target.innerText = "파일 압축중..." zip.generateAsync({ type: "blob", compression: "DEFLATE", compressionOptions: { level: 9 } }) .then(zipFile => { resolve(zipFile) }) } }).then(resultFile => { e.target.innerText = "파일 업로드중..." if (imageFileInput.files[0]) { resolve(imagezipBind(imageFileInput.files[0], resultFile)) } else { let imgSrc if (imageUrl.value) { imgSrc = imageUrl.value } else { imgSrc = "https://p-ac.namu.la/20210409/be7f73a4b81529309fb0777af0ef42972e8869d637f0fa5940ab8cf145edfc0e.png?type=orig" } doFetch(imgSrc, { method: "GET", responseType: "blob" }) .then(blob => { resolve(imagezipBind(blob, resultFile)) }) } }) }) .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(webkitdirectoryH5) body.appendChild(webkitdirectoryInput) 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 => { try { let ta = tag.querySelectorAll("a") ta.forEach(pta => { if (pta) { const taa = pta.querySelector("a") if (taa) { taa.href = pta.href } } }) } catch { // none } }) } // 아카콘 링크하기 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', (e) => { 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 = (e.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)); e.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; box-shadow: 0 15px 35px rgba(50,50,90,0.1), 0 5px 15px rgba(0,0,0,0.07); overflow: hidden; z-index: 1400; } .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 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 = `${opt} 확장자로 저장` }) } 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"); }; const allPtag = document.querySelector(".article-content").querySelectorAll("p") const allAtag = document.querySelector(".article-content").querySelectorAll("a") const allImgtag = document.querySelector(".article-content").querySelectorAll("img") const allTag = Array.from(new Set([...allPtag, ...allAtag, ...allImgtag])) allTag.forEach(pretag => { 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 = "다운로드 중..." } }); pretag.addEventListener("contextmenu", e => { let tag = pretag.querySelector("a") || pretag.closest("a") || pretag pretag.querySelectorAll("img").forEach(multiTag => { if (multiTag === e.target) { tag = e.target.closest("a") } }) console.log(tag) if (tag && tag.href.includes("?type=orig")) { e.preventDefault(); const origin = { left: e.pageX - 200, top: e.pageY - 172 }; imageSaveContextMenuTargetImage = tag.href setPosition(origin); return false; } }); }) } // 이미지 다른 확장자로 저장 컨텍스트 메뉴 init function imageSaveContextMenuInit() { if (location.href.match(/\/b\/.*?\/\d+/) && !location.href.includes("/edit")) { imageSaveContextMenu() } } // MEGA 링크 암호화 function MEGAencryptCore(e, t, n) { function r(e) { for (var t = window.atob(e.replace(/_/g, "/").replace(/-/g, "+")), n = t.length, r = new Uint8Array(n), a = 0; a < n; a++) r[a] = t.charCodeAt(a); return r } let a, i; e.includes("/folder/") ? (a = 0, i = 16) : e.includes("/file/") && (a = 1, i = 32); let l = e.match(/mega.nz\/.*\/(.*)#(.*)/), o = l[1], c = l[2], s = new TextEncoder("utf-8").encode(t), g = crypto.getRandomValues(new Uint8Array(32)); crypto.subtle.importKey("raw", s, "PBKDF2", !1, ["deriveBits"]).then(function(e) { var t = { name: "PBKDF2", hash: "SHA-512", salt: g, iterations: 1e5 }; return crypto.subtle.deriveBits(t, e, 512) }).then(function(e) { let t = new Uint8Array(e), l = new Uint8Array(t.buffer, 0, i), s = r(c), h = new Uint8Array(t.buffer, 32, 32), u = new Uint8Array(l.length); for (var y = 0; y < u.length; y++) u[y] = l[y] ^ s[y]; let f = r(o), w = 2 + f.length + g.length + u.length, A = new Uint8Array(w); A[0] = 2, A[1] = a, A.set(f, 2), A.set(g, 8), A.set(u, 40), async function(e, t) { const n = e, r = t, a = await crypto.subtle.importKey("raw", n, { name: "HMAC", hash: "SHA-256" }, !0, ["sign"]), i = await crypto.subtle.sign("HMAC", a, r); return new Uint8Array(i) }(h, A).then(function(e) { let t = new Uint8Array(A.length + e.length); t.set(A, 0), t.set(e, A.length); let r = function(e) { for (var t = "", n = e.byteLength, r = 0; r < n; r++) t += String.fromCharCode(e[r]); return window.btoa(t).replace(/\//g, "_").replace(/\+/g, "-").replace(/=/g, "") }(t); n("https://mega.nz/#P!" + r) }) }) } // MEGA 링크 암호화 모달 function MEGAencrypt() { let smmModalStyle = document.createElement("style") smmModalStyle.append(` .smmmodal { 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); } .smmmodal-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} } .smmclose { color: white; float: right; font-size: 28px; font-weight: bold; } .smmclose:hover, .smmclose:focus { color: white; text-decoration: none; cursor: pointer; } .smmmodal-header { padding: 0 20px 45px 20px; background-color: lightgray; color: white; } .smmmodal-body { padding: 2px 30px; } .smmmodal-footer { padding: 0 0 45px 0; background-color: lightgray; color: white; } .sexy-color-red { background: linear-gradient(to right, red 0%, crimson 100%); } `) let smmModal = document.createElement("div") smmModal.setAttribute("id", "sm-mega-modal") smmModal.setAttribute("class", "smmmodal") smmModal.innerHTML = `
×

MEGA 링크 암호화

` document.head.appendChild(smmModalStyle) document.body.appendChild(smmModal) let modal = document.getElementById("sm-mega-modal"); let span = document.getElementById("sm-mega-modal-close") let body = document.getElementById("sm-mega-modal-body"); const getLinkH5 = document.createElement("h5") getLinkH5.setAttribute("style", "margin-bottom:20px") getLinkH5.innerText = "공유 링크 입력" const getLinkInput = document.createElement("input") getLinkInput.setAttribute("id", "sm-mega-modal-url-input") getLinkInput.setAttribute("class", "form-control form-control-sm float-right") getLinkInput.setAttribute("style", "margin-bottom:20px") const getPasswordH5 = document.createElement("h5") getPasswordH5.setAttribute("style", "margin-bottom:20px") getPasswordH5.innerText = "비밀번호 입력 (빈칸일 시 국룰 적용)" const getPasswordInput = document.createElement("input") getPasswordInput.setAttribute("id", "sm-mega-modal-password-input") getPasswordInput.setAttribute("class", "form-control form-control-sm") const outputLinkTextarea = document.createElement("div") outputLinkTextarea.setAttribute("id", "sm-mega-modal-url-output") outputLinkTextarea.setAttribute("class", "clearfix") outputLinkTextarea.setAttribute("style", "margin-bottom:20px;padding:5px;border:1px solid #bbb;word-break: break-all") outputLinkTextarea.innerText = "..." const confirmBtn = document.createElement("div") confirmBtn.setAttribute("id", "sm-mega-modal-confirm-btn") confirmBtn.setAttribute("class", "btn btn-arca float-right") confirmBtn.setAttribute("style", "margin:20px 0 20px 0") confirmBtn.innerText = "암호화" confirmBtn.onclick = function(e) { let link = getLinkInput.value let pw = getPasswordInput.value if (!pw) { pw = "smpeople" } if (link) { MEGAencryptCore(link, pw, function(l) { outputLinkTextarea.innerText = l }) } } body.appendChild(getLinkH5) body.appendChild(getLinkInput) body.appendChild(getPasswordH5) body.appendChild(getPasswordInput) body.appendChild(confirmBtn) body.appendChild(outputLinkTextarea) 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 MEGAencryptInit() { let modal = document.getElementById("sm-mega-modal"); if (modal === null && (location.href.includes("/write") || location.href.includes("/edit"))) { MEGAencrypt() } else { if (modal !== null) { modal.style.display = "block" } } } // 메뉴 커맨드 function registerMenuCommand() { GM_registerMenuCommand("< 미방짤 만들기 >", deviantTasteMenuInit, "T"); GM_registerMenuCommand("< 이미지+파일 합치기 >", imagezipMakerInit, "Z"); GM_registerMenuCommand("< MEGA 링크 암호화 >", MEGAencryptInit, "M"); 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) trList.push(J2Kdictionary[tok] || tok) }) return arr2str(Array.from(new Set(trList))) } J2Kdictionary = { "萌え": "모에", "燃え": "열혈", "感動": "감동", "癒し": "힐링", "鬱": "우울", "淡白": "담백", "あっさり": "담백", "オールハッピー": "올해피", "着衣": "착의", "チラリズム": "치라리즘", "フェチ": "페티즘", "女性視点": "여성시점", "女主人公": "여주인공", "男主人公": "남주인공", "逆転無し": "역전없음", "マニアック": "마니악", "変態": "변태", "野外": "야외", "おさわり": "만지기", "きせかえ": "옷갈아입히기", "脚": "다리", "お尻": "엉덩이", "ヒップ": "엉덩이", "おっぱい": "가슴", "淫語": "음어", "汁": "체액대량", "液大量": "체액대량", "連続絶頂": "연속절정", "断面図": "단면도", "ドット": "도트", "ドット制作": "도트", "アニメ": "애니메이션", "総集編": "총집편", "バイノーラル": "바이노럴", "ダミヘ": "더미헤드폰", "催眠音声": "최면음성", "アンソロジー": "앤솔로지", "技術書": "기술서", "ツクール": "알만툴", "3D作品": "3D작품", "東方Project": "동방Project", "ピアス": "피어스", "装飾品": "장식품", "首輪": "목줄", "鎖": "사슬", "拘束具": "구속구", "ムチ": "채찍", "縄": "밧줄", "蝋燭": "촛농", "薬物": "약물", "ローション": "로션", "おむつ": "기저귀", "おもちゃ": "장난감", "道具": "도구", "異物": "이물질", "メガネ": "안경", "めがね": "안경", "靴下": "양말", "少女": "소녀", "ロリ": "로리", "ぷに": "뿌니", "少年": "소년", "ショタ": "쇼타", "年上": "연상", "妹": "여동생", "母親": "모친", "義妹": "의여동생", "娘": "딸", "義母": "의어머니", "実姉": "친누나/친언니", "義姉": "의누나/의언니", "おやじ": "아버지", "熟女": "숙녀", "人妻": "유부녀", "お姉さん": "누나/언니", "未亡人": "미망인", "既婚者": "기혼자", "幼なじみ": "소꿉친구", "双子": "쌍둥이", "姉妹": "자매", "保健医": "보건의", "女医": "여의사", "女教師": "여교사", "教師": "교사", "学生": "학생", "同級生": "동급생", "同僚": "동료", "先輩": "선배", "後輩": "후배", "お嬢様": "아가씨", "ギャル": "갸루", "ビッチ": "빗치", "天然": "천연", "主従": "주종", "主婦": "주부", "女王様": "여왕님", "お姫様": "공주님", "エルフ": "엘프", "妖精": "요정", "天使": "천사", "悪魔": "악마", "変身ヒロイン": "변신히로인", "魔法少女": "마법소녀", "魔法使い": "마법사", "魔女": "마녀", "男の娘": "오토코노코", "妖怪": "요괴", "擬人化": "의인화", "ヤンデレ": "얀데레", "人外娘": "인외", "モンスター娘": "몬스터소녀", "ロボット": "로봇", "アンドロイド": "안드로이드", "芸能人": "예능인", "アイドル": "아이돌", "モデル": "모델", "警察": "경찰", "刑事": "형사", "ヤクザ": "야쿠자", "裏社会": "야쿠자", "不良": "불량", "ヤンキー": "일진", "レスラー": "레슬러", "格闘家": "격투가", "幽霊": "유령", "ゾンビ": "좀비", "けもの": "짐승", "獣化": "동물화", "外国人": "외국인", "体育会系": "체육", "スポーツ選手": "스포츠선수", "ニューハーフ": "뉴하프", "戦士": "전사", "くノ一": "쿠노이치", "サキュバス": "서큐버스", "淫魔": "음마", "制服": "교복", "제복": "교복", "セーラー服": "세일러복", "体操着": "체조복", "水着": "수영복", "メイド": "메이드", "看護婦": "간호사", "ナース": "간호사", "巫女": "무녀", "軍服": "군복", "下着": "속옷", "パンツ": "팬티", "ゴスロリ": "고스로리", "コスプレ": "코스프레", "ボンデージ": "본디지", "ボンテージ": "본디지", "ブルマ": "브루마", "ミニスカ": "미니스커트", "着物": "기모노", "和服": "기모노", "エプロン": "앞치마", "ラバー": "고무재질", "レオタード": "레오타드", "シスター": "수녀", "ウェイトレス": "웨이트리스", "バニーガール": "바니걸", "버니걸": "바니걸", "スパッツ": "스판츠", "ニーソックス": "니삭스", "ストッキング": "스타킹", "スクール水着": "학교수영복", "スーツ": "정장", "ガーター": "가터", "女装": "여장", "学校": "학교", "学園": "학원", "学園もの": "학원", "オフィス": "오피스", "職場": "직장", "ラブコメ": "러브코메디", "耳かき": "귀청소", "屋外": "옥외", "ギャグ": "개그", "ラブラブ": "러브러브", "あまあま": "달콤달콤", "退廃": "퇴폐", "背徳": "배덕", "インモラル": "인모럴", "憑依": "빙의", "石化": "석화", "コメディ": "코메디", "日常": "일상", "生活": "생활", "時間停止": "시간정지", "ミリタリー": "밀리터리", "スポーツ": "스포츠", "格闘": "격투", "ほのぼの": "푸근함", "同棲": "동거", "恋人同士": "연인끼리", "初体験": "첫체험", "色仕掛け": "미인계", "女体化": "여체화", "性転換": "성전환", "浮気": "바람", "売春": "매춘", "援交": "원교", "風俗": "풍속", "ソープ": "소프", "シリアス": "시리어스", "ファンタジー": "판타지", "歴史": "역사", "時代物": "시대물", "ホラー": "호러", "キャットファイト": "캣파이트", "サスペンス": "서스펜스", "バイオレンス": "바이올런스", "ノンフィクション": "논픽션", "体験談": "체험담", "オカルト": "오컬트", "歳の差": "나이차", "魔法": "마법", "同居": "동거", "純愛": "순애", "戦場": "전장", "おもらし": "실금", "ハーレム": "할렘", "寝取られ": "NTR", "女子校生": "여고생", "百合": "백합", "ミステリー": "미스터리", "丸呑み": "통째삼키기", "のぞき": "엿보기", "電車": "전차", "寝取り": "NTL", "おねショタ": "오네쇼타", "睡眠姦": "수면간", "ツンデレ": "츤데레", "アヘ顔": "아헤가오", "ソフトエッチ": "소프트엣찌", "手コキ": "손으로", "足コキ": "발로", "ぶっかけ": "붓카케", "顔射": "안면사정", "中出し": "질내사정", "妊娠": "임신", "孕ませ": "임신", "パイズリ": "파이즈리", "レズ": "레즈", "女同士": "여자끼리", "ゲイ": "게이", "男同士": "남자끼리", "母乳": "모유", "搾乳": "착유", "出産": "출산", "産卵": "산란", "陵辱": "능욕", "オナニー": "자위", "オナサポ": "자위서포트", "緊縛": "묶기/긴박", "フェラ": "펠라", "フェラチオ": "펠라치오", "痴漢": "치한", "調教": "조교", "淫乱": "음란", "露出": "노출", "言葉責め": "언어고문/음어", "青姦": "야외플레이", "拘束": "구속", "奴隷": "노예", "浣腸": "관장", "羞恥": "수치", "辱め": "굴욕", "監禁": "감금", "焦らし": "애태우기", "くすぐり": "간지럼", "鬼畜": "귀축", "ノーマルプレイ": "노멀플레이", "放置プレイ": "방치플레이", "複数プレイ": "복수플레이", "乱交": "난교", "強制": "강제", "無理矢理": "강제", "レイプ": "레이프", "輪姦": "윤간", "和姦": "화간", "近親相姦": "근친상간", "逆レイプ": "역레이프", "盗撮": "도촬", "男性受け": "수비남", "催眠": "최면", "放尿": "방뇨", "おしっこ": "오줌", "アナル": "애널", "スカトロ": "스캇물", "尿道": "요도", "触手": "촉수", "獣姦": "수간", "機械姦": "기계간", "下克上": "하극상", "モブ姦": "몹캐릭간", "異種姦": "이종간", "悪堕ち": "타락", "洗脳": "세뇌", "ごっくん": "정액삼킴", "食ザー": "정액삼킴", "口内射精": "구내사정", "イラマチオ": "이라마치오", "スパンキング": "스파킹", "耳舐め": "귀햝기", "潮吹き": "시오후키", "ささやき": "속삭임", "拡張": "확장", "ボクっ娘": "보쿠코", "ショートカット": "짧은머리", "ロングヘア": "긴머리", "金髪": "금발", "黒髪": "흑발", "ポニーテール": "포니테일", "ツインテール": "트윈테일", "ネコミミ": "고양이귀", "獣耳": "짐승귀", "長身": "장신", "筋肉": "근육", "巨乳": "거유", "爆乳": "폭유", "つるぺた": "평평가슴", "貧乳": "빈유", "微乳": "미유", "複乳": "복유", "怪乳": "괴유", "超乳": "초유", "乳首": "유두", "乳輪": "유륜", "ぼて腹": "볼록배", "妊婦": "임산부", "スレンダー": "슬렌더", "ツルペタ": "평평한가슴", "パイパン": "음모없음", "陰毛": "음모", "腋毛": "겨드랑이털", "フタナリ": "후타나리", "ふたなり": "후타나리", "巨根": "거근", "童貞": "동정", "処女": "처녀", "巨大化": "거대화", "方言": "사투리", "無表情": "무표정", "褐色": "갈색피부", "日焼け": "갈색피부", "包茎": "포경", "ムチムチ": "쭉쭉빵빵", "太め": "통통한", "デブ": "뚱뚱한", "蟲姦": "충간", "腹パン": "배빵", "猟奇": "엽기", "人体改造": "인체개조", "拷問": "고문", "フィストファック": "Fistfuck", "ニプルファック": "Nipplefuck", "血液": "혈액", "スプラッター": "유혈", "狂気": "광기", "リョナ": "료나", "料理": "요리", "グルメ": "미식가", "評論": "평론", "シリーズもの": "시리즈물", "遠距離恋愛": "원거리연애", "家族": "가족", "ギャンブル": "도박", "劇画": "극화", "耽美": "탐미", "ティーンズラブ": "어린사랑", "伝奇": "전기", "ハードボイルド": "비장한", "パラレル": "평행세계", "パンチラ": "팬티보임", "ブラチラ": "브라보임", "ボーイズラブ": "BL", "恋愛": "연애", "委員長": "위원장", "叔父": "숙부", "義父": "시아버지", "ガテン系": "가텐계", "サラリーマン": "직장인", "爺": "할아버지", "実妹": "친동생", "秘書": "비서", "ヤリチン": "야리칭", "プレイボーイ": "플레이보이", "インテリ": "지식인", "おかっぱ": "단발", "タトゥー": "타투", "刺青": "문신", "ハード系": "하드계", "痴女": "치녀", "茶髪": "갈색머리", "ドジっ娘": "도짓코/덜렁이", "ぽっちゃり": "풍만", "三つ編み": "땋은머리", "ミニ系": "작은체형/어린", "ガードル": "거들", "カチューシャ": "머리띠", "しっぽ": "꼬리", "スタンガン": "전기충격", "スポユニ": "운동복/스포츠유니폼", "男装": "남장", "チャイナ": "차이나/중국풍", "道着": "도복", "ドラッグ": "마약", "バイブ": "바이브/진동", "白衣": "백의", "半ズボン": "반바지", "ブレザー": "재킷/블레이저", "ふんどし": "훈도시", "包帯": "붕대", "注射器": "주사기", "リボン": "리본", "ローター": "로터", "ローレグ": "로우레그컷/속옷", "ワイシャツ": "와이셔츠", "乙女受け": "처녀역할", "オヤジ受け": "아버지역할", "俺様攻め": "오레사마역할", "クール受け": "쿨플레이", "クール攻め": "쿨플레이", "クンニ": "쿤닐링구스/애무", "健気受け": "씩씩한", "誘い受け": "권유받은", "強気受け": "강하게받는", "ヘタレ攻め": "엉터리공격역할", "やんちゃ受け": "응석받이", "アクション": "액션", "アドベンチャー": "어드벤처", "クイズ": "퀴즈", "シミュレーション": "시뮬레이션", "シューティング": "슈팅", "その他ゲーム": "기타게임", "タイピング": "타이핑", "テーブルゲーム": "테이블게임", "デジタルノベル": "디지털노벨", "パズル": "퍼즐", "ロールプレイング": "롤플래잉", "オリジナル": "오리지널", "二次創作": "2차창작", "漫画": "만화", "アニメ": "애니메이션", "二次創作": "2차창작", "ゲーム系": "게임계열", "パロディ": "패러디", "その他": "기타", "成人向け": "성인용", "アクセサリー": "악세서리", "イラスト": "일러스트", "CG集": "CG집", "男無": "남자없음", "音声付き": "음성첨부", "女主人公のみ": "여주인공만", "擬人化": "의인화", "逆転無し": "역전없음", "作家複数": "여러작가", "残虐表現": "잔학표현", "新作": "신작", "準新作": "준신작", "旧作": "구작", "女性視点": "여성시점", "シリーズもの": "시리즈물", "全年齢向け": "전연령용", "断面図あり": "단면도있음", "デモ": "데모", "体験版あり": "체험판있음", "動画": "동영상", "アニメーション": "애니메이션", "ノベル": "노벨", "ベスト": "베스트", "総集編": "총집편", "男性向け": "남성용", "女性向け": "여성용", "DL版独占販売": "DL독점", "FANZA専売": "FANZA독점", "がんばろう同人!": "힘내자동인!", "ゲームCP": "게임CP" }