..
This commit is contained in:
parent
197d704e27
commit
e9b287cf81
@ -421,31 +421,65 @@ var time1 = null
|
|||||||
function gotoNext() {
|
function gotoNext() {
|
||||||
clearTimeout(time1)
|
clearTimeout(time1)
|
||||||
try {
|
try {
|
||||||
console.log("targetUrl :: " + targetUrl);
|
let attempts = 0; // 시도 횟수를 기록할 변수
|
||||||
if (document.querySelector('[class="btn-group"]')) {
|
const maxAttempts = 8; // 최대 15초 동안 시도
|
||||||
time2 = setTimeout(function () {
|
let currentPage = 1; // 기본 페이지
|
||||||
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
|
|
||||||
|
|
||||||
|
// 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}초] ✅ 다음 페이지 링크를 찾았습니다.`);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if(targetElement !== null) {
|
|
||||||
targetElement.click()
|
|
||||||
} else {
|
|
||||||
// location.href = "https://naver.com"
|
|
||||||
}
|
|
||||||
}, 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,6 +697,9 @@ async function handleCommon() {
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let scrollInterval;
|
||||||
|
|
||||||
|
async function getImg() {
|
||||||
console.log(`Found TEST`);
|
console.log(`Found TEST`);
|
||||||
const imageSelector = 'img[class*="mw-100"][src*="images"]';
|
const imageSelector = 'img[class*="mw-100"][src*="images"]';
|
||||||
const images = Array.from(document.querySelectorAll(imageSelector));
|
const images = Array.from(document.querySelectorAll(imageSelector));
|
||||||
@ -674,9 +711,15 @@ async function handleCommon() {
|
|||||||
|
|
||||||
console.log(`Found ${'$'}{uniqueUrls.length} unique images to cache.`);
|
console.log(`Found ${'$'}{uniqueUrls.length} unique images to cache.`);
|
||||||
|
|
||||||
|
|
||||||
|
for (const image of uniqueUrls) {
|
||||||
|
|
||||||
|
}
|
||||||
// 3. 각 URL을 순회하며 Base64로 변환하고 즉시 네이티브로 전송
|
// 3. 각 URL을 순회하며 Base64로 변환하고 즉시 네이티브로 전송
|
||||||
// (모든 작업을 병렬로 처리하지 않고 순차적(또는 하나씩)으로 보내 메모리 부담을 줄임)
|
// (모든 작업을 병렬로 처리하지 않고 순차적(또는 하나씩)으로 보내 메모리 부담을 줄임)
|
||||||
|
var idx = 0
|
||||||
for (const url of uniqueUrls) {
|
for (const url of uniqueUrls) {
|
||||||
|
try {
|
||||||
const base64Data = await getBase64FromUrl(url);
|
const base64Data = await getBase64FromUrl(url);
|
||||||
if (base64Data) {
|
if (base64Data) {
|
||||||
// 이미지 하나를 성공할 때마다 네이티브로 즉시 전송
|
// 이미지 하나를 성공할 때마다 네이티브로 즉시 전송
|
||||||
@ -686,9 +729,51 @@ async function handleCommon() {
|
|||||||
base64Data: base64Data
|
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")) {
|
else if(location.href.search("javt")) {
|
||||||
console.log(`Found TEST`);
|
console.log(`Found TEST`);
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import android.content.ComponentCallbacks2
|
|||||||
import android.database.sqlite.SQLiteDatabase
|
import android.database.sqlite.SQLiteDatabase
|
||||||
import bums.lunatic.launcher.helpers.HourlyLogWriter
|
import bums.lunatic.launcher.helpers.HourlyLogWriter
|
||||||
import bums.lunatic.launcher.helpers.PrefHelper
|
import bums.lunatic.launcher.helpers.PrefHelper
|
||||||
|
import bums.lunatic.launcher.home.Base64ImageCache
|
||||||
import bums.lunatic.launcher.home.Base64RequestHandler
|
import bums.lunatic.launcher.home.Base64RequestHandler
|
||||||
import bums.lunatic.launcher.utils.Blog
|
import bums.lunatic.launcher.utils.Blog
|
||||||
import com.squareup.picasso.OkHttp3Downloader
|
import com.squareup.picasso.OkHttp3Downloader
|
||||||
@ -45,6 +46,7 @@ internal class LunaticLauncher : Application() {
|
|||||||
appContext = this
|
appContext = this
|
||||||
// Base.initialize(this)
|
// Base.initialize(this)
|
||||||
PrefHelper.initialize(this)
|
PrefHelper.initialize(this)
|
||||||
|
Base64ImageCache.init(this)
|
||||||
val dir = File("/storage/emulated/0/bums_ob/BUM'S PACED /scraped/logs")
|
val dir = File("/storage/emulated/0/bums_ob/BUM'S PACED /scraped/logs")
|
||||||
///BUM'S PACED/pdfs
|
///BUM'S PACED/pdfs
|
||||||
if (!dir.exists()) {
|
if (!dir.exists()) {
|
||||||
|
|||||||
@ -1,15 +1,25 @@
|
|||||||
package bums.lunatic.launcher.home
|
package bums.lunatic.launcher.home
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import androidx.collection.LruCache
|
import androidx.collection.LruCache
|
||||||
import com.squareup.picasso.Picasso
|
import com.squareup.picasso.Picasso
|
||||||
import com.squareup.picasso.Request
|
import com.squareup.picasso.Request
|
||||||
import com.squareup.picasso.RequestHandler
|
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.io.IOException
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.security.MessageDigest
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base64 데이터를 URL 키와 함께 저장하는 메모리 캐시 (Singleton 또는 DI로 관리)
|
* Base64 데이터를 URL 키와 함께 저장하는 2단계 캐시 (메모리 + 파일)
|
||||||
|
* 1. 메모리 캐시(LruCache)를 먼저 확인하여 빠른 접근을 제공합니다.
|
||||||
|
* 2. 메모리에 없는 경우 파일 캐시를 확인하여 디스크 영속성을 지원합니다.
|
||||||
* OOM(메모리 부족)을 피하기 위해 비트맵 자체가 아닌 Base64 문자열을 저장합니다.
|
* OOM(메모리 부족)을 피하기 위해 비트맵 자체가 아닌 Base64 문자열을 저장합니다.
|
||||||
*/
|
*/
|
||||||
object Base64ImageCache {
|
object Base64ImageCache {
|
||||||
@ -17,22 +27,83 @@ object Base64ImageCache {
|
|||||||
private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
|
private val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
|
||||||
private val cacheSize = maxMemory / 8
|
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)
|
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) {
|
fun put(url: String, base64Data: String) {
|
||||||
|
// 1. 메모리 캐시에 저장
|
||||||
if (lru.get(url) == null) {
|
if (lru.get(url) == null) {
|
||||||
lru.put(url, base64Data)
|
lru.put(url, base64Data)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun get(url: String): String? {
|
// 2. 파일 캐시에 비동기적으로 저장
|
||||||
return lru.get(url)
|
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? {
|
||||||
|
// 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에 있는지 확인하는 커스텀 핸들러
|
* Picasso가 요청한 URL이 Base64ImageCache에 있는지 확인하는 커스텀 핸들러
|
||||||
|
* (이 클래스는 변경할 필요가 없습니다)
|
||||||
*/
|
*/
|
||||||
class Base64RequestHandler : RequestHandler() {
|
class Base64RequestHandler : RequestHandler() {
|
||||||
|
|
||||||
@ -44,7 +115,7 @@ class Base64RequestHandler : RequestHandler() {
|
|||||||
*/
|
*/
|
||||||
override fun canHandleRequest(data: Request): Boolean {
|
override fun canHandleRequest(data: Request): Boolean {
|
||||||
val url = data.uri.toString()
|
val url = data.uri.toString()
|
||||||
// data: 스킴이 아니고, 우리 캐시에 URL 키가 존재할 때만 true 반환
|
// data: 스킴이 아니고, 우리 캐시(메모리 또는 파일)에 URL 키가 존재할 때만 true 반환
|
||||||
return !url.startsWith(DATA_SCHEME) && Base64ImageCache.get(url) != null
|
return !url.startsWith(DATA_SCHEME) && Base64ImageCache.get(url) != null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -947,6 +947,7 @@ class GeckoWeb : BWebview {
|
|||||||
"WebtoonContents"-> {
|
"WebtoonContents"-> {
|
||||||
}
|
}
|
||||||
"MSG" -> {
|
"MSG" -> {
|
||||||
|
context.toast("Received Msg privates form ${lPortMessage.msg}")
|
||||||
}
|
}
|
||||||
"SHOWVIEWER" -> {
|
"SHOWVIEWER" -> {
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user