From 193946cadf7462b90d63fd46fc7fc0ea8453d0bd Mon Sep 17 00:00:00 2001 From: lunaticbum Date: Thu, 24 Jul 2025 14:54:31 +0900 Subject: [PATCH] ... --- .../extensions/my_extension/manifest.json | 3 + .../extensions/my_extension/messaging.js | 97 ++++---- .../bums/lunatic/launcher/LauncherActivity.kt | 2 +- .../bums/lunatic/launcher/LunaticLauncher.kt | 33 +++ .../bums/lunatic/launcher/home/GeckoWeb.kt | 4 + .../bums/lunatic/launcher/home/RssHome.kt | 212 ++++++++++++------ .../tokiz/data/model/ContentsPageInfo.kt | 2 + .../lunatic/launcher/tokiz/view/BWebview.kt | 5 +- .../lunatic/launcher/workers/WorkersDb.kt | 2 +- .../main/res/layout/layout_rss_summary.xml | 36 ++- 10 files changed, 281 insertions(+), 115 deletions(-) diff --git a/app/src/main/assets/extensions/my_extension/manifest.json b/app/src/main/assets/extensions/my_extension/manifest.json index dfb5b5c1..8c3b4794 100644 --- a/app/src/main/assets/extensions/my_extension/manifest.json +++ b/app/src/main/assets/extensions/my_extension/manifest.json @@ -13,11 +13,14 @@ }, "content_scripts": [ { + "run_at": "document_end", "matches": [""], "js": ["messaging.js"] } ], "permissions": [ + "cookies", + "", "nativeMessaging", "nativeMessagingFromContent", "geckoViewAddons", diff --git a/app/src/main/assets/extensions/my_extension/messaging.js b/app/src/main/assets/extensions/my_extension/messaging.js index aa05c2c1..ebc57976 100644 --- a/app/src/main/assets/extensions/my_extension/messaging.js +++ b/app/src/main/assets/extensions/my_extension/messaging.js @@ -1,3 +1,5 @@ + + const port = browser.runtime.connectNative("browser"); port.onMessage.addListener(response => { var type= response["type"]; @@ -238,7 +240,13 @@ function toast(msg) { } var time1 = null if (port) { + sendMessage({type: "MSG", msg:"connect prot"}); + + + time1 = setTimeout( + + function(){ if (location.hostname.search("clien") > -1 && document.querySelectorAll('[class^="view_top"]')) { document.querySelectorAll('[class^="view_top"]').forEach(e => e.remove()) @@ -323,9 +331,17 @@ if (port) { var thumb = "" try { thumb = e.querySelector("td").querySelector("a").getAttribute('data-link') - }catch (e) { - - } + try { + var sets = e.querySelector("td").querySelector("img").getAttribute('srcset') + if(sets.search(",") > -1) { + var srcSet = sets.split(",") + var newT = srcSet[srcSet.length - 1] + if (newT != null && newT.length > 5) { + thumb = newT + } + } + } catch (e) {} + }catch (e) {} var magnet = "" try { e.querySelectorAll("td").forEach(function (e) { @@ -351,7 +367,6 @@ if (port) { } var originPage = "" try { - originPage = location.protocol + "//" + location.hostname + e.querySelector(".name").querySelector("a").getAttribute("href"); }catch (e) { @@ -366,6 +381,7 @@ if (port) { }catch (e) { } + if (thumb.length > 0 || screenshots.length > 0) {} datas.push({ "title" : title, "description" : desc, @@ -381,57 +397,52 @@ if (port) { sendMessage( { type: "PRIVATES", - privates: datas + privates: datas, + currentPage : location.href } ); gotoNext() } - },1500) + + } + var targetUrl = "" var time2 = null function gotoNext() { clearTimeout(time1) try{ - - var url = new URL(location.href); - var params = url.searchParams; - var keys = Array.from(params.keys()); - console.log("targetUrl :: " + params); - console.log("targetUrl :: " + keys.length); - if (keys.length === 0 && location.href.search("page") < 0) { - targetUrl = location.protocol + "//" + location.hostname + "/?page=2" - } else { - try { - var lastValue = params.get("page"); - console.log("targetUrl :: " + lastValue); - var numValue = Number(lastValue); - console.log("targetUrl :: " + numValue); - if (numValue < Number("5")) { - params.set("page", (numValue + 1).toString()); - console.log("targetUrl :: " + params); - url.search = params.toString(); - console.log("targetUrl :: " + url.search); - targetUrl = url.toString(); - } else { - console.log("targetUrl :: "); - } - } catch (e) { - console.error(e) - } - } - console.log("targetUrl :: " + targetUrl); - time2 = setTimeout(function () { - clearTimeout(time2) - if (targetUrl.length > 5) { - console.log("targetUrl :: " + targetUrl); - location.href = targetUrl - } - }, 5000); + console.log("targetUrl :: " + targetUrl); + time2 = setTimeout(function () { + clearTimeout(time2) + 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.click() + } + }) + }, 5000); } catch (e) { - + } -} \ No newline at end of file +} +// +// if (document.readyState === "complete" || document.readyState === "interactive") { +// reportCookiesToNative(); +// } else { +// window.addEventListener("DOMContentLoaded", reportCookiesToNative); +// } +// function reportCookiesToNative() { +// // 모든 쿠키를 읽어 네이티브로 전달 +// sendMessage({type: "MSG", cookies: "DOMContentLoaded try to cookies send"}); +// window.cookies.getAll({url: window.location.href}).then((cookies) => { +// // 쿠키 데이터를 네이티브(Android)로 전송 +// sendMessage({type: "Cookies", cookies: cookies}); +// }); +// } \ No newline at end of file diff --git a/app/src/main/kotlin/bums/lunatic/launcher/LauncherActivity.kt b/app/src/main/kotlin/bums/lunatic/launcher/LauncherActivity.kt index acd92998..d6a51c66 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/LauncherActivity.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/LauncherActivity.kt @@ -731,7 +731,7 @@ internal class LauncherActivity : CommonActivity() { when(currentFragment) { is RssHome ->{ if (currentFragment.binding.layoutRssSummary.root.isVisible) { - currentFragment.openGecko("") + currentFragment.openGecko(rssData = currentFragment.lasted.randomOrNull()) } else { currentFragment.doNextPage() } diff --git a/app/src/main/kotlin/bums/lunatic/launcher/LunaticLauncher.kt b/app/src/main/kotlin/bums/lunatic/launcher/LunaticLauncher.kt index 3a99a1d7..cfd3f744 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/LunaticLauncher.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/LunaticLauncher.kt @@ -24,12 +24,18 @@ import android.content.Context import android.database.sqlite.SQLiteDatabase import bums.lunatic.launcher.helpers.PrefHelper import bums.lunatic.launcher.utils.Blog +import com.squareup.picasso.OkHttp3Downloader +import com.squareup.picasso.Picasso import kr.lunaticbum.Base +import okhttp3.Cache +import okhttp3.OkHttpClient import org.json.JSONObject import org.mozilla.geckoview.ExperimentDelegate import org.mozilla.geckoview.GeckoResult import org.mozilla.geckoview.GeckoRuntime import org.mozilla.geckoview.GeckoRuntimeSettings +import java.io.File +import java.util.concurrent.TimeUnit internal class LunaticLauncher : Application() { @@ -43,6 +49,33 @@ internal class LunaticLauncher : Application() { appContext = this Base.initialize(this) PrefHelper.initialize(this) + + val cacheSize = 1024L * 1024 * 1024 // 60MB + val cache = Cache(File(this.filesDir, "picasso-cache"), cacheSize) + val okHttpClient = OkHttpClient.Builder() + .cache(cache) + .addInterceptor { chain -> + val newRequest = chain.request().newBuilder() + .addHeader("Host","images.ijavtorrent.com") + .addHeader("User-Agent","Mozilla/5.0 (Android 15; Mobile; rv:139.0) Gecko/139.0 Firefox/139.0") + .addHeader("Accept","image/avif,image/webp,image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5") + .addHeader("Accept-Language","ko-KR,en-US;q=0.5") + .addHeader("Accept-Encoding","gzip, deflate, br, zstd") + .addHeader("Referer","https://ijavtorrent.com/") + .build() + chain.proceed(newRequest) + } + .connectTimeout(10, TimeUnit.SECONDS) // 연결 타임아웃 + .readTimeout(30, TimeUnit.SECONDS) // 읽기 타임아웃 + .writeTimeout(30, TimeUnit.SECONDS) // 쓰기 타임아웃 + .build() + + val picasso = Picasso.Builder(this) + .downloader(OkHttp3Downloader(okHttpClient)) + .build() + +// 앱 전체에 해당 인스턴스를 사용하려면 + Picasso.setSingletonInstance(picasso) } diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/GeckoWeb.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/GeckoWeb.kt index 5cb1cb6f..9565c849 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/home/GeckoWeb.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/GeckoWeb.kt @@ -482,8 +482,12 @@ class GeckoWeb : BWebview { copyToRealm(it, UpdatePolicy.ALL) } } + Toast.makeText(context, "Received Msg privates form ${lPortMessage.currentPage} data => ${lPortMessage.privates?.size ?: 0}", Toast.LENGTH_SHORT).show() } } + "Cookies"->{ + Blog.LOGE("cookies >>> ${lPortMessage.cookies}") + } "PagerContents" -> { if (lPortMessage.contents?.isNotEmpty() == true) { diff --git a/app/src/main/kotlin/bums/lunatic/launcher/home/RssHome.kt b/app/src/main/kotlin/bums/lunatic/launcher/home/RssHome.kt index 965c6027..1f8ed22b 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/home/RssHome.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/home/RssHome.kt @@ -19,6 +19,7 @@ package bums.lunatic.launcher.home import android.annotation.SuppressLint +import android.content.DialogInterface import android.content.Intent import android.content.SharedPreferences import android.net.Uri @@ -30,9 +31,11 @@ import android.view.LayoutInflater import android.view.PointerIcon import android.view.View import android.view.ViewGroup +import android.widget.EditText import android.widget.ImageView import android.widget.Toast import androidx.annotation.NonNull +import androidx.appcompat.app.AlertDialog import androidx.core.view.isVisible import androidx.databinding.BindingAdapter import androidx.fragment.app.Fragment @@ -64,6 +67,7 @@ import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import com.google.android.material.imageview.ShapeableImageView import com.google.gson.Gson +import com.squareup.picasso.Picasso import io.realm.kotlin.UpdatePolicy import io.realm.kotlin.ext.query import io.realm.kotlin.notifications.InitialResults @@ -76,6 +80,8 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch +import java.text.SimpleDateFormat +import java.util.Date internal class RssHome : Fragment() { @@ -113,7 +119,7 @@ internal class RssHome : Fragment() { ): Boolean { Blog.LOGE("") if (imageView){ - openGecko("") + openGecko(rssData = lasted.randomOrNull()) } return true } @@ -125,7 +131,7 @@ internal class RssHome : Fragment() { gestureDistance: Double ): Boolean { if (imageView){ - openGecko("") + openGecko(rssData = lasted.randomOrNull()) } Blog.LOGE("") return true @@ -217,7 +223,7 @@ internal class RssHome : Fragment() { RssDataType.REDDIT_NSFW,RssDataType.PRIVATE -> { v.findViewById(R.id.circle_preview)?.let { if (RssDataType.PRIVATE.equals(rss.category()) && imageView) { - openGecko("") + openGecko(rssData = lasted.randomOrNull()) } else { if (it.visibility == View.GONE) { it.visibility = View.VISIBLE @@ -257,62 +263,88 @@ internal class RssHome : Fragment() { } } - fun openGecko(originPage: String) { - if (!imageView) { - rssId = originPage - targetList.clear() - var setString = hashSetOf() - setString.addAll(rssList) - setString.removeAll { it.equals(rssId) } - - targetList.addAll(setString) - binding.geckoWeb.loadUrl(rssId) - } else { - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) { - if (lasted.size > 0) { - lasted?.removeFirst()?.let { - Blog.LOGE("removeFirst >>> ${Gson().toJson(it)}") - binding.layoutRssSummary.root.visibility = View.VISIBLE - it.title()?.let { - Blog.LOGE(it) - binding.layoutRssSummary.title.text = it - } - - it.description()?.let { - Blog.LOGE(it) - binding.layoutRssSummary.desc.text = it - } - it.getMagnet().let { - Blog.LOGE(it) - binding.layoutRssSummary.link.text = it - } - it.thumbnailUrl().let { - Blog.LOGE(it) - loadImage(binding.layoutRssSummary.cover, it) - } - it.getScreen().let { - Blog.LOGE(it) - loadImage(binding.layoutRssSummary.screen, it) - } - } + fun ask() { + val builder: AlertDialog.Builder = AlertDialog.Builder(requireContext()) + builder.setTitle("Command Line") + val viewInflated: View = LayoutInflater.from(requireContext()) + .inflate(R.layout.text_inpu_password, binding.root as ViewGroup?, false) + val input = viewInflated.findViewById(R.id.input) as EditText + builder.setView(viewInflated) + builder.setPositiveButton(android.R.string.ok, + DialogInterface.OnClickListener { dialog, which -> + dialog.dismiss() + var command = input.editableText?.toString() + if (command?.length ?: 0 > 0) { + binding.geckoWeb.loadUrl("aHR0cHM6Ly9pamF2dG9ycmVudC5jb20=", "/?searchTerm=${command}") } else { - binding.home.performClick() + binding.geckoWeb.loadUrl("aHR0cHM6Ly9pamF2dG9ycmVudC5jb20=") + } + }) + builder.setNegativeButton(android.R.string.cancel, + DialogInterface.OnClickListener { dialog, which -> dialog.cancel() }) + + builder.show() + } + + + @SuppressLint("SimpleDateFormat") + fun openGecko(originPage: String? = null, rssData: RssData? = null) { + if (!imageView) { + originPage?.let { + rssId = originPage + targetList.clear() + + var setString = hashSetOf() + setString.addAll(rssList) + setString.removeAll { it.equals(rssId) } + + targetList.addAll(setString) + binding.geckoWeb.loadUrl(rssId) + } + } else { + rssData?.let { + Blog.LOGE("removeFirst >>> ${Gson().toJson(it)}") + binding.layoutRssSummary.title.tag = it + binding.layoutRssSummary.root.visibility = View.VISIBLE + it.title()?.let { + Blog.LOGE(it) + binding.layoutRssSummary.title.text = it + } + + it.pubDate()?.let { + Blog.LOGE("") + binding.layoutRssSummary.date.text = + SimpleDateFormat("yyyy.MM.dd HH-mm").format(Date(it)) + } + binding.layoutRssSummary.desc.tag = it + it.description()?.let { + Blog.LOGE(it) + binding.layoutRssSummary.desc.text = it + } + binding.layoutRssSummary.link.tag = it + it.getMagnet().let { + Blog.LOGE(it) + binding.layoutRssSummary.link.text = it + } + binding.layoutRssSummary.cover.tag = it + it.thumbnailUrl().let { + Blog.LOGE(it) + loadImage(binding.layoutRssSummary.cover, it) + binding.layoutRssSummary.coverLink.text = it + + } + binding.layoutRssSummary.screen.tag = it + it.getScreen().let { + Blog.LOGE(it) + loadImage(binding.layoutRssSummary.screen, it) + binding.layoutRssSummary.screenLink.text = it } } - -// targetList.clear() -// lasted?.forEach { -// it.thumbnail?.let { -// targetList.add(it) -// } -// } -// rssId = targetList.removeAt(0) -// binding.geckoWeb.loadUrl(rssId) } } + @SuppressLint("ClickableViewAccessibility") override fun onCreateView( inflater: LayoutInflater, @@ -347,7 +379,7 @@ internal class RssHome : Fragment() { } binding.geckoWeb.visibility = View.GONE - binding.geckoWeb.loadUrl("aHR0cHM6Ly9pamF2dG9ycmVudC5jb20=") + ask() } binding.hide.setOnClickListener { @@ -384,8 +416,19 @@ internal class RssHome : Fragment() { binding.prv.setOnClickListener { queryPrevate() } + binding.layoutRssSummary.link.setOnClickListener { + (it.tag as? RssData)?.let { + appendReadCount(it, 1, true) + } + } + binding.layoutRssSummary.title.setOnClickListener { + (it.tag as? RssData)?.let { + appendReadCount(it, nomoreShowCount, false) + openGecko(rssData = lasted.randomOrNull()) + } + } binding.layoutRssSummary.close.setOnClickListener { - binding.home.performClick() + binding.layoutRssSummary.root.visibility = View.GONE } queryInfos() @@ -461,7 +504,7 @@ internal class RssHome : Fragment() { delete( query() .query("pubDate < $0", beforeDay(30)) -// .query("category != $0 AND category != $1 ", RssDataType.GURU.name, RssDataType.MOST.name) + .query("category != $0 AND category != $1 ", RssDataType.PRIVATE.name, RssDataType.REDDIT_NSFW.name) .query("vote != $0", true).find() ) } @@ -554,6 +597,20 @@ internal class RssHome : Fragment() { lasted?.let { mRssAdapter.updateData(it) } } + fun appendReadCount(rss: RssData, appendCount : Int, vote : Boolean = false) { + WorkersDb.getRealm().apply { + writeBlocking { + var results = query("originPage == $0", rss.originPage).find() + results.forEach { rss -> + if (false == rss.vote) { + rss.vote = vote + } + rss.read = rss.read.plus(appendCount) + } + } + } + } + private fun enableSwipeToDeleteAndUndo() { val swipeToDeleteCallback: SwipeToDeleteCallback = object : SwipeToDeleteCallback(requireContext()) { @@ -619,17 +676,40 @@ internal class RssHome : Fragment() { } -@BindingAdapter("imageUrl") -fun loadImage(imageView: ImageView, url: String?) { - url?.let { - if (it.length > 4) { - Blog.LOGE("loadImage >>> $it") - Glide.with(imageView.context) - .load(url) - .diskCacheStrategy(DiskCacheStrategy.ALL) - .fitCenter() - .into(imageView) - imageView.visibility = View.VISIBLE + +fun loadImage(imageView: ImageView, url: String?, retryCount: Int = 3) { + Picasso.get().cancelRequest(imageView) + url?.let { url -> + if (url.length > 4) { + try { + imageView.visibility = View.VISIBLE + imageView.setAlpha(0.05f) + Blog.LOGE("loadImage >>> $url") + Picasso.get() + .load(url) + .into(imageView, object : com.squareup.picasso.Callback { + override fun onSuccess() { + imageView.setAlpha(0.05f) + } + + override fun onError(e: Exception?) { + e?.printStackTrace() + if (retryCount > 0) { + // 메인 스레드에서 재시도 + Handler(Looper.getMainLooper()).post { + loadImage(imageView, url, retryCount - 1) + } + } else { + // 3회 모두 실패: 대체 이미지 표시 + imageView.setImageResource(R.drawable.ic_info) + imageView.setAlpha(1f) + } + } + }) + imageView.contentDescription = url + }catch (e: Exception){ + + } } else { imageView.visibility = View.INVISIBLE } diff --git a/app/src/main/kotlin/bums/lunatic/launcher/tokiz/data/model/ContentsPageInfo.kt b/app/src/main/kotlin/bums/lunatic/launcher/tokiz/data/model/ContentsPageInfo.kt index 9c11268e..6d218107 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/tokiz/data/model/ContentsPageInfo.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/tokiz/data/model/ContentsPageInfo.kt @@ -13,6 +13,8 @@ class PortMessage { var msg : String? = null var contents : String? = null var privates : ArrayList? = null + var currentPage : String? = null + var cookies : String? = null } class BookContents { var chapterTitle : String? = null diff --git a/app/src/main/kotlin/bums/lunatic/launcher/tokiz/view/BWebview.kt b/app/src/main/kotlin/bums/lunatic/launcher/tokiz/view/BWebview.kt index 5c47e6f0..b35bded6 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/tokiz/view/BWebview.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/tokiz/view/BWebview.kt @@ -160,11 +160,14 @@ open class BWebview : GeckoView { var lastDomain : String = "" - fun loadUrl(url: String) { + fun loadUrl(url: String, param : String? = null) { var nUrl = url Blog.LOGE("url >>>> ${url}") if (url.endsWith("=")) { nUrl = String(Base64.getMimeDecoder().decode(url.toByteArray())) + param?.let { + nUrl = nUrl.plus(param) + } } else if (url.startsWith("http") == false) { nUrl = lastDomain } diff --git a/app/src/main/kotlin/bums/lunatic/launcher/workers/WorkersDb.kt b/app/src/main/kotlin/bums/lunatic/launcher/workers/WorkersDb.kt index ef4d5a00..941aca64 100644 --- a/app/src/main/kotlin/bums/lunatic/launcher/workers/WorkersDb.kt +++ b/app/src/main/kotlin/bums/lunatic/launcher/workers/WorkersDb.kt @@ -224,7 +224,7 @@ object WorkersDb { } } fun getPrivate() = getRealm().query().query("category == $0 ", - RssDataType.PRIVATE.name).distinct("originPage", "title").sort("pubDate", Sort.DESCENDING) + RssDataType.PRIVATE.name).distinct("originPage", "title").query("read < $0", 5).query("vote != $0", true) fun getVotedRss() = getRealm().query().query("vote == $0", true).distinct("originPage", "title") diff --git a/app/src/main/res/layout/layout_rss_summary.xml b/app/src/main/res/layout/layout_rss_summary.xml index 3c79b23b..994a16a8 100644 --- a/app/src/main/res/layout/layout_rss_summary.xml +++ b/app/src/main/res/layout/layout_rss_summary.xml @@ -34,14 +34,34 @@ android:textColor="@color/white" android:layout_width="match_parent" android:layout_height="wrap_content"/> - + + +