This commit is contained in:
lunaticbum 2025-10-13 18:57:22 +09:00
parent 197d704e27
commit e9b287cf81
4 changed files with 207 additions and 48 deletions

View File

@ -420,32 +420,66 @@ var time2 = null
var time1 = null
function gotoNext() {
clearTimeout(time1)
try{
console.log("targetUrl :: " + targetUrl);
if (document.querySelector('[class="btn-group"]')) {
time2 = setTimeout(function () {
clearTimeout(time2)
var targetElement = null
document.querySelector('[class="btn-group"]').querySelectorAll('a').forEach(function(e){
if(e.hasAttribute("href") &&
(
(e.getAttribute("href").search("page=2") > -1 && location.href.search("page") < 0) ||
(e.getAttribute("href").search("page=3") > -1 && location.href.search("page=2") > 0) ||
(e.getAttribute("href").search("page=4") > -1 && location.href.search("page=3") > 0)
)) {
targetElement = e
try {
let attempts = 0; // 시도 횟수를 기록할 변수
const maxAttempts = 8; // 최대 15초 동안 시도
let currentPage = 1; // 기본 페이지
}
})
if(targetElement !== null) {
targetElement.click()
} else {
// location.href = "https://naver.com"
// 1초마다 실행될 로직을 담는 변수
const findAndClickInterval = setInterval(function () {
attempts++; // 시도 횟수 증가
// 현재 페이지 번호 가져오기 (이 로직은 한 번만 실행해도 되지만, 페이지가 동적으로 바뀔 경우를 대비해 내부에 둡니다)
var pageMatch = location.href.match(/page=(\d+)/);
if (pageMatch && pageMatch[1]) {
currentPage = parseInt(pageMatch[1], 10);
toast(`[${0}초] ⏳ 다음 페이지 링크를 찾는 중... (현재: page=${currentPage})`)
}
var nextPage = currentPage + 1;
var targetElement = null;
document.querySelectorAll('.btn-group')?.forEach((el) => {
var pageLinks =el?.querySelectorAll('a');
if (pageLinks) {
pageLinks.forEach(function(link) {
toast(`[${attempts}초] ✅ 다음 페이지 링크를 찾는 중 ${link.getAttribute('href')}`);
if (link.getAttribute('href')?.includes(`page=${nextPage}`)) {
targetElement = link;
toast(`[${attempts}초] ✅ 다음 페이지 링크를 찾았습니다.`);
}
});
}
}, 15000);
}
} catch (e) {
})
// 1. 다음 페이지 링크를 찾았을 경우
if (attempts > 3 && targetElement) {
console.log(`[${attempts}초] ✅ 다음 페이지 링크를 찾았습니다. 클릭합니다.`);
toast(`[${attempts}초] ✅ 다음 페이지 링크를 찾았습니다. 클릭합니다.`)
clearInterval(findAndClickInterval); // 반복을 중단
targetElement.click(); // 링크 클릭
}
// 2. 시간(15초)이 초과되었을 경우
else if (attempts >= maxAttempts) {
console.log(`[${attempts}초] ❌ 시간 초과. 다음 페이지 링크를 찾지 못했습니다.`);
toast(`[${attempts}초] ❌ 시간 초과. 다음 페이지 링크를 찾지 못했습니다.`)
clearInterval(findAndClickInterval); // 반복을 중단
// 필요하다면 이곳에 다른 페이지로 이동하는 로직을 추가하세요.
// location.href = "https://naver.com";
}
// 3. 아직 링크를 찾지 못했고, 시간도 남았을 경우
else {
toast(`[${attempts}초] ⏳ 다음 페이지 링크를 찾는 중... (대상: page=${nextPage})`)
console.log(`[${attempts}초] ⏳ 다음 페이지 링크를 찾는 중... (대상: page=${nextPage})`);
}
}, 1500); // 1000ms = 1초마다 함수 실행
} catch (e) {
console.error("스크립트 실행 중 오류 발생:", e);
}
}
@ -663,32 +697,83 @@ async function handleCommon() {
}
);
console.log(`Found TEST`);
const imageSelector = 'img[class*="mw-100"][src*="images"]';
const images = Array.from(document.querySelectorAll(imageSelector));
console.log(`Found ${images}`);
if (images.length > 0) {
const validImageUrls = images.map(img => img.src)
.filter(src => src && src.startsWith('http'));
const uniqueUrls = [...new Set(validImageUrls)];
let scrollInterval;
console.log(`Found ${'$'}{uniqueUrls.length} unique images to cache.`);
async function getImg() {
console.log(`Found TEST`);
const imageSelector = 'img[class*="mw-100"][src*="images"]';
const images = Array.from(document.querySelectorAll(imageSelector));
console.log(`Found ${images}`);
if (images.length > 0) {
const validImageUrls = images.map(img => img.src)
.filter(src => src && src.startsWith('http'));
const uniqueUrls = [...new Set(validImageUrls)];
console.log(`Found ${'$'}{uniqueUrls.length} unique images to cache.`);
for (const image of uniqueUrls) {
// 3. 각 URL을 순회하며 Base64로 변환하고 즉시 네이티브로 전송
// (모든 작업을 병렬로 처리하지 않고 순차적(또는 하나씩)으로 보내 메모리 부담을 줄임)
for (const url of uniqueUrls) {
const base64Data = await getBase64FromUrl(url);
if (base64Data) {
// 이미지 하나를 성공할 때마다 네이티브로 즉시 전송
sendMessage({
type: "SINGLE_IMAGE_DATA",
imgSrc: url,
base64Data: base64Data
});
}
// 3. 각 URL을 순회하며 Base64로 변환하고 즉시 네이티브로 전송
// (모든 작업을 병렬로 처리하지 않고 순차적(또는 하나씩)으로 보내 메모리 부담을 줄임)
var idx = 0
for (const url of uniqueUrls) {
try {
const base64Data = await getBase64FromUrl(url);
if (base64Data) {
// 이미지 하나를 성공할 때마다 네이티브로 즉시 전송
sendMessage({
type: "SINGLE_IMAGE_DATA",
imgSrc: url,
base64Data: base64Data
});
}
} catch (e) {
}
toast(`${idx + 1} / ${uniqueUrls.length} unique images to cache.`);
idx += 1;
}
gotoNext()
}
gotoNext()
}
// 스크롤 함수 정의
function smoothScrollDown() {
// 1. 전체 페이지의 높이를 가져옵니다.
const totalHeight = document.body.scrollHeight;
// 2. 한 번에 스크롤할 거리를 계산합니다 (전체 높이의 20분의 1).
const scrollIncrement = totalHeight / 25;
// 3. 1초(1000ms) 간격으로 스크롤을 반복 실행합니다.
scrollInterval = setInterval(() => {
// 현재 스크롤 위치 + 화면에 보이는 높이가 전체 페이지 높이보다 크거나 같으면
// 페이지의 끝에 도달한 것입니다.
if (Math.ceil(window.scrollY) + Math.ceil(window.innerHeight) >= (totalHeight * 0.9)) {
console.log("✅ 페이지 끝에 도달했습니다. 스크롤을 중지합니다.");
toast("✅ 페이지 끝에 도달했습니다. 스크롤을 중지합니다.")
clearInterval(scrollInterval); // 반복 실행을 멈춥니다.
getImg()
} else {
// 계산된 거리만큼 부드럽게 아래로 스크롤합니다.
window.scrollBy({
top: scrollIncrement,
left: 0,
behavior: 'smooth' // 부드럽게 스크롤하는 옵션
});
toast(`smooth scrollBy ${scrollIncrement}, ${window.scrollY}, ${window.innerHeight}, ${totalHeight}`)
}
}, 2000); // 1000밀리초 = 1초
}
// 스크롤 함수 실행
smoothScrollDown();
}
else if(location.href.search("javt")) {
console.log(`Found TEST`);

View File

@ -23,6 +23,7 @@ import android.content.ComponentCallbacks2
import android.database.sqlite.SQLiteDatabase
import bums.lunatic.launcher.helpers.HourlyLogWriter
import bums.lunatic.launcher.helpers.PrefHelper
import bums.lunatic.launcher.home.Base64ImageCache
import bums.lunatic.launcher.home.Base64RequestHandler
import bums.lunatic.launcher.utils.Blog
import com.squareup.picasso.OkHttp3Downloader
@ -45,6 +46,7 @@ internal class LunaticLauncher : Application() {
appContext = this
// Base.initialize(this)
PrefHelper.initialize(this)
Base64ImageCache.init(this)
val dir = File("/storage/emulated/0/bums_ob/BUM'S PACED /scraped/logs")
///BUM'S PACED/pdfs
if (!dir.exists()) {

View File

@ -1,15 +1,25 @@
package bums.lunatic.launcher.home
import android.content.Context
import android.graphics.BitmapFactory
import android.util.Base64
import androidx.collection.LruCache
import com.squareup.picasso.Picasso
import com.squareup.picasso.Request
import com.squareup.picasso.RequestHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import java.io.File
import java.io.IOException
import java.math.BigInteger
import java.security.MessageDigest
/**
* Base64 데이터를 URL 키와 함께 저장하는 메모리 캐시 (Singleton 또는 DI로 관리)
* Base64 데이터를 URL 키와 함께 저장하는 2단계 캐시 (메모리 + 파일)
* 1. 메모리 캐시(LruCache) 먼저 확인하여 빠른 접근을 제공합니다.
* 2. 메모리에 없는 경우 파일 캐시를 확인하여 디스크 영속성을 지원합니다.
* OOM(메모리 부족) 피하기 위해 비트맵 자체가 아닌 Base64 문자열을 저장합니다.
*/
object Base64ImageCache {
@ -17,22 +27,83 @@ object Base64ImageCache {
private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
private val cacheSize = maxMemory / 8
// Key: Image URL, Value: Base64 Data String
// L1 Cache: Memory (Key: Image URL, Value: Base64 Data String)
private val lru: LruCache<String, String> = LruCache(cacheSize)
// L2 Cache: File System
private var cacheDir: File? = null
private val coroutineScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
/**
* 캐시를 사용하기 전에 반드시 Application 클래스 등에서 초기화해야 합니다.
* @param context 애플리케이션 컨텍스트
*/
fun init(context: Context) {
// 앱의 캐시 디렉토리 내에 이미지 캐시 전용 폴더 생성
cacheDir = File(context.cacheDir, "base64_image_cache").apply { mkdirs() }
}
/**
* 메모리 캐시와 파일 캐시에 Base64 데이터를 저장합니다.
* 파일 저장은 백그라운드에서 비동기적으로 수행됩니다.
*/
fun put(url: String, base64Data: String) {
// 1. 메모리 캐시에 저장
if (lru.get(url) == null) {
lru.put(url, base64Data)
}
// 2. 파일 캐시에 비동기적으로 저장
coroutineScope.launch {
cacheDir?.let {
try {
val file = File(it, url.toMd5())
file.writeText(base64Data, Charsets.UTF_8)
} catch (e: Exception) {
e.printStackTrace() // 실제 앱에서는 로깅 라이브러리 사용 권장
}
}
}
}
/**
* 메모리 또는 파일 캐시에서 Base64 데이터를 가져옵니다.
* 파일 캐시에서 가져온 경우, 메모리 캐시에도 추가합니다.
*/
fun get(url: String): String? {
return lru.get(url)
// 1. 메모리 캐시에서 먼저 조회
lru.get(url)?.let { return it }
// 2. 메모리에 없으면 파일 캐시에서 조회
cacheDir?.let {
try {
val file = File(it, url.toMd5())
if (file.exists()) {
val base64Data = file.readText(Charsets.UTF_8)
// 파일에서 읽은 데이터를 메모리에 올려서 다음 접근 속도를 높임
lru.put(url, base64Data)
return base64Data
}
} catch (e: Exception) {
e.printStackTrace()
}
}
return null
}
/**
* URL을 MD5 해시로 변환하여 안전한 파일 이름으로 사용합니다.
*/
private fun String.toMd5(): String {
val md = MessageDigest.getInstance("MD5")
return BigInteger(1, md.digest(toByteArray())).toString(16).padStart(32, '0')
}
}
/**
* Picasso가 요청한 URL이 Base64ImageCache에 있는지 확인하는 커스텀 핸들러
* ( 클래스는 변경할 필요가 없습니다)
*/
class Base64RequestHandler : RequestHandler() {
@ -44,7 +115,7 @@ class Base64RequestHandler : RequestHandler() {
*/
override fun canHandleRequest(data: Request): Boolean {
val url = data.uri.toString()
// data: 스킴이 아니고, 우리 캐시에 URL 키가 존재할 때만 true 반환
// data: 스킴이 아니고, 우리 캐시(메모리 또는 파일)에 URL 키가 존재할 때만 true 반환
return !url.startsWith(DATA_SCHEME) && Base64ImageCache.get(url) != null
}

View File

@ -947,6 +947,7 @@ class GeckoWeb : BWebview {
"WebtoonContents"-> {
}
"MSG" -> {
context.toast("Received Msg privates form ${lPortMessage.msg}")
}
"SHOWVIEWER" -> {
}