implemented cookies

This commit is contained in:
deniscerri 2023-04-09 15:44:00 +02:00
parent f5ad2aee1d
commit 6fc87df98d
No known key found for this signature in database
GPG key ID: 95C43D517D830350
23 changed files with 1241 additions and 5 deletions

View file

@ -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')"
]
}
}

View file

@ -144,6 +144,32 @@
</intent-filter>
</activity>
<activity
android:name=".ui.more.CookiesActivity"
android:configChanges="layoutDirection|locale"
android:exported="true"
android:parentActivityName=".MainActivity"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="ytdlnis.CookiesActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity
android:name=".ui.more.WebViewActivity"
android:configChanges="layoutDirection|locale"
android:exported="true"
android:parentActivityName=".MainActivity"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="ytdlnis.WebViewActivity" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<receiver android:name=".receiver.CancelDownloadNotificationReceiver" />
<service

View file

@ -25,6 +25,7 @@ import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import androidx.navigation.ui.setupWithNavController
import com.deniscerri.ytdlnis.database.viewmodel.CookieViewModel
import com.deniscerri.ytdlnis.database.viewmodel.ResultViewModel
import com.deniscerri.ytdlnis.ui.HomeFragment
import com.deniscerri.ytdlnis.ui.downloads.DownloadQueueActivity
@ -48,6 +49,7 @@ class MainActivity : AppCompatActivity() {
lateinit var context: Context
private lateinit var preferences: SharedPreferences
private lateinit var resultViewModel: ResultViewModel
private lateinit var cookieViewModel: CookieViewModel
private lateinit var bottomNav: BottomNavigationView
private lateinit var navHostFragment : NavHostFragment
@ -58,6 +60,7 @@ class MainActivity : AppCompatActivity() {
setContentView(R.layout.activity_main)
context = baseContext
resultViewModel = ViewModelProvider(this)[ResultViewModel::class.java]
cookieViewModel = ViewModelProvider(this)[CookieViewModel::class.java]
preferences = context.getSharedPreferences("root_preferences", MODE_PRIVATE)
if (preferences.getBoolean("incognito", false)){
@ -99,6 +102,10 @@ class MainActivity : AppCompatActivity() {
}
}
cookieViewModel.getCookiesFromDB().getOrNull()?.let{
File(cacheDir, "cookies.txt").apply { writeText(it) }
}
val intent = intent
handleIntents(intent)
}

View file

@ -0,0 +1,75 @@
package com.deniscerri.ytdlnis.adapter
import android.app.Activity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.AsyncDifferConfig
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.deniscerri.ytdlnis.R
import com.deniscerri.ytdlnis.database.models.CookieItem
class CookieAdapter(onItemClickListener: OnItemClickListener, activity: Activity) : ListAdapter<CookieItem?, CookieAdapter.ViewHolder>(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<TextView>(R.id.title)
title.text = item?.url
val content = card.findViewById<TextView>(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<CookieItem> = object : DiffUtil.ItemCallback<CookieItem>() {
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
}
}
}
}

View file

@ -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

View file

@ -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<CookieItem>
@Query("SELECT * FROM cookies ORDER BY id DESC")
fun getAllCookiesLiveData() : LiveData<List<CookieItem>>
@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)
}

View file

@ -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
)

View file

@ -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<List<CookieItem>> = cookieDao.getAllCookiesLiveData()
fun getAll() : List<CookieItem> {
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)
}
}

View file

@ -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<List<CookieItem>>
private val jsonFormat = Json { prettyPrint = true }
init {
val dao = DBManager.getInstance(application).cookieDao
repository = CookieRepository(dao)
items = repository.items
}
fun getAll(): List<CookieItem> {
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<String> = 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<WebViewActivity.CookieItem>()
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()
}
}
}

View file

@ -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)
}

View file

@ -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<Chip>(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<EditText>(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()
}
}

View file

@ -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

View file

@ -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<AppBarLayout>(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()
}
}
}

View file

@ -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<ResultItem?>
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<String?> = 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<String?> = try {
val lineSeparator = System.getProperty("line.separator")

View file

@ -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

View file

@ -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)

View file

@ -0,0 +1,5 @@
<vector android:height="24dp"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="?android:colorAccent" android:pathData="M21.95,10.99c-1.79,-0.03 -3.7,-1.95 -2.68,-4.22c-2.98,1 -5.77,-1.59 -5.19,-4.56C6.95,0.71 2,6.58 2,12c0,5.52 4.48,10 10,10C17.89,22 22.54,16.92 21.95,10.99zM8.5,15C7.67,15 7,14.33 7,13.5S7.67,12 8.5,12s1.5,0.67 1.5,1.5S9.33,15 8.5,15zM10.5,10C9.67,10 9,9.33 9,8.5S9.67,7 10.5,7S12,7.67 12,8.5S11.33,10 10.5,10zM15,16c-0.55,0 -1,-0.45 -1,-1c0,-0.55 0.45,-1 1,-1s1,0.45 1,1C16,15.55 15.55,16 15,16z"/>
</vector>

View file

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.deniscerri.ytdlnis.ui.more.TerminalActivity">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:liftOnScroll="true"
android:background="@android:color/transparent"
android:fitsSystemWindows="true">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/logs_toolbar"
android:elevation="0dp"
app:title="@string/cookies"
app:menu="@menu/cookies_menu"
android:layout_width="match_parent"
app:navigationIcon="@drawable/ic_back"
android:layout_height="match_parent"/>
</com.google.android.material.appbar.AppBarLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_margin="10dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<com.google.android.material.chip.Chip
android:id="@+id/newCookie"
style="@style/Widget.Material3.Chip.Assist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/new_cookie"
app:chipIcon="@drawable/ic_plus" />
<androidx.recyclerview.widget.RecyclerView
android:layout_below="@+id/newCookie"
android:id="@+id/template_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
>
</androidx.recyclerview.widget.RecyclerView>
</RelativeLayout>
<include layout="@layout/history_no_results"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -81,6 +81,21 @@
android:paddingHorizontal="15dp"
app:drawableStartCompat="@drawable/ic_concurrent_downloads" />
<TextView
android:id="@+id/cookies"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:drawablePadding="35dp"
android:textSize="16sp"
android:paddingVertical="15dp"
android:text="@string/cookies"
android:textStyle="bold"
android:clickable="true"
android:focusable="true"
android:background="?android:attr/selectableItemBackground"
android:paddingHorizontal="15dp"
app:drawableStartCompat="@drawable/baseline_cookie_24" />
<TextView
android:id="@+id/terminate"
android:layout_width="match_parent"

View file

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/url_textinput"
style="@style/Widget.Material3.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="URL"
android:padding="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/url_edittext"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="text" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/webview_appbarlayout"
app:liftOnScroll="true"
android:background="@android:color/transparent"
android:elevation="0dp"
app:elevation="0dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/webviewToolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_scrollFlags="scroll"
app:navigationIcon="?attr/homeAsUpIndicator"
android:theme="@style/Toolbar"
>
<Button
android:id="@+id/generate"
android:layout_gravity="end"
style="@style/Widget.Material3.Button.ElevatedButton.Icon"
android:layout_width="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_height="wrap_content"
android:autoLink="all"
android:text="@string/ok"
app:icon="@drawable/ic_check"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="PrivateResource" />
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.appbar.AppBarLayout>
<RelativeLayout
android:layout_width="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
android:layout_height="match_parent">
<WebView
android:id="@+id/webview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/delete_cookies"
android:title="@string/delete_all_cookies"
app:showAsAction="never" />
<item
android:id="@+id/export_clipboard"
android:title="@string/export_from_clipboard"
app:showAsAction="never" />
</menu>

View file

@ -197,4 +197,11 @@
<string name="suggested">Suggested</string>
<string name="select_all">Select All</string>
<string name="share">Share</string>
<string name="cookies">Cookies</string>
<string name="new_cookie">New Cookie</string>
<string name="cookie">Cookie</string>
<string name="create_cookie">Create Cookie</string>
<string name="get_cookies">Get Cookies</string>
<string name="delete_all_cookies">Delete All Cookies</string>
<string name="confirm_delete_cookies_desc">Delete all the cookies, permanently</string>
</resources>