From 6fc87df98d279c7b9564192252064fac2d06990f Mon Sep 17 00:00:00 2001 From: deniscerri <64997243+deniscerri@users.noreply.github.com> Date: Sun, 9 Apr 2023 15:44:00 +0200 Subject: [PATCH] implemented cookies --- .../3.json | 421 ++++++++++++++++++ app/src/main/AndroidManifest.xml | 26 ++ .../com/deniscerri/ytdlnis/MainActivity.kt | 7 + .../ytdlnis/adapter/CookieAdapter.kt | 75 ++++ .../deniscerri/ytdlnis/database/DBManager.kt | 9 +- .../ytdlnis/database/dao/CookieDao.kt | 30 ++ .../ytdlnis/database/models/CookieItem.kt | 12 + .../database/repository/CookieRepository.kt | 36 ++ .../database/viewmodel/CookieViewModel.kt | 130 ++++++ .../ytdlnis/receiver/ShareActivity.kt | 7 + .../ytdlnis/ui/more/CookiesActivity.kt | 149 +++++++ .../ytdlnis/ui/more/MoreFragment.kt | 7 + .../ytdlnis/ui/more/WebViewActivity.kt | 127 ++++++ .../com/deniscerri/ytdlnis/util/InfoUtil.kt | 16 +- .../deniscerri/ytdlnis/work/DownloadWorker.kt | 5 + .../ytdlnis/work/TerminalDownloadWorker.kt | 5 + .../main/res/drawable/baseline_cookie_24.xml | 5 + app/src/main/res/layout/activity_cookies.xml | 61 +++ app/src/main/res/layout/fragment_more.xml | 15 + app/src/main/res/layout/textinput.xml | 26 ++ app/src/main/res/layout/webview_activity.xml | 57 +++ app/src/main/res/menu/cookies_menu.xml | 13 + app/src/main/res/values/strings.xml | 7 + 23 files changed, 1241 insertions(+), 5 deletions(-) create mode 100644 app/schemas/com.deniscerri.ytdlnis.database.DBManager/3.json create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/adapter/CookieAdapter.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/database/dao/CookieDao.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/database/models/CookieItem.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/database/repository/CookieRepository.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/database/viewmodel/CookieViewModel.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/ui/more/CookiesActivity.kt create mode 100644 app/src/main/java/com/deniscerri/ytdlnis/ui/more/WebViewActivity.kt create mode 100644 app/src/main/res/drawable/baseline_cookie_24.xml create mode 100644 app/src/main/res/layout/activity_cookies.xml create mode 100644 app/src/main/res/layout/textinput.xml create mode 100644 app/src/main/res/layout/webview_activity.xml create mode 100644 app/src/main/res/menu/cookies_menu.xml 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 @@ + + + + + + + +