From 008a7040d530d91252ef1179235cce5801cf3a25 Mon Sep 17 00:00:00 2001
From: contusionglory <102427829+contusionglory@users.noreply.github.com>
Date: Fri, 23 Dec 2022 23:36:28 +0000
Subject: [PATCH] Added SkillShare.com
---
SkillShareProvider/build.gradle.kts | 25 +++
.../src/main/AndroidManifest.xml | 2 +
.../com/lagradost/SkillShareProvider.kt | 204 ++++++++++++++++++
.../com/lagradost/SkillShareProviderPlugin.kt | 14 ++
4 files changed, 245 insertions(+)
create mode 100644 SkillShareProvider/build.gradle.kts
create mode 100644 SkillShareProvider/src/main/AndroidManifest.xml
create mode 100644 SkillShareProvider/src/main/kotlin/com/lagradost/SkillShareProvider.kt
create mode 100644 SkillShareProvider/src/main/kotlin/com/lagradost/SkillShareProviderPlugin.kt
diff --git a/SkillShareProvider/build.gradle.kts b/SkillShareProvider/build.gradle.kts
new file mode 100644
index 0000000..6105bdd
--- /dev/null
+++ b/SkillShareProvider/build.gradle.kts
@@ -0,0 +1,25 @@
+// use an integer for version numbers
+version = 1
+
+
+cloudstream {
+ // All of these properties are optional, you can safely remove them
+
+ // description = "Lorem Ipsum"
+ language= "it"
+ authors = listOf("Forthe")
+
+ /**
+ * Status int as the following:
+ * 0: Down
+ * 1: Ok
+ * 2: Slow
+ * 3: Beta only
+ * */
+ status = 1 // will be 3 if unspecified
+ tvTypes = listOf(
+ "TvSeries",
+ )
+
+ iconUrl = "https://www.google.com/s2/favicons?domain=skillshare.com&sz=%size%"
+}
diff --git a/SkillShareProvider/src/main/AndroidManifest.xml b/SkillShareProvider/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..29aec9d
--- /dev/null
+++ b/SkillShareProvider/src/main/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/SkillShareProvider/src/main/kotlin/com/lagradost/SkillShareProvider.kt b/SkillShareProvider/src/main/kotlin/com/lagradost/SkillShareProvider.kt
new file mode 100644
index 0000000..97c0a02
--- /dev/null
+++ b/SkillShareProvider/src/main/kotlin/com/lagradost/SkillShareProvider.kt
@@ -0,0 +1,204 @@
+package com.lagradost
+
+import com.fasterxml.jackson.annotation.JsonProperty
+import com.lagradost.cloudstream3.*
+import com.lagradost.cloudstream3.MainAPI
+import com.lagradost.cloudstream3.SearchResponse
+import com.lagradost.cloudstream3.TvType
+import com.lagradost.cloudstream3.utils.AppUtils.parseJson
+import com.lagradost.cloudstream3.utils.AppUtils.toJson
+import com.lagradost.cloudstream3.utils.ExtractorLink
+import com.lagradost.cloudstream3.utils.Qualities
+import okhttp3.MediaType.Companion.toMediaType
+import okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.RequestBody.Companion.toRequestBody
+
+class SkillShareProvider : MainAPI() { // all providers must be an instance of MainAPI
+ override var mainUrl = "https://www.skillshare.com"
+ override var name = "SkillShare"
+
+ val apiUrl = "https://www.skillshare.com/api/graphql"
+ val bypassApiUrl = "https://skillshare-api.heckernohecking.repl.co"
+
+ override val supportedTypes = setOf(TvType.TvSeries)
+ override val hasChromecastSupport = true
+ override var lang = "en"
+ override val hasMainPage = true
+ val client = OkHttpClient()
+ var cursor = ""
+
+ override val mainPage =
+ mainPageOf(
+ "SIX_MONTHS_ENGAGEMENT" to "Popular Classes",
+ "ML_TRENDINESS" to "Trending Classes",
+ )
+
+ override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse {
+ val sortAttribute = request.data
+ val payload =
+ """{ "query": "query GetClassesByType(${'$'}filter: ClassFilters!, ${'$'}pageSize: Int, ${'$'}cursor: String, ${'$'}type: ClassListType!, ${'$'}sortAttribute: ClassListByTypeSortAttribute) { classListByType(type: ${'$'}type, where: ${'$'}filter, first: ${'$'}pageSize, after: ${'$'}cursor, sortAttribute: ${'$'}sortAttribute) { nodes { id title url sku smallCoverUrl largeCoverUrl studentCount durationInSeconds __typename } } }", "variables": { "type": "TRENDING_CLASSES", "filter": { "subCategory": "", "classLength": [] }, "pageSize": 30, "cursor": "$cursor", "sortAttribute": "$sortAttribute" }, "operationName": "GetClassesByType" }"""
+ val body = payload.toRequestBody("application/json".toMediaType())
+ val r = Request.Builder()
+ .url(apiUrl)
+ .post(body)
+ .build()
+
+ val response = client.newCall(r).execute()
+ val responseBody = response.body.string()
+ val parsedJson = parseJson(responseBody).data!!.classListByType!!.nodes
+ val home = parsedJson.map {
+ it.toSearchResult()
+ }
+ home.let {
+ cursor =
+ parsedJson.last().id.toString()
+ }
+ return newHomePageResponse(
+ arrayListOf(HomePageList(request.name, home, isHorizontalImages = true)),
+ hasNext = home.isNotEmpty(),
+ )
+ }
+
+ override suspend fun search(query: String): List {
+ val payload =
+ """{"query":"fragment ClassFields on Class {\n id\n\tsmallCoverUrl\n largeCoverUrl\n sku\n title\n url\n}\n\nquery GetClassesQuery(${"$"}query: String!, ${"$"}where: SearchFilters!, ${"$"}after: String!, ${"$"}first: Int!) {\n search(query: ${"$"}query, where: ${"$"}where, analyticsTags: [\"src:browser\", \"src:browser:search\"], after: ${"$"}after, first: ${"$"}first) {\n edges {\n node {\n ...ClassFields\n }\n }\n }\n}\n","variables":{"query":"$query","where":{"level":["ALL_LEVELS","BEGINNER","INTERMEDIATE","ADVANCED"]},"after":"-1","first":30},"operationName":"GetClassesQuery"}"""
+ val body = payload.toRequestBody("application/json".toMediaType())
+ val r = Request.Builder()
+ .url(apiUrl)
+ .post(body)
+ .build()
+ val response = client.newCall(r).execute()
+ val responseBody = response.body.string()
+ val home = parseJson(responseBody).data!!.search!!.edges.map {
+ it.node!!.toSearchResult()
+ }
+ return home
+ }
+
+ private fun ApiNode.toSearchResult(): SearchResponse {
+ val title = this.title ?: ""
+ val posterUrl = this.smallCoverUrl
+ return newTvSeriesSearchResponse(
+ title,
+ Data(
+ title = this.title,
+ courseId = this.courseId,
+ largeCoverUrl = this.largeCoverUrl
+ ).toJson(),
+ TvType.TvSeries
+ ) {
+ addPoster(posterUrl)
+ }
+ }
+
+
+ override suspend fun load(url: String): LoadResponse {
+ val data = parseJson(url)
+ val document = app.get(bypassApiUrl + "/${data.courseId}/0")
+ .parsedSafe() ?: throw ErrorLoadingException("Invalid Json Response")
+ val title = data.title ?: ""
+ val poster = data.largeCoverUrl
+ val episodeList = document.lessons.mapIndexed() { index, episode ->
+ Episode(episode.url ?: "", episode.title, 1, index)
+ }
+
+ return newTvSeriesLoadResponse(title, mainUrl, TvType.TvSeries, episodeList) {
+ addPoster(poster)
+ }
+ }
+
+ override suspend fun loadLinks(
+ data: String,
+ isCasting: Boolean,
+ subtitleCallback: (SubtitleFile) -> Unit,
+ callback: (ExtractorLink) -> Unit
+ ): Boolean {
+ callback.invoke(
+ ExtractorLink(
+ name,
+ name,
+ data,
+ isM3u8 = false,
+ referer = mainUrl,
+ quality = Qualities.Unknown.value
+ )
+ )
+ return true
+ }
+
+
+ data class ApiNode(
+ //mainpage and search page
+ @JsonProperty("id") var id: String? = null,
+ @JsonProperty("title") var title: String? = null,
+ @JsonProperty("url") var url: String? = null,
+ @JsonProperty("sku") var courseId: String? = null,
+ @JsonProperty("smallCoverUrl") var smallCoverUrl: String? = null,
+ @JsonProperty("largeCoverUrl") var largeCoverUrl: String? = null,
+ )
+
+ data class ApiNodes( //mainpage
+
+ @JsonProperty("nodes") var nodes: ArrayList = arrayListOf()
+
+ )
+
+ data class ApiClassListByType( //mainpage
+
+ @JsonProperty("classListByType") var classListByType: ApiNodes? = ApiNodes()
+
+ )
+
+ data class ApiData( //mainpage
+
+ @JsonProperty("data") var data: ApiClassListByType? = ApiClassListByType()
+
+ )
+
+ data class SearchApiNodes( //search
+
+ @JsonProperty("node") var node: ApiNode? = ApiNode()
+
+ )
+
+ data class SearchApiEdges( //search
+
+ @JsonProperty("edges") var edges: ArrayList = arrayListOf()
+
+ )
+
+ data class SearchApiSearch( //search
+
+ @JsonProperty("search") var search: SearchApiEdges? = SearchApiEdges()
+
+ )
+
+ data class SearchApiData( //search
+
+ @JsonProperty("data") var data: SearchApiSearch? = SearchApiSearch()
+
+ )
+
+ data class BypassApiLesson( //bypass
+
+ @JsonProperty("title") var title: String? = null,
+ @JsonProperty("url") var url: String? = null
+
+ )
+
+ data class BypassApiData( //bypass
+
+ @JsonProperty("class") var title: String? = null,
+ @JsonProperty("class_thumbnail") var largeCoverUrl: String? = null,
+ @JsonProperty("lessons") var lessons: ArrayList = arrayListOf()
+
+ )
+
+ data class Data(
+ //for loading
+ val title: String? = null,
+ val courseId: String? = null,
+ val largeCoverUrl: String? = null,
+ )
+}
\ No newline at end of file
diff --git a/SkillShareProvider/src/main/kotlin/com/lagradost/SkillShareProviderPlugin.kt b/SkillShareProvider/src/main/kotlin/com/lagradost/SkillShareProviderPlugin.kt
new file mode 100644
index 0000000..2bc844e
--- /dev/null
+++ b/SkillShareProvider/src/main/kotlin/com/lagradost/SkillShareProviderPlugin.kt
@@ -0,0 +1,14 @@
+package com.lagradost
+
+import android.content.Context
+import com.lagradost.cloudstream3.plugins.CloudstreamPlugin
+import com.lagradost.cloudstream3.plugins.Plugin
+
+@CloudstreamPlugin
+class SkillShareProviderPlugin : Plugin() {
+ override fun load(context: Context) {
+ // All providers should be added in this manner. Please don't edit the providers list
+ // directly.
+ registerMainAPI(SkillShareProvider())
+ }
+}