diff --git a/app/schemas/com.deniscerri.ytdlnis.database.DBManager/3.json b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/3.json
new file mode 100644
index 00000000..f4ce367e
--- /dev/null
+++ b/app/schemas/com.deniscerri.ytdlnis.database.DBManager/3.json
@@ -0,0 +1,421 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 3,
+ "identityHash": "f911bed4915605d5b09bfd82892e2d7a",
+ "entities": [
+ {
+ "tableName": "results",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `website` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `formats` TEXT NOT NULL, `creationTime` INTEGER NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "author",
+ "columnName": "author",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "duration",
+ "columnName": "duration",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "thumb",
+ "columnName": "thumb",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "website",
+ "columnName": "website",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "playlistTitle",
+ "columnName": "playlistTitle",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "formats",
+ "columnName": "formats",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "creationTime",
+ "columnName": "creationTime",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "history",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `duration` TEXT NOT NULL, `thumb` TEXT NOT NULL, `type` TEXT NOT NULL, `time` INTEGER NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `format` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "author",
+ "columnName": "author",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "duration",
+ "columnName": "duration",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "thumb",
+ "columnName": "thumb",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "time",
+ "columnName": "time",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "downloadPath",
+ "columnName": "downloadPath",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "website",
+ "columnName": "website",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "format",
+ "columnName": "format",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "downloads",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `title` TEXT NOT NULL, `author` TEXT NOT NULL, `thumb` TEXT NOT NULL, `duration` TEXT NOT NULL, `type` TEXT NOT NULL, `format` TEXT NOT NULL, `downloadSections` TEXT NOT NULL DEFAULT '', `allFormats` TEXT NOT NULL, `downloadPath` TEXT NOT NULL, `website` TEXT NOT NULL, `downloadSize` TEXT NOT NULL, `playlistTitle` TEXT NOT NULL, `audioPreferences` TEXT NOT NULL, `videoPreferences` TEXT NOT NULL, `customFileNameTemplate` TEXT NOT NULL, `SaveThumb` INTEGER NOT NULL, `status` TEXT NOT NULL DEFAULT 'Queued', `downloadStartTime` INTEGER NOT NULL DEFAULT 0)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "author",
+ "columnName": "author",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "thumb",
+ "columnName": "thumb",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "duration",
+ "columnName": "duration",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "format",
+ "columnName": "format",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "downloadSections",
+ "columnName": "downloadSections",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "''"
+ },
+ {
+ "fieldPath": "allFormats",
+ "columnName": "allFormats",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "downloadPath",
+ "columnName": "downloadPath",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "website",
+ "columnName": "website",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "downloadSize",
+ "columnName": "downloadSize",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "playlistTitle",
+ "columnName": "playlistTitle",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "audioPreferences",
+ "columnName": "audioPreferences",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "videoPreferences",
+ "columnName": "videoPreferences",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "customFileNameTemplate",
+ "columnName": "customFileNameTemplate",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "SaveThumb",
+ "columnName": "SaveThumb",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "status",
+ "columnName": "status",
+ "affinity": "TEXT",
+ "notNull": true,
+ "defaultValue": "'Queued'"
+ },
+ {
+ "fieldPath": "downloadStartTime",
+ "columnName": "downloadStartTime",
+ "affinity": "INTEGER",
+ "notNull": true,
+ "defaultValue": "0"
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "commandTemplates",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `content` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "title",
+ "columnName": "title",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "searchHistory",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `query` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "query",
+ "columnName": "query",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "templateShortcuts",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `content` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "cookies",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `url` TEXT NOT NULL, `content` TEXT NOT NULL)",
+ "fields": [
+ {
+ "fieldPath": "id",
+ "columnName": "id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "content",
+ "columnName": "content",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "autoGenerate": true,
+ "columnNames": [
+ "id"
+ ]
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f911bed4915605d5b09bfd82892e2d7a')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index c700f689..5cccc364 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -144,6 +144,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
(AsyncDifferConfig.Builder(DIFF_CALLBACK).build()) {
+ private val onItemClickListener: OnItemClickListener
+ private val activity: Activity
+
+ init {
+ this.onItemClickListener = onItemClickListener
+ this.activity = activity
+ }
+
+ class ViewHolder(itemView: View, onItemClickListener: OnItemClickListener?) : RecyclerView.ViewHolder(itemView) {
+ val item: ConstraintLayout
+
+ init {
+ item = itemView.findViewById(R.id.command_template_item_constraint)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val cardView = LayoutInflater.from(parent.context)
+ .inflate(R.layout.command_template_item, parent, false)
+ return ViewHolder(cardView, onItemClickListener)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val item = getItem(position)
+ val card = holder.item
+
+ val title = card.findViewById(R.id.title)
+ title.text = item?.url
+
+ val content = card.findViewById(R.id.content)
+ content.visibility = View.GONE
+
+ card.setOnClickListener {
+ onItemClickListener.onItemClick(item!!)
+ }
+
+ card.setOnLongClickListener {
+ onItemClickListener.onDelete(item!!); true
+ }
+ }
+
+ interface OnItemClickListener {
+ fun onItemClick(commandTemplate: CookieItem)
+ fun onSelected(commandTemplate: CookieItem)
+ fun onDelete(commandTemplate: CookieItem)
+ }
+
+ companion object {
+ private val DIFF_CALLBACK: DiffUtil.ItemCallback = object : DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: CookieItem, newItem: CookieItem): Boolean {
+ return oldItem.id == newItem.id
+ }
+
+ override fun areContentsTheSame(oldItem: CookieItem, newItem: CookieItem): Boolean {
+ return oldItem.id == newItem.id
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt
index 2ed1079a..d5e74c1b 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/DBManager.kt
@@ -2,16 +2,16 @@ package com.deniscerri.ytdlnis.database
import android.content.Context
import androidx.room.*
-import androidx.room.migration.AutoMigrationSpec
import com.deniscerri.ytdlnis.database.dao.*
import com.deniscerri.ytdlnis.database.models.*
@TypeConverters(Converters::class)
@Database(
- entities = [ResultItem::class, HistoryItem::class, DownloadItem::class, CommandTemplate::class, SearchHistoryItem::class, TemplateShortcut::class],
- version = 2,
+ entities = [ResultItem::class, HistoryItem::class, DownloadItem::class, CommandTemplate::class, SearchHistoryItem::class, TemplateShortcut::class, CookieItem::class],
+ version = 3,
autoMigrations = [
- AutoMigration (from = 1, to = 2)
+ AutoMigration (from = 1, to = 2),
+ AutoMigration (from = 2, to = 3)
]
)
abstract class DBManager : RoomDatabase(){
@@ -20,6 +20,7 @@ abstract class DBManager : RoomDatabase(){
abstract val downloadDao : DownloadDao
abstract val commandTemplateDao : CommandTemplateDao
abstract val searchHistoryDao: SearchHistoryDao
+ abstract val cookieDao: CookieDao
companion object {
//prevents multiple instances of db getting created at the same time
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CookieDao.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CookieDao.kt
new file mode 100644
index 00000000..855e4902
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/dao/CookieDao.kt
@@ -0,0 +1,30 @@
+package com.deniscerri.ytdlnis.database.dao
+
+import androidx.lifecycle.LiveData
+import androidx.room.*
+import com.deniscerri.ytdlnis.database.models.CookieItem
+import com.deniscerri.ytdlnis.database.models.DownloadItem
+
+@Dao
+interface CookieDao {
+ @Query("SELECT * FROM cookies ORDER BY id DESC")
+ fun getAllCookies() : List
+
+ @Query("SELECT * FROM cookies ORDER BY id DESC")
+ fun getAllCookiesLiveData() : LiveData>
+
+ @Query("SELECT * FROM cookies WHERE url=:url")
+ fun checkIfExistsWithSameURL(url: String) : CookieItem
+
+ @Insert(onConflict = OnConflictStrategy.IGNORE)
+ suspend fun insert(item: CookieItem)
+
+ @Query("DELETE FROM cookies")
+ suspend fun deleteAll()
+
+ @Query("DELETE FROM cookies WHERE id=:itemId")
+ suspend fun delete(itemId: Long)
+
+ @Update(onConflict = OnConflictStrategy.REPLACE)
+ suspend fun update(item: CookieItem)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/models/CookieItem.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/models/CookieItem.kt
new file mode 100644
index 00000000..1cd9b224
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/models/CookieItem.kt
@@ -0,0 +1,12 @@
+package com.deniscerri.ytdlnis.database.models
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "cookies")
+data class CookieItem(
+ @PrimaryKey(autoGenerate = true)
+ var id: Long,
+ var url: String,
+ var content: String
+)
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CookieRepository.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CookieRepository.kt
new file mode 100644
index 00000000..bff6c098
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/repository/CookieRepository.kt
@@ -0,0 +1,36 @@
+package com.deniscerri.ytdlnis.database.repository
+
+import androidx.lifecycle.LiveData
+import com.deniscerri.ytdlnis.database.dao.CookieDao
+import com.deniscerri.ytdlnis.database.models.CookieItem
+
+class CookieRepository(private val cookieDao: CookieDao) {
+ val items : LiveData> = cookieDao.getAllCookiesLiveData()
+
+ fun getAll() : List {
+ return cookieDao.getAllCookies()
+ }
+
+
+ suspend fun insert(item: CookieItem){
+ try{
+ cookieDao.checkIfExistsWithSameURL(item.url)
+ }catch(e: Exception){
+ cookieDao.insert(item)
+ }
+ }
+
+ suspend fun delete(item: CookieItem){
+ cookieDao.delete(item.id)
+ }
+
+
+ suspend fun deleteAll(){
+ cookieDao.deleteAll()
+ }
+
+ suspend fun update(item: CookieItem){
+ cookieDao.update(item)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt
new file mode 100644
index 00000000..41aa1a1b
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt
@@ -0,0 +1,130 @@
+package com.deniscerri.ytdlnis.database.viewmodel
+
+import android.annotation.SuppressLint
+import android.app.Application
+import android.content.ClipboardManager
+import android.content.Context
+import android.database.sqlite.SQLiteDatabase
+import android.database.sqlite.SQLiteDatabase.OPEN_READONLY
+import android.webkit.CookieManager
+import androidx.lifecycle.AndroidViewModel
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.viewModelScope
+import com.deniscerri.ytdlnis.database.DBManager
+import com.deniscerri.ytdlnis.database.models.CommandTemplateExport
+import com.deniscerri.ytdlnis.database.models.CookieItem
+import com.deniscerri.ytdlnis.database.repository.CookieRepository
+import com.deniscerri.ytdlnis.ui.more.WebViewActivity
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import kotlinx.serialization.encodeToString
+import kotlinx.serialization.json.Json
+
+class CookieViewModel(private val application: Application) : AndroidViewModel(application) {
+ private val repository: CookieRepository
+ val items: LiveData>
+ private val jsonFormat = Json { prettyPrint = true }
+
+
+ init {
+ val dao = DBManager.getInstance(application).cookieDao
+ repository = CookieRepository(dao)
+ items = repository.items
+ }
+
+ fun getAll(): List {
+ return repository.getAll()
+ }
+
+ fun insert(item: CookieItem) = viewModelScope.launch(Dispatchers.IO) {
+ repository.insert(item)
+ }
+
+ fun delete(item: CookieItem) = viewModelScope.launch(Dispatchers.IO) {
+ repository.delete(item)
+ }
+
+ fun deleteAll() = viewModelScope.launch(Dispatchers.IO) {
+ repository.deleteAll()
+ }
+
+ fun update(item: CookieItem) = viewModelScope.launch(Dispatchers.IO) {
+ repository.update(item)
+ }
+
+ object CookieObject {
+ const val NAME = "name"
+ const val VALUE = "value"
+ const val SECURE = "is_secure"
+ const val EXPIRY = "expires_utc"
+ const val HOST = "host_key"
+ const val PATH = "path"
+ }
+
+ @SuppressLint("SdCardPath")
+ fun getCookiesFromDB() : Result = kotlin.runCatching {
+ CookieManager.getInstance().run {
+ if (!hasCookies()) throw Exception("There is no cookies in the database!")
+ flush()
+ }
+ SQLiteDatabase.openDatabase(
+ "/data/data/com.deniscerri.ytdl/app_webview/Default/Cookies", null, OPEN_READONLY
+ ).run {
+ val projection = arrayOf(
+ CookieObject.HOST,
+ CookieObject.EXPIRY,
+ CookieObject.PATH,
+ CookieObject.NAME,
+ CookieObject.VALUE,
+ CookieObject.SECURE
+ )
+ val cookieList = mutableListOf()
+ query(
+ "cookies", projection, null, null, null, null, null
+ ).run {
+ while (moveToNext()) {
+ val expiry = getLong(getColumnIndexOrThrow(CookieObject.EXPIRY))
+ val name = getString(getColumnIndexOrThrow(CookieObject.NAME))
+ val value = getString(getColumnIndexOrThrow(CookieObject.VALUE))
+ val path = getString(getColumnIndexOrThrow(CookieObject.PATH))
+ val secure = getLong(getColumnIndexOrThrow(CookieObject.SECURE)) == 1L
+ val hostKey = getString(getColumnIndexOrThrow(CookieObject.HOST))
+
+ val host = if (hostKey[0] != '.') ".$hostKey" else hostKey
+ cookieList.add(
+ WebViewActivity.CookieItem(
+ domain = host,
+ name = name,
+ value = value,
+ path = path,
+ secure = secure,
+ expiry = expiry
+ )
+ )
+ }
+ close()
+ }
+ close()
+ val header = "# Netscape HTTP Cookie File\n" +
+ "# WebView Generated by the YTDLnis app\n"
+ cookieList.fold(StringBuilder(header)) { acc, cookie ->
+ acc.append(cookie.toNetscapeFormat()).append("\n")
+ }.toString()
+ }
+ }
+
+ fun exportToClipboard() = viewModelScope.launch {
+ try{
+ val clipboard: ClipboardManager =
+ application.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
+
+ getCookiesFromDB().getOrNull()?.let {
+ clipboard.setText(it)
+ }
+ }catch (e: Exception){
+ e.printStackTrace()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt
index 63613edd..7c98c073 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/receiver/ShareActivity.kt
@@ -28,6 +28,7 @@ import com.deniscerri.ytdlnis.MainActivity
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.DownloadItem
import com.deniscerri.ytdlnis.database.models.ResultItem
+import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
import com.deniscerri.ytdlnis.database.viewmodel.DownloadViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.ui.downloadcard.DownloadBottomSheetDialog
@@ -45,6 +46,7 @@ class ShareActivity : AppCompatActivity() {
lateinit var context: Context
private lateinit var resultViewModel: ResultViewModel
private lateinit var downloadViewModel: DownloadViewModel
+ private lateinit var cookieViewModel: CookieViewModel
private lateinit var sharedPreferences: SharedPreferences
@@ -73,8 +75,13 @@ class ShareActivity : AppCompatActivity() {
context = baseContext
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
downloadViewModel = ViewModelProvider(this)[DownloadViewModel::class.java]
+ cookieViewModel = ViewModelProvider(this)[CookieViewModel::class.java]
sharedPreferences = getSharedPreferences("root_preferences", Activity.MODE_PRIVATE)
+ cookieViewModel.getCookiesFromDB().getOrNull()?.let{
+ File(cacheDir, "cookies.txt").apply { writeText(it) }
+ }
+
val intent = intent
handleIntents(intent)
}
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/CookiesActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/CookiesActivity.kt
new file mode 100644
index 00000000..c2c74203
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/CookiesActivity.kt
@@ -0,0 +1,149 @@
+package com.deniscerri.ytdlnis.ui.more
+
+import android.content.Context
+import android.content.DialogInterface
+import android.content.Intent
+import android.os.Bundle
+import android.view.MenuItem
+import android.view.inputmethod.InputMethodManager
+import android.widget.EditText
+import android.widget.LinearLayout
+import androidx.appcompat.app.AlertDialog
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.deniscerri.ytdlnis.R
+import com.deniscerri.ytdlnis.adapter.CookieAdapter
+import com.deniscerri.ytdlnis.database.models.CookieItem
+import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
+import com.deniscerri.ytdlnis.util.FileUtil
+import com.deniscerri.ytdlnis.util.UiUtil
+import com.google.android.material.appbar.MaterialToolbar
+import com.google.android.material.chip.Chip
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import java.io.File
+import java.net.CookieManager
+
+
+class CookiesActivity : AppCompatActivity(), CookieAdapter.OnItemClickListener {
+ private lateinit var recyclerView: RecyclerView
+ private lateinit var listAdapter: CookieAdapter
+ private lateinit var topAppBar: MaterialToolbar
+ private lateinit var cookiesViewModel: CookieViewModel
+ private lateinit var uiUtil: UiUtil
+ var context: Context? = null
+
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_cookies)
+ context = baseContext
+
+ topAppBar = findViewById(R.id.logs_toolbar)
+ topAppBar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() }
+
+ listAdapter =
+ CookieAdapter(
+ this,
+ this@CookiesActivity
+ )
+ recyclerView = findViewById(R.id.template_recyclerview)
+ recyclerView.layoutManager = LinearLayoutManager(context)
+ recyclerView.adapter = listAdapter
+
+ uiUtil = UiUtil(FileUtil())
+
+ cookiesViewModel = ViewModelProvider(this)[CookieViewModel::class.java]
+ cookiesViewModel.items.observe(this) {
+ listAdapter.submitList(it)
+ }
+ initMenu()
+ initChips()
+ }
+
+ private fun initMenu() {
+ topAppBar.setOnMenuItemClickListener { m: MenuItem ->
+ when (m.itemId) {
+ R.id.delete_cookies -> {
+ val deleteDialog = MaterialAlertDialogBuilder(this)
+ deleteDialog.setTitle(getString(R.string.confirm_delete_history))
+ deleteDialog.setMessage(getString(R.string.confirm_delete_cookies_desc))
+ deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
+ deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
+ android.webkit.CookieManager.getInstance().removeAllCookies(null)
+ kotlin.runCatching {
+ File(cacheDir, "cookies.txt").apply { writeText("") }
+ }
+ }
+ deleteDialog.show()
+ }
+ R.id.export_clipboard -> {
+ cookiesViewModel.exportToClipboard()
+ }
+ }
+ true
+ }
+ }
+
+ private fun initChips() {
+ val new = findViewById(R.id.newCookie)
+ new.setOnClickListener {
+ showDialog("")
+ }
+ }
+
+ private fun showDialog(url: String){
+ val builder = MaterialAlertDialogBuilder(this)
+ builder.setTitle(getString(R.string.cookies))
+ val inputLayout = layoutInflater.inflate(R.layout.textinput, null)
+ val editText = inputLayout.findViewById(R.id.url_edittext)
+ editText.setText(url)
+ builder.setView(inputLayout)
+ builder.setPositiveButton(
+ getString(R.string.get_cookies)
+ ) { dialog: DialogInterface?, which: Int ->
+ val myIntent = Intent(this, WebViewActivity::class.java)
+ myIntent.putExtra("url", editText.text.toString())
+ startActivity(myIntent)
+ }
+
+ // handle the negative button of the alert dialog
+ builder.setNegativeButton(
+ getString(R.string.cancel)
+ ) { dialog: DialogInterface?, which: Int -> }
+ val dialog = builder.create()
+ editText.setOnKeyListener { view, i, keyEvent ->
+ dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
+ false
+ }
+ dialog.show()
+ val imm = getSystemService(INPUT_METHOD_SERVICE) as InputMethodManager
+ editText!!.postDelayed({
+ editText.requestFocus()
+ imm.showSoftInput(editText, 0)
+ }, 300)
+ dialog.getButton(AlertDialog.BUTTON_POSITIVE).isEnabled = editText.text.isNotEmpty()
+ }
+
+ companion object {
+ private const val TAG = "CookiesActivity"
+ }
+
+ override fun onItemClick(cookie: CookieItem) {
+ showDialog(cookie.url)
+ }
+
+ override fun onSelected(cookie: CookieItem) {
+ TODO("Not yet implemented")
+ }
+
+ override fun onDelete(cookie: CookieItem) {
+ val deleteDialog = MaterialAlertDialogBuilder(this)
+ deleteDialog.setTitle(getString(R.string.you_are_going_to_delete) + " \"" + cookie.url + "\"!")
+ deleteDialog.setNegativeButton(getString(R.string.cancel)) { dialogInterface: DialogInterface, _: Int -> dialogInterface.cancel() }
+ deleteDialog.setPositiveButton(getString(R.string.ok)) { _: DialogInterface?, _: Int ->
+ cookiesViewModel.delete(cookie)
+ }
+ deleteDialog.show()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/MoreFragment.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/MoreFragment.kt
index 5db65592..a0fea34d 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/MoreFragment.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/MoreFragment.kt
@@ -31,6 +31,7 @@ class MoreFragment : Fragment() {
private lateinit var logs: TextView
private lateinit var commandTemplates: TextView
private lateinit var downloadQueue: TextView
+ private lateinit var cookies: TextView
private lateinit var terminateApp: TextView
private lateinit var settings: TextView
private lateinit var mainActivity: MainActivity
@@ -52,6 +53,7 @@ class MoreFragment : Fragment() {
logs = view.findViewById(R.id.logs)
commandTemplates = view.findViewById(R.id.command_templates)
downloadQueue = view.findViewById(R.id.download_queue)
+ cookies = view.findViewById(R.id.cookies)
terminateApp = view.findViewById(R.id.terminate)
settings = view.findViewById(R.id.settings)
@@ -84,6 +86,11 @@ class MoreFragment : Fragment() {
startActivity(intent)
}
+ cookies.setOnClickListener {
+ val intent = Intent(context, CookiesActivity::class.java)
+ startActivity(intent)
+ }
+
terminateApp.setOnClickListener {
if (mainSharedPreferences.getBoolean("ask_terminate_app", true)){
var doNotShowAgain = false
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/ui/more/WebViewActivity.kt b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/WebViewActivity.kt
new file mode 100644
index 00000000..be582bcc
--- /dev/null
+++ b/app/src/main/java/com/deniscerri/ytdlnis/ui/more/WebViewActivity.kt
@@ -0,0 +1,127 @@
+package com.deniscerri.ytdlnis.ui.more
+
+import android.os.Bundle
+import android.webkit.CookieManager
+import android.webkit.WebView
+import android.webkit.WebViewClient
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.ViewModelProvider
+import com.deniscerri.ytdlnis.R
+import com.deniscerri.ytdlnis.database.models.CookieItem
+import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
+import com.google.android.material.appbar.AppBarLayout
+import com.google.android.material.appbar.MaterialToolbar
+import com.google.android.material.button.MaterialButton
+import java.io.File
+
+
+class WebViewActivity : AppCompatActivity() {
+ private lateinit var cookiesViewModel: CookieViewModel
+ private lateinit var webView: WebView
+ private lateinit var toolbar: MaterialToolbar
+ private lateinit var generateBtn: MaterialButton
+ private lateinit var cookieManager: CookieManager
+ private lateinit var url: String
+ private lateinit var cookies: String
+
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.webview_activity)
+ url = intent.extras!!.getString("url")!!
+ cookies = ""
+ cookiesViewModel = ViewModelProvider(this)[CookieViewModel::class.java]
+ val appbar = findViewById(R.id.webview_appbarlayout)
+ toolbar = appbar.findViewById(R.id.webviewToolbar)
+ generateBtn = toolbar.findViewById(R.id.generate)
+ cookieManager = CookieManager.getInstance()
+
+ webView = findViewById(R.id.webview)
+ webView.settings.javaScriptEnabled = true
+ webView.settings.domStorageEnabled = true
+ webView.settings.javaScriptCanOpenWindowsAutomatically = true
+
+ cookieManager = CookieManager.getInstance()
+
+ toolbar.setNavigationOnClickListener {
+ cookieManager.flush()
+ onBackPressedDispatcher.onBackPressed()
+ }
+
+ val webViewClient: WebViewClient = object : WebViewClient() {
+
+ override fun onPageFinished(view: WebView, url: String) {
+ super.onPageFinished(view, url)
+ kotlin.runCatching {
+ toolbar.title = view.title
+ cookies = CookieManager.getInstance().getCookie(view.url)
+ }
+ }
+ }
+ webView.webViewClient = webViewClient
+
+ generateBtn.setOnClickListener {
+ cookiesViewModel.getCookiesFromDB().getOrNull()?.let {
+ File(cacheDir , "/cookies.txt").apply { writeText(it) }
+ }
+ kotlin.runCatching {
+ cookiesViewModel.insert(
+ CookieItem(
+ 0,
+ url,
+ ""
+ )
+ )
+ }.onFailure {
+ Toast.makeText(this, "Something went wrong", Toast.LENGTH_SHORT).show()
+ }
+ cookieManager.flush()
+ onBackPressedDispatcher.onBackPressed()
+ }
+
+ webView.loadUrl(url)
+
+ }
+
+ companion object {
+ const val TAG = "WebViewActivity"
+ }
+
+ data class CookieItem(
+ val domain: String = "",
+ val name: String = "",
+ val value: String = "",
+ val includeSubdomains: Boolean = true,
+ val path: String = "/",
+ val secure: Boolean = true,
+ val expiry: Long = 0L,
+ ) {
+ constructor(
+ url: String,
+ name: String,
+ value: String
+ ) : this(domain = url.replace(Regex("""http(s)?://(\w*(www|m|account|sso))?|/.*"""), ""), name = name, value = value)
+
+ fun toNetscapeFormat(): String {
+ val stringList = listOf(domain,
+ includeSubdomains.toString().uppercase(),
+ path,
+ secure.toString().uppercase(),
+ expiry.toString(),
+ name,
+ value)
+
+ val builder = StringBuilder(stringList.first())
+
+ for (s in stringList.subList(1, stringList.size)) {
+ if (s.isNotEmpty()) {
+ if (builder.isNotEmpty())
+ builder.append("\u0009")
+ builder.append(s)
+ }
+ }
+ return builder.toString()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt
index 6d18728c..ecda6225 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/util/InfoUtil.kt
@@ -2,6 +2,7 @@ package com.deniscerri.ytdlnis.util
import android.app.Activity
import android.content.Context
+import android.os.Environment
import android.util.Log
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.Format
@@ -13,13 +14,14 @@ import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.io.BufferedReader
+import java.io.File
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.util.*
import java.util.regex.Pattern
-class InfoUtil(context: Context) {
+class InfoUtil(private val context: Context) {
private var items: ArrayList
private var key: String? = null
private var useInvidous = false
@@ -494,6 +496,12 @@ class InfoUtil(context: Context) {
request.addOption("-R", "1")
request.addOption("--socket-timeout", "5")
if (!query.contains("http")) request.addOption("--default-search", "ytsearch25")
+
+ val cookiesFile = File(context.cacheDir, "cookies.txt")
+ if (cookiesFile.exists()){
+ request.addOption("--cookies", cookiesFile.absolutePath)
+ }
+
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
val results: Array = try {
val lineSeparator = System.getProperty("line.separator")
@@ -637,6 +645,12 @@ class InfoUtil(context: Context) {
request.addOption("--skip-download")
request.addOption("-R", "1")
request.addOption("--socket-timeout", "5")
+
+ val cookiesFile = File(context.cacheDir, "cookies.txt")
+ if (cookiesFile.exists()){
+ request.addOption("--cookies", cookiesFile.absolutePath)
+ }
+
val youtubeDLResponse = YoutubeDL.getInstance().execute(request)
val results: Array = try {
val lineSeparator = System.getProperty("line.separator")
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt
index d285eefd..51fcec0e 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/work/DownloadWorker.kt
@@ -132,6 +132,11 @@ class DownloadWorker(
request.addOption("--restrict-filenames")
}
+ val cookiesFile = File(context.cacheDir, "cookies.txt")
+ if (cookiesFile.exists()){
+ request.addOption("--cookies", cookiesFile.absolutePath)
+ }
+
when(type){
DownloadViewModel.Type.audio -> {
var audioQualityId : String = downloadItem.format.format_id
diff --git a/app/src/main/java/com/deniscerri/ytdlnis/work/TerminalDownloadWorker.kt b/app/src/main/java/com/deniscerri/ytdlnis/work/TerminalDownloadWorker.kt
index 6f39e60f..26c5480f 100644
--- a/app/src/main/java/com/deniscerri/ytdlnis/work/TerminalDownloadWorker.kt
+++ b/app/src/main/java/com/deniscerri/ytdlnis/work/TerminalDownloadWorker.kt
@@ -56,6 +56,11 @@ class TerminalDownloadWorker(
}.absolutePath
)
+ val cookiesFile = File(context.cacheDir, "cookies.txt")
+ if (cookiesFile.exists()){
+ request.addOption("--cookies", cookiesFile.absolutePath)
+ }
+
request.addOption("-P", tempFileDir.absolutePath)
val logDownloads = sharedPreferences.getBoolean("log_downloads", false) && !sharedPreferences.getBoolean("incognito", false)
diff --git a/app/src/main/res/drawable/baseline_cookie_24.xml b/app/src/main/res/drawable/baseline_cookie_24.xml
new file mode 100644
index 00000000..c00d691c
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_cookie_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/activity_cookies.xml b/app/src/main/res/layout/activity_cookies.xml
new file mode 100644
index 00000000..3ed277c7
--- /dev/null
+++ b/app/src/main/res/layout/activity_cookies.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_more.xml b/app/src/main/res/layout/fragment_more.xml
index 829ca95d..7a782c17 100644
--- a/app/src/main/res/layout/fragment_more.xml
+++ b/app/src/main/res/layout/fragment_more.xml
@@ -81,6 +81,21 @@
android:paddingHorizontal="15dp"
app:drawableStartCompat="@drawable/ic_concurrent_downloads" />
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/webview_activity.xml b/app/src/main/res/layout/webview_activity.xml
new file mode 100644
index 00000000..96e18df9
--- /dev/null
+++ b/app/src/main/res/layout/webview_activity.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/menu/cookies_menu.xml b/app/src/main/res/menu/cookies_menu.xml
new file mode 100644
index 00000000..64b25d79
--- /dev/null
+++ b/app/src/main/res/menu/cookies_menu.xml
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 42596584..429a423b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -197,4 +197,11 @@
Suggested
Select All
Share
+ Cookies
+ New Cookie
+ Cookie
+ Create Cookie
+ Get Cookies
+ Delete All Cookies
+ Delete all the cookies, permanently
\ No newline at end of file