diff --git a/src/pt/socialcomics/AndroidManifest.xml b/src/pt/socialcomics/AndroidManifest.xml
deleted file mode 100644
index 30deb7f79..000000000
--- a/src/pt/socialcomics/AndroidManifest.xml
+++ /dev/null
@@ -1,2 +0,0 @@
-
-
diff --git a/src/pt/socialcomics/build.gradle b/src/pt/socialcomics/build.gradle
deleted file mode 100644
index a4e903355..000000000
--- a/src/pt/socialcomics/build.gradle
+++ /dev/null
@@ -1,16 +0,0 @@
-apply plugin: 'com.android.application'
-apply plugin: 'kotlin-android'
-
-ext {
- extName = 'Social Comics'
- pkgNameSuffix = 'pt.socialcomics'
- extClass = '.SocialComics'
- extVersionCode = 2
- libVersion = '1.2'
-}
-
-dependencies {
- implementation project(':lib-ratelimit')
-}
-
-apply from: "$rootDir/common.gradle"
diff --git a/src/pt/socialcomics/res/mipmap-hdpi/ic_launcher.png b/src/pt/socialcomics/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index 841701b3e..000000000
Binary files a/src/pt/socialcomics/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/src/pt/socialcomics/res/mipmap-mdpi/ic_launcher.png b/src/pt/socialcomics/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index 543d3bf03..000000000
Binary files a/src/pt/socialcomics/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/src/pt/socialcomics/res/mipmap-xhdpi/ic_launcher.png b/src/pt/socialcomics/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index ee6bc9fcd..000000000
Binary files a/src/pt/socialcomics/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/pt/socialcomics/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/socialcomics/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index d6f4d5aae..000000000
Binary files a/src/pt/socialcomics/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/pt/socialcomics/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/socialcomics/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index c174c0f5f..000000000
Binary files a/src/pt/socialcomics/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/src/pt/socialcomics/res/web_hi_res_512.png b/src/pt/socialcomics/res/web_hi_res_512.png
deleted file mode 100644
index 360bba46a..000000000
Binary files a/src/pt/socialcomics/res/web_hi_res_512.png and /dev/null differ
diff --git a/src/pt/socialcomics/src/eu/kanade/tachiyomi/extension/pt/socialcomics/SocialComics.kt b/src/pt/socialcomics/src/eu/kanade/tachiyomi/extension/pt/socialcomics/SocialComics.kt
deleted file mode 100644
index bd181d927..000000000
--- a/src/pt/socialcomics/src/eu/kanade/tachiyomi/extension/pt/socialcomics/SocialComics.kt
+++ /dev/null
@@ -1,396 +0,0 @@
-package eu.kanade.tachiyomi.extension.pt.socialcomics
-
-import android.app.Application
-import android.content.SharedPreferences
-import android.text.InputType
-import android.widget.Toast
-import com.github.salomonbrys.kotson.array
-import com.github.salomonbrys.kotson.get
-import com.github.salomonbrys.kotson.int
-import com.github.salomonbrys.kotson.jsonObject
-import com.github.salomonbrys.kotson.obj
-import com.github.salomonbrys.kotson.string
-import com.google.gson.JsonElement
-import com.google.gson.JsonObject
-import com.google.gson.JsonParser
-import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
-import eu.kanade.tachiyomi.network.GET
-import eu.kanade.tachiyomi.network.POST
-import eu.kanade.tachiyomi.source.ConfigurableSource
-import eu.kanade.tachiyomi.source.model.FilterList
-import eu.kanade.tachiyomi.source.model.MangasPage
-import eu.kanade.tachiyomi.source.model.Page
-import eu.kanade.tachiyomi.source.model.SChapter
-import eu.kanade.tachiyomi.source.model.SManga
-import eu.kanade.tachiyomi.source.online.HttpSource
-import okhttp3.Headers
-import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
-import okhttp3.Interceptor
-import okhttp3.MediaType.Companion.toMediaTypeOrNull
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okhttp3.RequestBody
-import okhttp3.RequestBody.Companion.toRequestBody
-import okhttp3.Response
-import rx.Observable
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
-import java.io.IOException
-import java.util.concurrent.TimeUnit
-import androidx.preference.EditTextPreference as AndroidXEditTextPreference
-import androidx.preference.PreferenceScreen as AndroidXPreferenceScreen
-
-class SocialComics : HttpSource(), ConfigurableSource {
-
- override val name = "Social Comics"
-
- override val baseUrl = "https://socialcomics.com.br"
-
- override val lang = "pt-BR"
-
- override val supportsLatest = false
-
- override val client: OkHttpClient = network.cloudflareClient.newBuilder()
- .addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS))
- .addInterceptor(::authIntercept)
- .build()
-
- override fun headersBuilder(): Headers.Builder = Headers.Builder()
- .add("Referer", "$baseUrl/home")
- .add("User-Agent", USER_AGENT)
-
- private fun sourceHeadersBuilder(): Headers.Builder = headersBuilder()
- .add("Accept", ACCEPT_JSON)
- .add("Origin", baseUrl)
-
- private val preferences: SharedPreferences by lazy {
- Injekt.get().getSharedPreferences("source_$id", 0x0000)
- }
-
- private val email: String
- get() = preferences.getString(EMAIL_PREF_KEY, "")!!
-
- private val password: String
- get() = preferences.getString(PASSWORD_PREF_KEY, "")!!
-
- private var apiToken: String
- get() = preferences.getString(API_TOKEN_PREF_KEY, "")!!
- set(value) { preferences.edit().putString(API_TOKEN_PREF_KEY, value).apply() }
-
- private var userHash: String = ""
-
- override fun popularMangaRequest(page: Int): Request {
- val newHeaders = sourceHeadersBuilder()
- .add("Referer", "$baseUrl/home")
- .build()
-
- return GET("$SERVICE_URL/api/mobile/home/list/web", newHeaders)
- }
-
- override fun popularMangaParse(response: Response): MangasPage {
- val result = response.asJson().obj
-
- val freeTrack = result["tracks"].array
- .firstOrNull {
- it.obj["name"].string.contains("Grátis")
- }
-
- if (freeTrack != null) {
- val popularMangas = freeTrack.obj["comics"]["items"].array
- .map { popularMangaItemParse(it.obj) }
-
- return MangasPage(popularMangas, hasNextPage = false)
- }
-
- return MangasPage(emptyList(), hasNextPage = false)
- }
-
- private fun popularMangaItemParse(obj: JsonObject) = SManga.create().apply {
- title = obj["name"].string
- thumbnail_url = obj["thumb"].string
- url = "/quadrinho/${obj["hash"].string}?monetize=${obj["monetize"].int}"
- }
-
- override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
- val newHeaders = sourceHeadersBuilder()
- .add("Referer", "$baseUrl/pesquisa")
- .build()
-
- val endpointUrl = "$SERVICE_URL/api/mobile/search/".toHttpUrlOrNull()!!.newBuilder()
- .addEncodedPathSegment(query)
- .toString()
-
- return GET(endpointUrl, newHeaders)
- }
-
- override fun searchMangaParse(response: Response): MangasPage {
- val result = response.asJson().obj
-
- val searchMangas = result["comics"]["items"].array
- .filter { it.obj["monetize"].int == 0 }
- .map { searchMangaItemParse(it.obj) }
-
- return MangasPage(searchMangas, false)
- }
-
- private fun searchMangaItemParse(obj: JsonObject) = SManga.create().apply {
- title = obj["name"].string
- thumbnail_url = obj["thumb"].string
- url = "/quadrinho/${obj["hash"].string}?monetize=${obj["monetize"].int}"
- }
-
- override fun mangaDetailsRequest(manga: SManga): Request {
- val hash = manga.url
- .substringAfterLast("/")
- .substringBefore("?")
-
- val newHeaders = sourceHeadersBuilder()
- .add("Referer", "$baseUrl/quadrinho/$hash")
- .build()
-
- return GET("$SERVICE_URL/api/mobile/comic/detail/$hash", newHeaders)
- }
-
- override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply {
- val apiResult = response.asJson().obj
-
- title = apiResult["name"].string
- author = apiResult["script"].string
- description = apiResult["description"].string +
- "\n\nEdição: #${apiResult["edition"].int}"
- genre = apiResult["tags"].array
- .joinToString { it.obj["name"].string }
- status = SManga.COMPLETED
- thumbnail_url = apiResult["thumb"].string
-
- if (apiResult["art"].string != "Não Informado") {
- artist = apiResult["art"].string
- }
- }
-
- override fun fetchChapterList(manga: SManga): Observable> {
- if (manga.url.substringAfter("?monetize=") == "1") {
- return Observable.error(Exception(ERROR_PAID_CONTENT))
- }
-
- return super.fetchChapterList(manga)
- }
-
- override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga)
-
- override fun chapterListParse(response: Response): List {
- val apiResult = response.asJson().obj
-
- val chapter = SChapter.create().apply {
- name = "Quadrinho"
- scanlator = apiResult["publisher"]["name"].string
- url = "/leitor/${apiResult["hash"].string}"
- }
-
- return listOf(chapter)
- }
-
- override fun pageListRequest(chapter: SChapter): Request {
- val newHeaders = sourceHeadersBuilder()
- .set("Referer", baseUrl + chapter.url)
- .build()
-
- val hash = chapter.url.substringAfterLast("/")
-
- return GET("$SERVICE_URL/api/mobile/comic/pages/$hash", newHeaders)
- }
-
- override fun pageListParse(response: Response): List {
- val apiResult = response.asJson().obj
- val hash = apiResult["hash"].string
- val comicUrl = "$baseUrl/leitor/$hash"
-
- return apiResult["pages"].array
- .mapIndexed { i, el -> Page(i, comicUrl, el.obj["page"].string) }
- }
-
- override fun fetchImageUrl(page: Page): Observable = Observable.just(page.imageUrl!!)
-
- override fun imageUrlParse(response: Response): String = ""
-
- override fun imageRequest(page: Page): Request {
- val newHeaders = headersBuilder()
- .set("Accept", ACCEPT_IMAGE)
- .add("Referer", page.url)
- .removeAll("Origin")
- .build()
-
- return GET(page.imageUrl!!, newHeaders)
- }
-
- override fun setupPreferenceScreen(screen: AndroidXPreferenceScreen) {
- val emailPref = AndroidXEditTextPreference(screen.context).apply {
- key = EMAIL_PREF_KEY
- title = EMAIL_PREF_TITLE
- setDefaultValue("")
- summary = EMAIL_PREF_SUMMARY
- dialogTitle = EMAIL_PREF_TITLE
-
- setOnPreferenceChangeListener { _, newValue ->
- val res = preferences.edit()
- .putString(EMAIL_PREF_KEY, newValue as String)
- .putString(API_TOKEN_PREF_KEY, "")
- .commit()
-
- Toast.makeText(screen.context, TOAST_RESTART_TO_APPLY, Toast.LENGTH_LONG).show()
- res
- }
- }
- val passwordPref = AndroidXEditTextPreference(screen.context).apply {
- key = PASSWORD_PREF_KEY
- title = PASSWORD_PREF_TITLE
- setDefaultValue("")
- summary = PASSWORD_PREF_SUMMARY
- dialogTitle = PASSWORD_PREF_TITLE
-
- setOnBindEditTextListener {
- it.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
- }
-
- setOnPreferenceChangeListener { _, newValue ->
- val res = preferences.edit()
- .putString(PASSWORD_PREF_KEY, newValue as String)
- .putString(API_TOKEN_PREF_KEY, "")
- .commit()
-
- Toast.makeText(screen.context, TOAST_RESTART_TO_APPLY, Toast.LENGTH_LONG).show()
- res
- }
- }
-
- screen.addPreference(emailPref)
- screen.addPreference(passwordPref)
- }
-
- override fun latestUpdatesRequest(page: Int): Request =
- throw UnsupportedOperationException("Not used")
-
- override fun latestUpdatesParse(response: Response): MangasPage =
- throw UnsupportedOperationException("Not used")
-
- private fun authIntercept(chain: Interceptor.Chain): Response {
- val request = chain.request()
-
- if (request.url.toString().contains(THUMBNAIL_CDN)) {
- return chain.proceed(request)
- }
-
- if (email.isEmpty() || password.isEmpty()) {
- throw IOException(ERROR_CREDENTIALS_MISSING)
- }
-
- // Always do logout at the first time to reset the token.
- if (userHash.isEmpty() && apiToken.isNotEmpty()) {
- doLogout(chain)
- }
-
- if (apiToken.isEmpty()) {
- doLogin(chain)
- }
-
- val authRequest = request.newBuilder()
- .addHeader("Authorization", "Bearer $apiToken")
- .build()
-
- return chain.proceed(authRequest)
- }
-
- private fun doLogin(chain: Interceptor.Chain) {
- val loginPayload = jsonObject(
- "device" to jsonObject(
- "device" to USER_AGENT,
- "device_id" to "window.navigator.userAgent.replace(/\\d+/g, '')",
- "platform" to "web"
- ),
- "email" to email,
- "facebook_id" to null,
- "password" to password
- )
-
- val loginBody = RequestBody.create(JSON_MEDIA_TYPE, loginPayload.toString())
-
- val loginHeaders = sourceHeadersBuilder()
- .add("Content-Type", loginBody.contentType().toString())
- .add("Content-Length", loginBody.contentLength().toString())
- .add("Referer", "$baseUrl/login")
- .build()
-
- val loginRequest = POST("$SERVICE_URL/api/mobile/login", loginHeaders, loginBody)
- val response = chain.proceed(loginRequest)
-
- if (response.code != 200) {
- throw IOException(ERROR_CANNOT_LOGIN)
- }
-
- val apiResult = response.asJson().obj
-
- apiToken = apiResult["api_token"].string
- userHash = apiResult["hash"].string
-
- response.close()
- }
-
- private fun doLogout(chain: Interceptor.Chain) {
- val logoutPayload = jsonObject(
- "device" to jsonObject(
- "device_id" to "window.navigator.userAgent.replace(/\\d+/g, '')"
- ),
- "hash_master" to userHash
- )
-
- val logoutBody = logoutPayload.toString().toRequestBody(JSON_MEDIA_TYPE)
-
- val logoutHeaders = sourceHeadersBuilder()
- .add("Content-Type", logoutBody.contentType().toString())
- .add("Content-Length", logoutBody.contentLength().toString())
- .build()
-
- val logoutRequest = POST("$SERVICE_URL/api/mobile/logout", logoutHeaders, logoutBody)
- val response = chain.proceed(logoutRequest)
-
- if (response.code != 200) {
- throw IOException(ERROR_CANNOT_RENEW_TOKEN)
- }
-
- apiToken = ""
- userHash = ""
-
- response.close()
- }
-
- private fun Response.asJson(): JsonElement = JsonParser.parseString(body!!.string())
-
- companion object {
- private const val SERVICE_URL = "https://service.socialcomics.com.br"
- private const val THUMBNAIL_CDN = "amazonaws.com"
-
- private const val ACCEPT_JSON = "application/json, text/plain, */*"
- private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/*,*/*;q=0.8"
- private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
- "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.128 Safari/537.36"
-
- private const val EMAIL_PREF_KEY = "email"
- private const val EMAIL_PREF_TITLE = "E-mail"
- private const val EMAIL_PREF_SUMMARY = "Defina aqui o seu e-mail para o login."
-
- private const val PASSWORD_PREF_KEY = "password"
- private const val PASSWORD_PREF_TITLE = "Senha"
- private const val PASSWORD_PREF_SUMMARY = "Defina aqui a sua senha para o login."
-
- private const val API_TOKEN_PREF_KEY = "api_token"
-
- private const val ERROR_CANNOT_LOGIN = "Não foi possível realizar o login. Verifique suas informações."
- private const val ERROR_CANNOT_RENEW_TOKEN = "Não foi possível renovar o token de autenticação."
- private const val ERROR_CREDENTIALS_MISSING = "Informações de login incompletas. Revise as configurações."
- private const val ERROR_PAID_CONTENT = "O quadrinho não está disponível no pacote gratuito."
-
- private const val TOAST_RESTART_TO_APPLY = "Reinicie o Tachiyomi para aplicar as novas configurações."
-
- private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull()
- }
-}