Skip to content
This repository was archived by the owner on Jan 12, 2024. It is now read-only.

Commit c5dae82

Browse files
author
miguelantonioe
authored
New extensions added (#5)
* xnxx extension added * EnNovelas extension added * fixes
1 parent f3db924 commit c5dae82

File tree

16 files changed

+386
-0
lines changed

16 files changed

+386
-0
lines changed

src/all/xnxx/AndroidManifest.xml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest package="eu.kanade.tachiyomi.animeextension" />

src/all/xnxx/build.gradle

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
apply plugin: 'com.android.application'
2+
apply plugin: 'kotlin-android'
3+
4+
ext {
5+
extName = 'Xnxx'
6+
pkgNameSuffix = 'all.xnxx'
7+
extClass = '.Xnxx'
8+
extVersionCode = 1
9+
libVersion = '13'
10+
containsNsfw = true
11+
}
12+
13+
apply from: "$rootDir/common.gradle"
6.12 KB
Loading
3.39 KB
Loading
9.69 KB
Loading
18.2 KB
Loading
27.6 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
package eu.kanade.tachiyomi.animeextension.all.xnxx
2+
3+
import android.app.Application
4+
import android.content.SharedPreferences
5+
import androidx.preference.ListPreference
6+
import androidx.preference.PreferenceScreen
7+
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
8+
import eu.kanade.tachiyomi.animesource.model.AnimeFilter
9+
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
10+
import eu.kanade.tachiyomi.animesource.model.SAnime
11+
import eu.kanade.tachiyomi.animesource.model.SEpisode
12+
import eu.kanade.tachiyomi.animesource.model.Video
13+
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
14+
import eu.kanade.tachiyomi.network.GET
15+
import eu.kanade.tachiyomi.util.asJsoup
16+
import okhttp3.OkHttpClient
17+
import okhttp3.Request
18+
import okhttp3.Response
19+
import org.jsoup.nodes.Document
20+
import org.jsoup.nodes.Element
21+
import uy.kohesive.injekt.Injekt
22+
import uy.kohesive.injekt.api.get
23+
import java.lang.Exception
24+
25+
class Xnxx : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
26+
27+
override val name = "Xnxx"
28+
29+
override val baseUrl = "https://www.xnxx.com"
30+
31+
override val lang = "all"
32+
33+
override val supportsLatest = false
34+
35+
override val client: OkHttpClient = network.cloudflareClient
36+
37+
private val preferences: SharedPreferences by lazy {
38+
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
39+
}
40+
41+
override fun popularAnimeSelector(): String = "div[id*='video_'].thumb-block"
42+
43+
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/hits/$page")
44+
45+
override fun popularAnimeFromElement(element: Element): SAnime {
46+
val anime = SAnime.create()
47+
anime.setUrlWithoutDomain("$baseUrl${element.select("div.thumb-inside div.thumb > a").attr("href")}")
48+
anime.title = element.select("div.thumb-under > p > a").text()
49+
anime.thumbnail_url = element.select("div.thumb-inside div.thumb img[id*='pic_']").attr("data-src")
50+
return anime
51+
}
52+
53+
override fun popularAnimeNextPageSelector(): String = "#content-thumbs div.pagination ul li a.next"
54+
55+
override fun episodeListParse(response: Response): List<SEpisode> {
56+
val episodes = mutableListOf<SEpisode>()
57+
val episode = SEpisode.create().apply {
58+
name = "Video"
59+
setUrlWithoutDomain(response.request.url.toString())
60+
date_upload = System.currentTimeMillis()
61+
}
62+
episodes.add(episode)
63+
return episodes
64+
}
65+
66+
override fun episodeListSelector() = throw Exception("not used")
67+
68+
override fun episodeFromElement(element: Element) = throw Exception("not used")
69+
70+
override fun videoListParse(response: Response): List<Video> {
71+
val document = response.asJsoup()
72+
val sourcesJson = document.select("script:containsData(html5player.setVideoUrl)").toString()
73+
val lowQuality = sourcesJson.substringAfter("VideoUrlLow('").substringBefore("')")
74+
val hlsQuality = sourcesJson.substringAfter("setVideoHLS('").substringBefore("')")
75+
val highQuality = sourcesJson.substringAfter("VideoUrlHigh('").substringBefore("')")
76+
return listOf(
77+
Video(lowQuality, "Low", lowQuality),
78+
Video(hlsQuality, "HLS", hlsQuality),
79+
Video(highQuality, "High", highQuality)
80+
)
81+
}
82+
83+
override fun videoListSelector() = throw Exception("not used")
84+
85+
override fun videoUrlParse(document: Document) = throw Exception("not used")
86+
87+
override fun videoFromElement(element: Element) = throw Exception("not used")
88+
89+
override fun List<Video>.sort(): List<Video> {
90+
val quality = preferences.getString("preferred_quality", "HLS")
91+
if (quality != null) {
92+
val newList = mutableListOf<Video>()
93+
var preferred = 0
94+
for (video in this) {
95+
if (video.quality == quality) {
96+
newList.add(preferred, video)
97+
preferred++
98+
} else {
99+
newList.add(video)
100+
}
101+
}
102+
return newList
103+
}
104+
return this
105+
}
106+
107+
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
108+
val tagFilter = filters.find { it is Tags } as Tags
109+
val calPage = page - 1
110+
return when {
111+
query.isNotBlank() -> GET("$baseUrl/search/$query/$calPage", headers)
112+
tagFilter.state.isNotBlank() -> GET("$baseUrl/search/${tagFilter.state}/$calPage")
113+
else -> popularAnimeRequest(calPage)
114+
}
115+
}
116+
override fun searchAnimeFromElement(element: Element): SAnime {
117+
return popularAnimeFromElement(element)
118+
}
119+
120+
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
121+
122+
override fun searchAnimeSelector(): String = popularAnimeSelector()
123+
124+
override fun animeDetailsParse(document: Document): SAnime {
125+
val anime = SAnime.create()
126+
anime.title = document.select("#video-content-metadata > div.clear-infobar > strong").text()
127+
anime.description = document.select("#video-content-metadata > p").text().replace("\n", "")
128+
anime.genre = document.select("#video-content-metadata > div.metadata-row.video-tags > a").joinToString { it.text() }
129+
anime.status = SAnime.COMPLETED
130+
return anime
131+
}
132+
133+
override fun latestUpdatesNextPageSelector() = throw Exception("not used")
134+
135+
override fun latestUpdatesFromElement(element: Element) = throw Exception("not used")
136+
137+
override fun latestUpdatesRequest(page: Int) = throw Exception("not used")
138+
139+
override fun latestUpdatesSelector() = throw Exception("not used")
140+
141+
override fun getFilterList(): AnimeFilterList = AnimeFilterList(
142+
AnimeFilter.Header("Search by text does not affect the filter"),
143+
Tags("Tag")
144+
)
145+
146+
internal class Tags(name: String) : AnimeFilter.Text(name)
147+
148+
override fun setupPreferenceScreen(screen: PreferenceScreen) {
149+
val videoQualityPref = ListPreference(screen.context).apply {
150+
key = "preferred_quality"
151+
title = "Preferred quality"
152+
entries = arrayOf("High", "Low", "HLS")
153+
entryValues = arrayOf("High", "Low", "HLS")
154+
setDefaultValue("HLS")
155+
summary = "%s"
156+
157+
setOnPreferenceChangeListener { _, newValue ->
158+
val selected = newValue as String
159+
val index = findIndexOfValue(selected)
160+
val entry = entryValues[index] as String
161+
preferences.edit().putString(key, entry).commit()
162+
}
163+
}
164+
screen.addPreference(videoQualityPref)
165+
}
166+
}

src/es/ennovelas/AndroidManifest.xml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest package="eu.kanade.tachiyomi.animeextension" />

src/es/ennovelas/build.gradle

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
apply plugin: 'com.android.application'
2+
apply plugin: 'kotlin-android'
3+
4+
ext {
5+
extName = 'EnNovelas'
6+
pkgNameSuffix = 'es.ennovelas'
7+
extClass = '.EnNovelas'
8+
extVersionCode = 1
9+
libVersion = '13'
10+
}
11+
12+
apply from: "$rootDir/common.gradle"
7.24 KB
Loading
4.14 KB
Loading
Loading
Loading
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package eu.kanade.tachiyomi.animeextension.es.ennovelas
2+
3+
import android.app.Application
4+
import android.content.SharedPreferences
5+
import androidx.preference.ListPreference
6+
import androidx.preference.PreferenceScreen
7+
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
8+
import eu.kanade.tachiyomi.animesource.model.AnimeFilterList
9+
import eu.kanade.tachiyomi.animesource.model.SAnime
10+
import eu.kanade.tachiyomi.animesource.model.SEpisode
11+
import eu.kanade.tachiyomi.animesource.model.Video
12+
import eu.kanade.tachiyomi.animesource.online.ParsedAnimeHttpSource
13+
import eu.kanade.tachiyomi.network.GET
14+
import eu.kanade.tachiyomi.util.asJsoup
15+
import okhttp3.OkHttpClient
16+
import okhttp3.Request
17+
import okhttp3.Response
18+
import org.jsoup.nodes.Document
19+
import org.jsoup.nodes.Element
20+
import uy.kohesive.injekt.Injekt
21+
import uy.kohesive.injekt.api.get
22+
import java.lang.Exception
23+
import java.text.Normalizer
24+
25+
class EnNovelas : ConfigurableAnimeSource, ParsedAnimeHttpSource() {
26+
27+
override val name = "EnNovelas"
28+
29+
override val baseUrl = "https://www.ennovelas.com"
30+
31+
override val lang = "es"
32+
33+
override val supportsLatest = false
34+
35+
override val client: OkHttpClient = network.cloudflareClient
36+
37+
private val preferences: SharedPreferences by lazy {
38+
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
39+
}
40+
41+
override fun popularAnimeSelector(): String = "#container section.search-videos div.section-content div.row div div.col-xs-6 div.video-post"
42+
43+
override fun popularAnimeRequest(page: Int): Request = GET("$baseUrl/?op=categories_all&per_page=60&page=$page")
44+
45+
override fun popularAnimeFromElement(element: Element): SAnime {
46+
val anime = SAnime.create()
47+
anime.setUrlWithoutDomain(changeUrlFormat(element.select("a").attr("href")))
48+
anime.title = element.select("a p").text()
49+
anime.thumbnail_url = element.select("a div.thumb").attr("style")
50+
.substringAfter("background-image:url(").substringBefore(")")
51+
anime.description = ""
52+
return anime
53+
}
54+
55+
private fun changeUrlFormat(link: String): String {
56+
val novel = link.substringAfter("/category/").replace("+", "%20")
57+
return "$baseUrl/?cat_name=$novel&op=search&per_page=all"
58+
}
59+
60+
override fun popularAnimeNextPageSelector(): String = "#container section div.section-content div.paging a:last-of-type"
61+
62+
override fun episodeListParse(response: Response): List<SEpisode> {
63+
val document = response.asJsoup()
64+
val episodeList = mutableListOf<SEpisode>()
65+
document.select("#col3 div.videobox").forEach { element ->
66+
val ep = SEpisode.create()
67+
val noEpisode = getNumberFromEpsString(
68+
element.selectFirst("a:nth-child(2)").text().substringAfter("Cap")
69+
.substringBefore("FIN").substringBefore("fin")
70+
)
71+
ep.setUrlWithoutDomain(element.selectFirst("a.video200").attr("href"))
72+
ep.name = "Cap" + element.selectFirst("a:nth-child(2)").text().substringAfter("Cap")
73+
ep.episode_number = noEpisode.toFloat()
74+
episodeList.add(ep)
75+
}
76+
return episodeList.sortedByDescending { x -> x.episode_number }
77+
}
78+
79+
override fun episodeListSelector() = "uwu"
80+
81+
override fun episodeFromElement(element: Element) = throw Exception("not used")
82+
83+
private fun getNumberFromEpsString(epsStr: String): String {
84+
return epsStr.filter { it.isDigit() }
85+
}
86+
87+
override fun videoListParse(response: Response): List<Video> {
88+
val document = response.asJsoup()
89+
val videoList = mutableListOf<Video>()
90+
document.select("script").forEach { script ->
91+
if (script.data().contains("window.hola_player({")) {
92+
val url = script.data().substringAfter("sources: [{src: \"").substringBefore("\",")
93+
val quality = script.data().substringAfter("res: ").substringBefore(",")
94+
videoList.add(Video(url, "${quality}p", url, headers = null))
95+
}
96+
}
97+
return videoList
98+
}
99+
100+
override fun videoListSelector() = throw Exception("not used")
101+
102+
override fun videoUrlParse(document: Document) = throw Exception("not used")
103+
104+
override fun videoFromElement(element: Element) = throw Exception("not used")
105+
106+
override fun List<Video>.sort(): List<Video> {
107+
val quality = preferences.getString("preferred_quality", "720p")
108+
if (quality != null) {
109+
val newList = mutableListOf<Video>()
110+
var preferred = 0
111+
for (video in this) {
112+
if (video.quality == quality) {
113+
newList.add(preferred, video)
114+
preferred++
115+
} else {
116+
newList.add(video)
117+
}
118+
}
119+
return newList
120+
}
121+
return this
122+
}
123+
124+
override fun searchAnimeRequest(page: Int, query: String, filters: AnimeFilterList): Request {
125+
return when {
126+
query.isNotBlank() -> GET("$baseUrl/?name=$query&op=categories_all&page=$page")
127+
else -> popularAnimeRequest(page)
128+
}
129+
}
130+
131+
override fun searchAnimeFromElement(element: Element): SAnime {
132+
return popularAnimeFromElement(element)
133+
}
134+
135+
override fun searchAnimeNextPageSelector(): String = popularAnimeNextPageSelector()
136+
137+
override fun searchAnimeSelector(): String = popularAnimeSelector()
138+
139+
override fun animeDetailsParse(document: Document): SAnime {
140+
val descriptionElement = document.selectFirst("#inwg").text()
141+
.substringAfter("Notifications:")
142+
.substringBefore("Capitulo")
143+
.substringBefore("Capítulo")
144+
val anime = SAnime.create()
145+
val title = document.selectFirst("#inwg h3 span.first-word").text()
146+
val idx = idxDescription(descriptionElement, title)
147+
val description = descriptionElement.substring(0, idx).trim()
148+
149+
anime.title = title.trim()
150+
anime.description = description.ifEmpty { title }
151+
anime.genre = "novela"
152+
anime.status = SAnime.UNKNOWN
153+
return anime
154+
}
155+
156+
private fun String.removeNonSpacingMarks() = Normalizer.normalize(this, Normalizer.Form.NFD).replace("\\p{Mn}+".toRegex(), "")
157+
158+
private fun idxDescription(text: String, title: String): Int {
159+
val descriptionLowercase = text.lowercase().removeNonSpacingMarks()
160+
val titleLowercase = title.lowercase().removeNonSpacingMarks()
161+
val idx = descriptionLowercase.lastIndexOf(titleLowercase)
162+
return if (idx == -1) descriptionLowercase.length - 1 else idx
163+
}
164+
165+
override fun latestUpdatesNextPageSelector() = popularAnimeNextPageSelector()
166+
167+
override fun latestUpdatesFromElement(element: Element) = popularAnimeFromElement(element)
168+
169+
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/browse?order=added&page=$page")
170+
171+
override fun latestUpdatesSelector() = popularAnimeSelector()
172+
173+
override fun setupPreferenceScreen(screen: PreferenceScreen) {
174+
val videoQualityPref = ListPreference(screen.context).apply {
175+
key = "preferred_quality"
176+
title = "Preferred quality"
177+
entries = arrayOf("1080p", "720p", "480p")
178+
entryValues = arrayOf("1080p", "720p", "480p")
179+
setDefaultValue("720p")
180+
summary = "%s"
181+
182+
setOnPreferenceChangeListener { _, newValue ->
183+
val selected = newValue as String
184+
val index = findIndexOfValue(selected)
185+
val entry = entryValues[index] as String
186+
preferences.edit().putString(key, entry).commit()
187+
}
188+
}
189+
screen.addPreference(videoQualityPref)
190+
}
191+
}

0 commit comments

Comments
 (0)