2020-09-07 00:12:25 +02:00
|
|
|
import Vue from 'vue'
|
|
|
|
import { mapActions, mapMutations } from 'vuex'
|
|
|
|
import FtCard from '../ft-card/ft-card.vue'
|
|
|
|
import FtButton from '../ft-button/ft-button.vue'
|
|
|
|
import FtToggleSwitch from '../ft-toggle-switch/ft-toggle-switch.vue'
|
|
|
|
import FtFlexBox from '../ft-flex-box/ft-flex-box.vue'
|
|
|
|
import FtPrompt from '../ft-prompt/ft-prompt.vue'
|
|
|
|
|
|
|
|
import fs from 'fs'
|
2021-01-21 20:19:25 +01:00
|
|
|
import { opmlToJSON } from 'opml-to-json'
|
2020-09-07 00:12:25 +02:00
|
|
|
import ytch from 'yt-channel-info'
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
// FIXME: Missing web logic branching
|
2020-09-07 00:12:25 +02:00
|
|
|
|
|
|
|
export default Vue.extend({
|
|
|
|
name: 'DataSettings',
|
|
|
|
components: {
|
|
|
|
'ft-card': FtCard,
|
|
|
|
'ft-button': FtButton,
|
|
|
|
'ft-toggle-switch': FtToggleSwitch,
|
|
|
|
'ft-flex-box': FtFlexBox,
|
|
|
|
'ft-prompt': FtPrompt
|
|
|
|
},
|
|
|
|
data: function () {
|
|
|
|
return {
|
|
|
|
showImportSubscriptionsPrompt: false,
|
|
|
|
showExportSubscriptionsPrompt: false,
|
|
|
|
subscriptionsPromptValues: [
|
|
|
|
'freetube',
|
2021-08-25 09:47:21 +02:00
|
|
|
'youtubenew',
|
2020-09-07 00:12:25 +02:00
|
|
|
'youtube',
|
2020-11-01 22:23:06 +01:00
|
|
|
'youtubeold',
|
2020-09-07 00:12:25 +02:00
|
|
|
'newpipe'
|
|
|
|
]
|
|
|
|
}
|
|
|
|
},
|
|
|
|
computed: {
|
|
|
|
rememberHistory: function () {
|
|
|
|
return this.$store.getters.getRememberHistory
|
|
|
|
},
|
|
|
|
saveWatchedProgress: function () {
|
|
|
|
return this.$store.getters.getSaveWatchedProgress
|
|
|
|
},
|
|
|
|
backendPreference: function () {
|
|
|
|
return this.$store.getters.getBackendPreference
|
|
|
|
},
|
|
|
|
backendFallback: function () {
|
|
|
|
return this.$store.getters.getBackendFallback
|
|
|
|
},
|
2021-07-03 03:55:56 +02:00
|
|
|
currentInvidiousInstance: function () {
|
|
|
|
return this.$store.getters.getCurrentInvidiousInstance
|
2020-09-07 00:12:25 +02:00
|
|
|
},
|
|
|
|
profileList: function () {
|
|
|
|
return this.$store.getters.getProfileList
|
|
|
|
},
|
|
|
|
importSubscriptionsPromptNames: function () {
|
|
|
|
const importFreeTube = this.$t('Settings.Data Settings.Import FreeTube')
|
|
|
|
const importYouTube = this.$t('Settings.Data Settings.Import YouTube')
|
|
|
|
const importNewPipe = this.$t('Settings.Data Settings.Import NewPipe')
|
|
|
|
return [
|
|
|
|
`${importFreeTube} (.db)`,
|
2021-08-25 09:47:21 +02:00
|
|
|
`${importYouTube} (.csv)`,
|
2020-11-01 22:23:06 +01:00
|
|
|
`${importYouTube} (.json)`,
|
2020-09-07 00:12:25 +02:00
|
|
|
`${importYouTube} (.opml)`,
|
|
|
|
`${importNewPipe} (.json)`
|
|
|
|
]
|
|
|
|
},
|
|
|
|
exportSubscriptionsPromptNames: function () {
|
|
|
|
const exportFreeTube = this.$t('Settings.Data Settings.Export FreeTube')
|
|
|
|
const exportYouTube = this.$t('Settings.Data Settings.Export YouTube')
|
|
|
|
const exportNewPipe = this.$t('Settings.Data Settings.Export NewPipe')
|
|
|
|
return [
|
|
|
|
`${exportFreeTube} (.db)`,
|
2021-08-25 09:47:21 +02:00
|
|
|
`${exportYouTube} (.csv)`,
|
2020-11-01 22:23:06 +01:00
|
|
|
`${exportYouTube} (.json)`,
|
2020-09-07 00:12:25 +02:00
|
|
|
`${exportYouTube} (.opml)`,
|
|
|
|
`${exportNewPipe} (.json)`
|
|
|
|
]
|
|
|
|
}
|
|
|
|
},
|
|
|
|
methods: {
|
2021-01-14 19:51:33 +01:00
|
|
|
openProfileSettings: function () {
|
|
|
|
this.$router.push({
|
|
|
|
path: '/settings/profile/'
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2020-09-07 00:12:25 +02:00
|
|
|
importSubscriptions: function (option) {
|
|
|
|
this.showImportSubscriptionsPrompt = false
|
|
|
|
|
|
|
|
if (option === null) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (option) {
|
|
|
|
case 'freetube':
|
|
|
|
this.importFreeTubeSubscriptions()
|
|
|
|
break
|
2021-08-25 09:47:21 +02:00
|
|
|
case 'youtubenew':
|
|
|
|
this.importCsvYouTubeSubscriptions()
|
|
|
|
break
|
2020-09-07 00:12:25 +02:00
|
|
|
case 'youtube':
|
|
|
|
this.importYouTubeSubscriptions()
|
|
|
|
break
|
2020-11-01 22:23:06 +01:00
|
|
|
case 'youtubeold':
|
|
|
|
this.importOpmlYouTubeSubscriptions()
|
|
|
|
break
|
2020-09-07 00:12:25 +02:00
|
|
|
case 'newpipe':
|
|
|
|
this.importNewPipeSubscriptions()
|
|
|
|
break
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2020-10-08 23:35:36 +02:00
|
|
|
handleFreetubeImportFile: function (filePath) {
|
|
|
|
fs.readFile(filePath, async (err, data) => {
|
|
|
|
if (err) {
|
|
|
|
const message = this.$t('Settings.Data Settings.Unable to read file')
|
|
|
|
this.showToast({
|
|
|
|
message: `${message}: ${err}`
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let textDecode = new TextDecoder('utf-8').decode(data)
|
|
|
|
textDecode = textDecode.split('\n')
|
|
|
|
textDecode.pop()
|
|
|
|
textDecode = textDecode.map(data => JSON.parse(data))
|
|
|
|
|
|
|
|
const firstEntry = textDecode[0]
|
|
|
|
if (firstEntry.channelId && firstEntry.channelName && firstEntry.channelThumbnail && firstEntry._id && firstEntry.profile) {
|
|
|
|
// Old FreeTube subscriptions format detected, so convert it to the new one:
|
|
|
|
textDecode = await this.convertOldFreeTubeFormatToNew(textDecode)
|
|
|
|
}
|
|
|
|
|
2020-10-09 03:19:29 +02:00
|
|
|
const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
|
|
|
|
|
2020-10-08 23:35:36 +02:00
|
|
|
textDecode.forEach((profileData) => {
|
|
|
|
// We would technically already be done by the time the data is parsed,
|
|
|
|
// however we want to limit the possibility of malicious data being sent
|
|
|
|
// to the app, so we'll only grab the data we need here.
|
|
|
|
|
|
|
|
const requiredKeys = [
|
|
|
|
'_id',
|
|
|
|
'name',
|
|
|
|
'bgColor',
|
|
|
|
'textColor',
|
|
|
|
'subscriptions'
|
|
|
|
]
|
|
|
|
|
|
|
|
const profileObject = {}
|
|
|
|
Object.keys(profileData).forEach((key) => {
|
|
|
|
if (!requiredKeys.includes(key)) {
|
|
|
|
const message = this.$t('Settings.Data Settings.Unknown data key')
|
|
|
|
this.showToast({
|
|
|
|
message: `${message}: ${key}`
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
profileObject[key] = profileData[key]
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
if (Object.keys(profileObject).length < requiredKeys.length) {
|
|
|
|
const message = this.$t('Settings.Data Settings.Profile object has insufficient data, skipping item')
|
|
|
|
this.showToast({
|
|
|
|
message: message
|
|
|
|
})
|
|
|
|
} else {
|
2020-10-09 03:19:29 +02:00
|
|
|
if (profileObject.name === 'All Channels' || profileObject._id === 'allChannels') {
|
2020-10-08 23:35:36 +02:00
|
|
|
primaryProfile.subscriptions = primaryProfile.subscriptions.concat(profileObject.subscriptions)
|
2020-10-09 03:19:29 +02:00
|
|
|
primaryProfile.subscriptions = primaryProfile.subscriptions.filter((sub, index) => {
|
|
|
|
const profileIndex = primaryProfile.subscriptions.findIndex((x) => {
|
|
|
|
return x.name === sub.name
|
|
|
|
})
|
|
|
|
|
|
|
|
return profileIndex === index
|
|
|
|
})
|
2020-10-08 23:35:36 +02:00
|
|
|
this.updateProfile(primaryProfile)
|
|
|
|
} else {
|
2020-10-09 03:19:29 +02:00
|
|
|
const existingProfileIndex = this.profileList.findIndex((profile) => {
|
|
|
|
return profile.name.includes(profileObject.name)
|
|
|
|
})
|
|
|
|
|
|
|
|
if (existingProfileIndex !== -1) {
|
|
|
|
const existingProfile = JSON.parse(JSON.stringify(this.profileList[existingProfileIndex]))
|
|
|
|
existingProfile.subscriptions = existingProfile.subscriptions.concat(profileObject.subscriptions)
|
|
|
|
existingProfile.subscriptions = existingProfile.subscriptions.filter((sub, index) => {
|
|
|
|
const profileIndex = existingProfile.subscriptions.findIndex((x) => {
|
|
|
|
return x.name === sub.name
|
|
|
|
})
|
|
|
|
|
|
|
|
return profileIndex === index
|
|
|
|
})
|
|
|
|
this.updateProfile(existingProfile)
|
|
|
|
} else {
|
|
|
|
this.updateProfile(profileObject)
|
|
|
|
}
|
|
|
|
|
|
|
|
primaryProfile.subscriptions = primaryProfile.subscriptions.concat(profileObject.subscriptions)
|
|
|
|
primaryProfile.subscriptions = primaryProfile.subscriptions.filter((sub, index) => {
|
|
|
|
const profileIndex = primaryProfile.subscriptions.findIndex((x) => {
|
|
|
|
return x.name === sub.name
|
|
|
|
})
|
|
|
|
|
|
|
|
return profileIndex === index
|
|
|
|
})
|
|
|
|
this.updateProfile(primaryProfile)
|
2020-10-08 23:35:36 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.All subscriptions and profiles have been successfully imported')
|
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
importFreeTubeSubscriptions: async function () {
|
2020-09-07 00:12:25 +02:00
|
|
|
const options = {
|
|
|
|
properties: ['openFile'],
|
|
|
|
filters: [
|
|
|
|
{
|
|
|
|
name: 'Database File',
|
|
|
|
extensions: ['db']
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const response = await this.showOpenDialog(options)
|
|
|
|
if (response.canceled || response.filePaths.length === 0) {
|
|
|
|
return
|
|
|
|
}
|
2020-09-07 00:12:25 +02:00
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const filePath = response.filePaths[0]
|
|
|
|
this.handleFreetubeImportFile(filePath)
|
2020-09-07 00:12:25 +02:00
|
|
|
},
|
|
|
|
|
2021-08-25 09:47:21 +02:00
|
|
|
handleYoutubeCsvImportFile: function(filePath) { // first row = header, last row = empty
|
|
|
|
fs.readFile(filePath, async (err, data) => {
|
|
|
|
if (err) {
|
|
|
|
const message = this.$t('Settings.Data Settings.Unable to read file')
|
|
|
|
this.showToast({
|
|
|
|
message: `${message}: ${err}`
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
const textDecode = new TextDecoder('utf-8').decode(data)
|
|
|
|
console.log(textDecode)
|
|
|
|
const youtubeSubscriptions = textDecode.split('\n')
|
|
|
|
const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
|
|
|
|
const subscriptions = []
|
|
|
|
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.This might take a while, please wait')
|
|
|
|
})
|
|
|
|
|
|
|
|
this.updateShowProgressBar(true)
|
|
|
|
this.setProgressBarPercentage(0)
|
|
|
|
let count = 0
|
|
|
|
for (let i = 1; i < (youtubeSubscriptions.length - 1); i++) {
|
|
|
|
const channelId = youtubeSubscriptions[i].split(',')[0]
|
|
|
|
const subExists = primaryProfile.subscriptions.findIndex((sub) => {
|
|
|
|
return sub.id === channelId
|
|
|
|
})
|
|
|
|
if (subExists === -1) {
|
|
|
|
let channelInfo
|
|
|
|
if (this.backendPreference === 'invidious') { // only needed for thumbnail
|
|
|
|
channelInfo = await this.getChannelInfoInvidious(channelId)
|
|
|
|
} else {
|
|
|
|
channelInfo = await this.getChannelInfoLocal(channelId)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof channelInfo.author !== 'undefined') {
|
|
|
|
const subscription = {
|
|
|
|
id: channelId,
|
|
|
|
name: channelInfo.author,
|
|
|
|
thumbnail: channelInfo.authorThumbnails[1].url
|
|
|
|
}
|
|
|
|
subscriptions.push(subscription)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
count++
|
|
|
|
|
|
|
|
const progressPercentage = (count / (youtubeSubscriptions.length - 1)) * 100
|
|
|
|
this.setProgressBarPercentage(progressPercentage)
|
|
|
|
if (count + 1 === (youtubeSubscriptions.length - 1)) {
|
|
|
|
primaryProfile.subscriptions = primaryProfile.subscriptions.concat(subscriptions)
|
|
|
|
this.updateProfile(primaryProfile)
|
|
|
|
|
|
|
|
if (subscriptions.length < count + 2) {
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.One or more subscriptions were unable to be imported')
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.All subscriptions have been successfully imported')
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
this.updateShowProgressBar(false)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2020-11-01 22:23:06 +01:00
|
|
|
handleYoutubeImportFile: function (filePath) {
|
|
|
|
fs.readFile(filePath, async (err, data) => {
|
|
|
|
if (err) {
|
|
|
|
const message = this.$t('Settings.Data Settings.Unable to read file')
|
|
|
|
this.showToast({
|
|
|
|
message: `${message}: ${err}`
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
let textDecode = new TextDecoder('utf-8').decode(data)
|
|
|
|
textDecode = JSON.parse(textDecode)
|
|
|
|
|
|
|
|
console.log(textDecode)
|
|
|
|
|
|
|
|
const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
|
|
|
|
const subscriptions = []
|
|
|
|
|
2021-04-21 04:43:40 +02:00
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.This might take a while, please wait')
|
|
|
|
})
|
|
|
|
|
2020-11-01 22:23:06 +01:00
|
|
|
this.updateShowProgressBar(true)
|
|
|
|
this.setProgressBarPercentage(0)
|
|
|
|
|
|
|
|
let count = 0
|
|
|
|
|
|
|
|
textDecode.forEach((channel) => {
|
|
|
|
const snippet = channel.snippet
|
|
|
|
|
|
|
|
if (typeof snippet === 'undefined') {
|
2021-04-21 04:43:40 +02:00
|
|
|
const message = this.$t('Settings.Data Settings.Invalid subscriptions file')
|
|
|
|
this.showToast({
|
|
|
|
message: message
|
|
|
|
})
|
|
|
|
|
2020-11-01 22:23:06 +01:00
|
|
|
throw new Error('Unable to find channel data')
|
|
|
|
}
|
|
|
|
|
|
|
|
const subscription = {
|
|
|
|
id: snippet.resourceId.channelId,
|
|
|
|
name: snippet.title,
|
|
|
|
thumbnail: snippet.thumbnails.default.url
|
|
|
|
}
|
|
|
|
|
|
|
|
const subExists = primaryProfile.subscriptions.findIndex((sub) => {
|
|
|
|
return sub.id === subscription.id || sub.name === subscription.name
|
|
|
|
})
|
|
|
|
|
2020-12-30 20:46:48 +01:00
|
|
|
const subDuplicateExists = subscriptions.findIndex((sub) => {
|
|
|
|
return sub.id === subscription.id || sub.name === subscription.name
|
|
|
|
})
|
|
|
|
|
|
|
|
if (subExists === -1 && subDuplicateExists === -1) {
|
2020-11-01 22:23:06 +01:00
|
|
|
subscriptions.push(subscription)
|
|
|
|
}
|
|
|
|
|
|
|
|
count++
|
|
|
|
|
|
|
|
const progressPercentage = (count / textDecode.length) * 100
|
|
|
|
this.setProgressBarPercentage(progressPercentage)
|
|
|
|
|
|
|
|
if (count === textDecode.length) {
|
|
|
|
primaryProfile.subscriptions = primaryProfile.subscriptions.concat(subscriptions)
|
|
|
|
this.updateProfile(primaryProfile)
|
|
|
|
|
|
|
|
if (subscriptions.length < count) {
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.One or more subscriptions were unable to be imported')
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.All subscriptions have been successfully imported')
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
this.updateShowProgressBar(false)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2021-08-25 09:47:21 +02:00
|
|
|
importCsvYouTubeSubscriptions: async function () {
|
|
|
|
const options = {
|
|
|
|
properties: ['openFile'],
|
|
|
|
filters: [
|
|
|
|
{
|
|
|
|
name: 'Database File',
|
|
|
|
extensions: ['csv']
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
const response = await this.showOpenDialog(options)
|
|
|
|
if (response.canceled || response.filePaths.length === 0) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const filePath = response.filePaths[0]
|
|
|
|
this.handleYoutubeCsvImportFile(filePath)
|
|
|
|
},
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
importYouTubeSubscriptions: async function () {
|
2020-11-01 22:23:06 +01:00
|
|
|
const options = {
|
|
|
|
properties: ['openFile'],
|
|
|
|
filters: [
|
|
|
|
{
|
|
|
|
name: 'Database File',
|
|
|
|
extensions: ['json']
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const response = await this.showOpenDialog(options)
|
|
|
|
if (response.canceled || response.filePaths.length === 0) {
|
|
|
|
return
|
|
|
|
}
|
2020-11-01 22:23:06 +01:00
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const filePath = response.filePaths[0]
|
|
|
|
this.handleYoutubeImportFile(filePath)
|
2020-11-01 22:23:06 +01:00
|
|
|
},
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
importOpmlYouTubeSubscriptions: async function () {
|
2020-09-07 00:12:25 +02:00
|
|
|
const options = {
|
|
|
|
properties: ['openFile'],
|
|
|
|
filters: [
|
|
|
|
{
|
|
|
|
name: 'Database File',
|
2020-09-29 01:53:59 +02:00
|
|
|
extensions: ['opml', 'xml']
|
2020-09-07 00:12:25 +02:00
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const response = await this.showOpenDialog(options)
|
|
|
|
if (response.canceled || response.filePaths.length === 0) {
|
|
|
|
return
|
|
|
|
}
|
2020-09-07 00:12:25 +02:00
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const filePath = response.filePaths[0]
|
2020-09-12 22:59:49 +02:00
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
fs.readFile(filePath, async (err, data) => {
|
|
|
|
if (err) {
|
|
|
|
const message = this.$t('Settings.Data Settings.Unable to read file')
|
|
|
|
this.showToast({
|
|
|
|
message: `${message}: ${err}`
|
2020-09-07 00:12:25 +02:00
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
opmlToJSON(data).then((json) => {
|
|
|
|
let feedData = json.children[0].children
|
2020-09-07 00:12:25 +02:00
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
if (typeof feedData === 'undefined') {
|
|
|
|
if (json.title.includes('gPodder')) {
|
|
|
|
feedData = json.children
|
|
|
|
} else {
|
|
|
|
const message = this.$t('Settings.Data Settings.Invalid subscriptions file')
|
|
|
|
this.showToast({
|
|
|
|
message: message
|
|
|
|
})
|
2020-09-07 00:12:25 +02:00
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
return
|
|
|
|
}
|
2020-09-07 00:12:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
|
|
|
|
const subscriptions = []
|
|
|
|
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.This might take a while, please wait')
|
|
|
|
})
|
|
|
|
|
|
|
|
this.updateShowProgressBar(true)
|
|
|
|
this.setProgressBarPercentage(0)
|
|
|
|
|
2020-09-12 22:59:49 +02:00
|
|
|
let count = 0
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
feedData.forEach(async (channel, index) => {
|
|
|
|
const channelId = channel.xmlurl.replace('https://www.youtube.com/feeds/videos.xml?channel_id=', '')
|
2021-08-25 09:47:21 +02:00
|
|
|
const subExists = primaryProfile.subscriptions.findIndex((sub) => {
|
|
|
|
return sub.id === channelId
|
|
|
|
})
|
|
|
|
if (subExists === -1) {
|
|
|
|
let channelInfo
|
|
|
|
if (this.backendPreference === 'invidious') {
|
|
|
|
channelInfo = await this.getChannelInfoInvidious(channelId)
|
|
|
|
} else {
|
|
|
|
channelInfo = await this.getChannelInfoLocal(channelId)
|
2020-09-12 22:59:49 +02:00
|
|
|
}
|
|
|
|
|
2021-08-25 09:47:21 +02:00
|
|
|
if (typeof channelInfo.author !== 'undefined') {
|
|
|
|
const subscription = {
|
|
|
|
id: channelId,
|
|
|
|
name: channelInfo.author,
|
|
|
|
thumbnail: channelInfo.authorThumbnails[1].url
|
|
|
|
}
|
2020-09-26 03:56:54 +02:00
|
|
|
subscriptions.push(subscription)
|
|
|
|
}
|
2020-09-07 00:12:25 +02:00
|
|
|
}
|
|
|
|
|
2020-09-12 22:59:49 +02:00
|
|
|
count++
|
2020-09-07 00:12:25 +02:00
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const progressPercentage = (count / feedData.length) * 100
|
2020-09-07 00:12:25 +02:00
|
|
|
this.setProgressBarPercentage(progressPercentage)
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
if (count === feedData.length) {
|
2020-09-07 00:12:25 +02:00
|
|
|
primaryProfile.subscriptions = primaryProfile.subscriptions.concat(subscriptions)
|
|
|
|
this.updateProfile(primaryProfile)
|
|
|
|
|
2020-09-12 22:59:49 +02:00
|
|
|
if (subscriptions.length < count) {
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.One or more subscriptions were unable to be imported')
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.All subscriptions have been successfully imported')
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-09-07 00:12:25 +02:00
|
|
|
this.updateShowProgressBar(false)
|
|
|
|
}
|
|
|
|
})
|
2021-05-22 01:49:48 +02:00
|
|
|
}).catch((err) => {
|
|
|
|
console.log(err)
|
|
|
|
console.log('error reading')
|
|
|
|
const message = this.$t('Settings.Data Settings.Invalid subscriptions file')
|
|
|
|
this.showToast({
|
|
|
|
message: `${message}: ${err}`
|
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
importNewPipeSubscriptions: async function () {
|
|
|
|
const options = {
|
|
|
|
properties: ['openFile'],
|
|
|
|
filters: [
|
|
|
|
{
|
|
|
|
name: 'Database File',
|
|
|
|
extensions: ['json']
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
const response = await this.showOpenDialog(options)
|
|
|
|
if (response.canceled || response.filePaths.length === 0) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const filePath = response.filePaths[0]
|
|
|
|
|
|
|
|
fs.readFile(filePath, async (err, data) => {
|
|
|
|
if (err) {
|
|
|
|
const message = this.$t('Settings.Data Settings.Unable to read file')
|
|
|
|
this.showToast({
|
|
|
|
message: `${message}: ${err}`
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const newPipeData = JSON.parse(data)
|
|
|
|
|
|
|
|
if (typeof newPipeData.subscriptions === 'undefined') {
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.Invalid subscriptions file')
|
|
|
|
})
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-23 17:42:04 +02:00
|
|
|
const newPipeSubscriptions = newPipeData.subscriptions.filter((channel, index) => {
|
|
|
|
return channel.service_id === 0
|
|
|
|
})
|
2021-05-22 01:49:48 +02:00
|
|
|
|
|
|
|
const primaryProfile = JSON.parse(JSON.stringify(this.profileList[0]))
|
|
|
|
const subscriptions = []
|
|
|
|
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.This might take a while, please wait')
|
|
|
|
})
|
|
|
|
|
|
|
|
this.updateShowProgressBar(true)
|
|
|
|
this.setProgressBarPercentage(0)
|
|
|
|
|
|
|
|
let count = 0
|
|
|
|
|
|
|
|
newPipeSubscriptions.forEach(async (channel, index) => {
|
|
|
|
const channelId = channel.url.replace(/https:\/\/(www\.)?youtube\.com\/channel\//, '')
|
2021-08-25 09:47:21 +02:00
|
|
|
const subExists = primaryProfile.subscriptions.findIndex((sub) => {
|
|
|
|
return sub.id === channelId
|
|
|
|
})
|
2021-05-22 01:49:48 +02:00
|
|
|
|
2021-08-25 09:47:21 +02:00
|
|
|
if (subExists === -1) {
|
|
|
|
let channelInfo
|
|
|
|
if (this.backendPreference === 'invidious') {
|
|
|
|
channelInfo = await this.getChannelInfoInvidious(channelId)
|
|
|
|
} else {
|
|
|
|
channelInfo = await this.getChannelInfoLocal(channelId)
|
2021-05-22 01:49:48 +02:00
|
|
|
}
|
|
|
|
|
2021-08-25 09:47:21 +02:00
|
|
|
if (typeof channelInfo.author !== 'undefined') {
|
|
|
|
const subscription = {
|
|
|
|
id: channelId,
|
|
|
|
name: channelInfo.author,
|
|
|
|
thumbnail: channelInfo.authorThumbnails[1].url
|
|
|
|
}
|
2021-05-22 01:49:48 +02:00
|
|
|
subscriptions.push(subscription)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
count++
|
|
|
|
|
|
|
|
const progressPercentage = (count / newPipeSubscriptions.length) * 100
|
|
|
|
this.setProgressBarPercentage(progressPercentage)
|
|
|
|
|
|
|
|
if (count === newPipeSubscriptions.length) {
|
|
|
|
primaryProfile.subscriptions = primaryProfile.subscriptions.concat(subscriptions)
|
|
|
|
this.updateProfile(primaryProfile)
|
|
|
|
|
|
|
|
if (subscriptions.length < count) {
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.One or more subscriptions were unable to be imported')
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.All subscriptions have been successfully imported')
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
this.updateShowProgressBar(false)
|
|
|
|
}
|
2020-09-07 00:12:25 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
exportSubscriptions: function (option) {
|
|
|
|
this.showExportSubscriptionsPrompt = false
|
|
|
|
|
|
|
|
if (option === null) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (option) {
|
|
|
|
case 'freetube':
|
|
|
|
this.exportFreeTubeSubscriptions()
|
|
|
|
break
|
2021-08-25 09:47:21 +02:00
|
|
|
case 'youtubenew':
|
|
|
|
this.exportCsvYouTubeSubscriptions()
|
|
|
|
break
|
2020-09-07 00:12:25 +02:00
|
|
|
case 'youtube':
|
|
|
|
this.exportYouTubeSubscriptions()
|
|
|
|
break
|
2020-11-01 22:23:06 +01:00
|
|
|
case 'youtubeold':
|
|
|
|
this.exportOpmlYouTubeSubscriptions()
|
|
|
|
break
|
2020-09-07 00:12:25 +02:00
|
|
|
case 'newpipe':
|
|
|
|
this.exportNewPipeSubscriptions()
|
|
|
|
break
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2021-04-21 06:09:06 +02:00
|
|
|
exportFreeTubeSubscriptions: async function () {
|
|
|
|
await this.compactProfiles()
|
2021-05-22 01:49:48 +02:00
|
|
|
const userData = await this.getUserDataPath()
|
2020-09-07 00:12:25 +02:00
|
|
|
const subscriptionsDb = `${userData}/profiles.db`
|
2021-08-25 09:47:21 +02:00
|
|
|
const date = new Date().toISOString().split('T')[0]
|
|
|
|
const exportFileName = 'freetube-subscriptions-' + date + '.db'
|
2020-09-07 00:12:25 +02:00
|
|
|
|
|
|
|
const options = {
|
|
|
|
defaultPath: exportFileName,
|
|
|
|
filters: [
|
|
|
|
{
|
|
|
|
name: 'Database File',
|
|
|
|
extensions: ['db']
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const response = await this.showSaveDialog(options)
|
|
|
|
if (response.canceled || response.filePath === '') {
|
|
|
|
// User canceled the save dialog
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const filePath = response.filePath
|
|
|
|
|
|
|
|
fs.readFile(subscriptionsDb, (readErr, data) => {
|
|
|
|
if (readErr) {
|
|
|
|
const message = this.$t('Settings.Data Settings.Unable to read file')
|
|
|
|
this.showToast({
|
|
|
|
message: `${message}: ${readErr}`
|
|
|
|
})
|
2020-09-07 00:12:25 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
fs.writeFile(filePath, data, (writeErr) => {
|
|
|
|
if (writeErr) {
|
|
|
|
const message = this.$t('Settings.Data Settings.Unable to write file')
|
2020-09-07 00:12:25 +02:00
|
|
|
this.showToast({
|
2021-05-22 01:49:48 +02:00
|
|
|
message: `${message}: ${writeErr}`
|
2020-09-07 00:12:25 +02:00
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.Subscriptions have been successfully exported')
|
2020-09-07 00:12:25 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
exportYouTubeSubscriptions: async function () {
|
2021-08-25 09:47:21 +02:00
|
|
|
const date = new Date().toISOString().split('T')[0]
|
|
|
|
const exportFileName = 'youtube-subscriptions-' + date + '.json'
|
2020-11-01 22:23:06 +01:00
|
|
|
|
|
|
|
const options = {
|
|
|
|
defaultPath: exportFileName,
|
|
|
|
filters: [
|
|
|
|
{
|
|
|
|
name: 'Database File',
|
|
|
|
extensions: ['json']
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
const subscriptionsObject = this.profileList[0].subscriptions.map((channel) => {
|
|
|
|
const object = {
|
|
|
|
contentDetails: {
|
|
|
|
activityType: 'all',
|
|
|
|
newItemCount: 0,
|
|
|
|
totalItemCount: 0
|
|
|
|
},
|
|
|
|
etag: '',
|
|
|
|
id: '',
|
|
|
|
kind: 'youtube#subscription',
|
|
|
|
snippet: {
|
|
|
|
channelId: channel.id,
|
|
|
|
description: '',
|
|
|
|
publishedAt: new Date(),
|
|
|
|
resourceId: {
|
|
|
|
channelId: channel.id,
|
|
|
|
kind: 'youtube#channel'
|
|
|
|
},
|
|
|
|
thumbnails: {
|
|
|
|
default: {
|
|
|
|
url: channel.thumbnail
|
|
|
|
},
|
|
|
|
high: {
|
|
|
|
url: channel.thumbnail
|
|
|
|
},
|
|
|
|
medium: {
|
|
|
|
url: channel.thumbnail
|
|
|
|
}
|
|
|
|
},
|
|
|
|
title: channel.name
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return object
|
|
|
|
})
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const response = await this.showSaveDialog(options)
|
|
|
|
if (response.canceled || response.filePath === '') {
|
|
|
|
// User canceled the save dialog
|
|
|
|
return
|
|
|
|
}
|
2020-11-01 22:23:06 +01:00
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const filePath = response.filePath
|
2020-11-01 22:23:06 +01:00
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
fs.writeFile(filePath, JSON.stringify(subscriptionsObject), (writeErr) => {
|
|
|
|
if (writeErr) {
|
|
|
|
const message = this.$t('Settings.Data Settings.Unable to write file')
|
2020-11-01 22:23:06 +01:00
|
|
|
this.showToast({
|
2021-05-22 01:49:48 +02:00
|
|
|
message: `${message}: ${writeErr}`
|
2020-11-01 22:23:06 +01:00
|
|
|
})
|
2021-05-22 01:49:48 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.Subscriptions have been successfully exported')
|
2020-11-01 22:23:06 +01:00
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
exportOpmlYouTubeSubscriptions: async function () {
|
2021-08-25 09:47:21 +02:00
|
|
|
const date = new Date().toISOString().split('T')[0]
|
|
|
|
const exportFileName = 'youtube-subscriptions-' + date + '.opml'
|
2020-09-07 00:12:25 +02:00
|
|
|
|
|
|
|
const options = {
|
|
|
|
defaultPath: exportFileName,
|
|
|
|
filters: [
|
|
|
|
{
|
|
|
|
name: 'Database File',
|
|
|
|
extensions: ['opml']
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
let opmlData = '<opml version="1.1"><body><outline text="YouTube Subscriptions" title="YouTube Subscriptions">'
|
|
|
|
const endingOpmlString = '</outline></body></opml>'
|
|
|
|
|
|
|
|
let count = 0
|
|
|
|
|
|
|
|
this.profileList[0].subscriptions.forEach((channel) => {
|
|
|
|
const channelOpmlString = `<outline text="${channel.name}" title="${channel.name}" type="rss" xmlUrl="https://www.youtube.com/feeds/videos.xml?channel_id=${channel.id}"/>`
|
|
|
|
count++
|
|
|
|
opmlData += channelOpmlString
|
|
|
|
|
|
|
|
if (count === this.profileList[0].subscriptions.length) {
|
|
|
|
opmlData += endingOpmlString
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const response = await this.showSaveDialog(options)
|
|
|
|
if (response.canceled || response.filePath === '') {
|
|
|
|
// User canceled the save dialog
|
|
|
|
return
|
|
|
|
}
|
2020-09-07 00:12:25 +02:00
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const filePath = response.filePath
|
2020-09-07 00:12:25 +02:00
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
fs.writeFile(filePath, opmlData, (writeErr) => {
|
|
|
|
if (writeErr) {
|
|
|
|
const message = this.$t('Settings.Data Settings.Unable to write file')
|
2020-09-07 00:12:25 +02:00
|
|
|
this.showToast({
|
2021-05-22 01:49:48 +02:00
|
|
|
message: `${message}: ${writeErr}`
|
2020-09-07 00:12:25 +02:00
|
|
|
})
|
2021-05-22 01:49:48 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.Subscriptions have been successfully exported')
|
2020-09-07 00:12:25 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2021-08-25 09:47:21 +02:00
|
|
|
exportCsvYouTubeSubscriptions: async function () {
|
|
|
|
const date = new Date().toISOString().split('T')[0]
|
|
|
|
const exportFileName = 'youtube-subscriptions-' + date + '.csv'
|
2020-09-07 00:12:25 +02:00
|
|
|
|
2021-08-25 09:47:21 +02:00
|
|
|
const options = {
|
|
|
|
defaultPath: exportFileName,
|
|
|
|
filters: [
|
|
|
|
{
|
|
|
|
name: 'Database File',
|
|
|
|
extensions: ['csv']
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
let exportText = 'Channel ID,Channel URL,Channel title\n'
|
|
|
|
this.profileList[0].subscriptions.forEach((channel) => {
|
|
|
|
const channelUrl = `https://www.youtube.com/channel/${channel.id}`
|
|
|
|
exportText += `${channel.id},${channelUrl},${channel.name}\n`
|
|
|
|
})
|
|
|
|
exportText += '\n'
|
|
|
|
const response = await this.showSaveDialog(options)
|
|
|
|
if (response.canceled || response.filePath === '') {
|
|
|
|
// User canceled the save dialog
|
|
|
|
return
|
2020-09-07 00:12:25 +02:00
|
|
|
}
|
|
|
|
|
2021-08-25 09:47:21 +02:00
|
|
|
const filePath = response.filePath
|
|
|
|
fs.writeFile(filePath, exportText, (writeErr) => {
|
|
|
|
if (writeErr) {
|
|
|
|
const message = this.$t('Settings.Data Settings.Unable to write file')
|
|
|
|
this.showToast({
|
|
|
|
message: `${message}: ${writeErr}`
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
2020-09-07 00:12:25 +02:00
|
|
|
|
2021-08-25 09:47:21 +02:00
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.Subscriptions have been successfully exported')
|
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
2020-09-07 00:12:25 +02:00
|
|
|
|
2021-08-25 09:47:21 +02:00
|
|
|
exportNewPipeSubscriptions: async function () {
|
|
|
|
const date = new Date().toISOString().split('T')[0]
|
|
|
|
const exportFileName = 'newpipe-subscriptions-' + date + '.json'
|
2020-09-07 00:12:25 +02:00
|
|
|
|
|
|
|
const options = {
|
|
|
|
defaultPath: exportFileName,
|
|
|
|
filters: [
|
|
|
|
{
|
|
|
|
name: 'Database File',
|
|
|
|
extensions: ['json']
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
|
|
|
const newPipeObject = {
|
|
|
|
app_version: '0.19.8',
|
|
|
|
app_version_int: 953,
|
|
|
|
subscriptions: []
|
|
|
|
}
|
|
|
|
|
|
|
|
this.profileList[0].subscriptions.forEach((channel) => {
|
|
|
|
const channelUrl = `https://www.youtube.com/channel/${channel.id}`
|
|
|
|
const subscription = {
|
|
|
|
service_id: 0,
|
|
|
|
url: channelUrl,
|
|
|
|
name: channel.name
|
|
|
|
}
|
|
|
|
|
|
|
|
newPipeObject.subscriptions.push(subscription)
|
|
|
|
})
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const response = await this.showSaveDialog(options)
|
|
|
|
if (response.canceled || response.filePath === '') {
|
|
|
|
// User canceled the save dialog
|
|
|
|
return
|
|
|
|
}
|
2020-09-07 00:12:25 +02:00
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const filePath = response.filePath
|
2020-09-07 00:12:25 +02:00
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
fs.writeFile(filePath, JSON.stringify(newPipeObject), (writeErr) => {
|
|
|
|
if (writeErr) {
|
|
|
|
const message = this.$t('Settings.Data Settings.Unable to write file')
|
2020-09-07 00:12:25 +02:00
|
|
|
this.showToast({
|
2021-05-22 01:49:48 +02:00
|
|
|
message: `${message}: ${writeErr}`
|
2020-09-07 00:12:25 +02:00
|
|
|
})
|
2021-05-22 01:49:48 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.Subscriptions have been successfully exported')
|
2020-09-07 00:12:25 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
checkForLegacySubscriptions: async function () {
|
|
|
|
let dbLocation = await this.getUserDataPath()
|
2020-10-08 23:35:36 +02:00
|
|
|
dbLocation = dbLocation + '/subscriptions.db'
|
|
|
|
this.handleFreetubeImportFile(dbLocation)
|
|
|
|
fs.unlink(dbLocation, (err) => {
|
|
|
|
if (err) {
|
|
|
|
console.log(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
importHistory: async function () {
|
2020-09-07 00:12:25 +02:00
|
|
|
const options = {
|
|
|
|
properties: ['openFile'],
|
|
|
|
filters: [
|
|
|
|
{
|
|
|
|
name: 'Database File',
|
|
|
|
extensions: ['db']
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const response = await this.showOpenDialog(options)
|
|
|
|
if (response.canceled || response.filePaths.length === 0) {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const filePath = response.filePaths[0]
|
|
|
|
|
|
|
|
fs.readFile(filePath, async (err, data) => {
|
|
|
|
if (err) {
|
|
|
|
const message = this.$t('Settings.Data Settings.Unable to read file')
|
|
|
|
this.showToast({
|
|
|
|
message: `${message}: ${err}`
|
|
|
|
})
|
2020-09-07 00:12:25 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
let textDecode = new TextDecoder('utf-8').decode(data)
|
|
|
|
textDecode = textDecode.split('\n')
|
|
|
|
textDecode.pop()
|
2020-09-07 00:12:25 +02:00
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
textDecode.forEach((history) => {
|
|
|
|
const historyData = JSON.parse(history)
|
|
|
|
// We would technically already be done by the time the data is parsed,
|
|
|
|
// however we want to limit the possibility of malicious data being sent
|
|
|
|
// to the app, so we'll only grab the data we need here.
|
|
|
|
const requiredKeys = [
|
|
|
|
'_id',
|
|
|
|
'author',
|
|
|
|
'authorId',
|
|
|
|
'description',
|
|
|
|
'isLive',
|
|
|
|
'lengthSeconds',
|
|
|
|
'paid',
|
|
|
|
'published',
|
|
|
|
'timeWatched',
|
|
|
|
'title',
|
|
|
|
'type',
|
|
|
|
'videoId',
|
|
|
|
'viewCount',
|
|
|
|
'watchProgress'
|
|
|
|
]
|
2020-09-07 00:12:25 +02:00
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const historyObject = {}
|
2020-09-07 00:12:25 +02:00
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
Object.keys(historyData).forEach((key) => {
|
|
|
|
if (!requiredKeys.includes(key)) {
|
2020-09-07 00:12:25 +02:00
|
|
|
this.showToast({
|
2021-05-22 01:49:48 +02:00
|
|
|
message: `Unknown data key: ${key}`
|
2020-09-07 00:12:25 +02:00
|
|
|
})
|
|
|
|
} else {
|
2021-05-22 01:49:48 +02:00
|
|
|
historyObject[key] = historyData[key]
|
2020-09-07 00:12:25 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
if (Object.keys(historyObject).length < (requiredKeys.length - 2)) {
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.History object has insufficient data, skipping item')
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
this.updateHistory(historyObject)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.All watched history has been successfully imported')
|
2020-09-07 00:12:25 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2021-04-21 06:09:06 +02:00
|
|
|
exportHistory: async function () {
|
|
|
|
await this.compactHistory()
|
2021-05-22 01:49:48 +02:00
|
|
|
const userData = await this.getUserDataPath()
|
2020-09-07 00:12:25 +02:00
|
|
|
const historyDb = `${userData}/history.db`
|
2021-08-25 09:47:21 +02:00
|
|
|
const date = new Date().toISOString().split('T')[0]
|
|
|
|
const exportFileName = 'freetube-history-' + date + '.db'
|
2020-09-07 00:12:25 +02:00
|
|
|
|
|
|
|
const options = {
|
|
|
|
defaultPath: exportFileName,
|
|
|
|
filters: [
|
|
|
|
{
|
|
|
|
name: 'Database File',
|
|
|
|
extensions: ['db']
|
|
|
|
}
|
|
|
|
]
|
|
|
|
}
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
const response = await this.showSaveDialog(options)
|
|
|
|
if (response.canceled || response.filePath === '') {
|
|
|
|
// User canceled the save dialog
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const filePath = response.filePath
|
|
|
|
|
|
|
|
fs.readFile(historyDb, (readErr, data) => {
|
|
|
|
if (readErr) {
|
|
|
|
const message = this.$t('Settings.Data Settings.Unable to read file')
|
|
|
|
this.showToast({
|
|
|
|
message: `${message}: ${readErr}`
|
|
|
|
})
|
2020-09-07 00:12:25 +02:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
fs.writeFile(filePath, data, (writeErr) => {
|
|
|
|
if (writeErr) {
|
|
|
|
const message = this.$t('Settings.Data Settings.Unable to write file')
|
2020-09-07 00:12:25 +02:00
|
|
|
this.showToast({
|
2021-05-22 01:49:48 +02:00
|
|
|
message: `${message}: ${writeErr}`
|
2020-09-07 00:12:25 +02:00
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2021-05-22 01:49:48 +02:00
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Settings.Data Settings.All watched history has been successfully exported')
|
2020-09-07 00:12:25 +02:00
|
|
|
})
|
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2020-09-12 10:00:18 +02:00
|
|
|
async convertOldFreeTubeFormatToNew(oldData) {
|
|
|
|
const convertedData = []
|
|
|
|
for (const channel of oldData) {
|
2020-10-07 19:23:18 +02:00
|
|
|
const listOfProfilesAlreadyAdded = []
|
2020-09-12 10:00:18 +02:00
|
|
|
for (const profile of channel.profile) {
|
|
|
|
let index = convertedData.findIndex(p => p.name === profile.value)
|
|
|
|
if (index === -1) { // profile doesn't exist yet
|
|
|
|
const randomBgColor = await this.getRandomColor()
|
|
|
|
const contrastyTextColor = await this.calculateColorLuminance(randomBgColor)
|
|
|
|
convertedData.push({
|
|
|
|
name: profile.value,
|
|
|
|
bgColor: randomBgColor,
|
|
|
|
textColor: contrastyTextColor,
|
|
|
|
subscriptions: [],
|
|
|
|
_id: channel._id
|
|
|
|
})
|
|
|
|
index = convertedData.length - 1
|
2020-10-07 19:23:18 +02:00
|
|
|
} else if (listOfProfilesAlreadyAdded.indexOf(index) !== -1) {
|
|
|
|
continue
|
2020-09-12 10:00:18 +02:00
|
|
|
}
|
2020-10-07 19:23:18 +02:00
|
|
|
listOfProfilesAlreadyAdded.push(index)
|
2020-09-12 10:00:18 +02:00
|
|
|
convertedData[index].subscriptions.push({
|
|
|
|
id: channel.channelId,
|
|
|
|
name: channel.channelName,
|
|
|
|
thumbnail: channel.channelThumbnail
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return convertedData
|
|
|
|
},
|
|
|
|
|
2020-09-07 00:12:25 +02:00
|
|
|
getChannelInfoInvidious: function (channelId) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
const subscriptionsPayload = {
|
|
|
|
resource: 'channels',
|
|
|
|
id: channelId,
|
|
|
|
params: {}
|
|
|
|
}
|
|
|
|
|
|
|
|
this.invidiousAPICall(subscriptionsPayload).then((response) => {
|
|
|
|
resolve(response)
|
|
|
|
}).catch((err) => {
|
|
|
|
console.log(err)
|
|
|
|
const errorMessage = this.$t('Invidious API Error (Click to copy)')
|
|
|
|
this.showToast({
|
2020-09-16 14:58:39 +02:00
|
|
|
message: `${errorMessage}: ${err.responseJSON.error}`,
|
2020-09-07 00:12:25 +02:00
|
|
|
time: 10000,
|
|
|
|
action: () => {
|
2020-09-16 14:58:39 +02:00
|
|
|
navigator.clipboard.writeText(err.responseJSON.error)
|
2020-09-07 00:12:25 +02:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2020-09-16 14:58:39 +02:00
|
|
|
if (this.backendFallback && this.backendPreference === 'invidious') {
|
2020-09-07 00:12:25 +02:00
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Falling back to the local API')
|
|
|
|
})
|
|
|
|
resolve(this.getChannelInfoLocal(channelId))
|
|
|
|
} else {
|
|
|
|
resolve([])
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
getChannelInfoLocal: function (channelId) {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
ytch.getChannelInfo(channelId, 'latest').then(async (response) => {
|
|
|
|
resolve(response)
|
|
|
|
}).catch((err) => {
|
|
|
|
console.log(err)
|
|
|
|
const errorMessage = this.$t('Local API Error (Click to copy)')
|
|
|
|
this.showToast({
|
|
|
|
message: `${errorMessage}: ${err}`,
|
|
|
|
time: 10000,
|
|
|
|
action: () => {
|
|
|
|
navigator.clipboard.writeText(err)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2020-09-16 14:58:39 +02:00
|
|
|
if (this.backendFallback && this.backendPreference === 'local') {
|
2020-09-07 00:12:25 +02:00
|
|
|
this.showToast({
|
|
|
|
message: this.$t('Falling back to the Invidious API')
|
|
|
|
})
|
|
|
|
resolve(this.getChannelInfoInvidious(channelId))
|
|
|
|
} else {
|
|
|
|
resolve([])
|
|
|
|
}
|
|
|
|
})
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
...mapActions([
|
|
|
|
'invidiousAPICall',
|
|
|
|
'updateProfile',
|
2021-04-21 06:09:06 +02:00
|
|
|
'compactProfiles',
|
2020-09-07 00:12:25 +02:00
|
|
|
'updateShowProgressBar',
|
|
|
|
'updateHistory',
|
2021-04-21 06:09:06 +02:00
|
|
|
'compactHistory',
|
2020-09-12 10:00:18 +02:00
|
|
|
'showToast',
|
|
|
|
'getRandomColor',
|
2021-05-22 01:49:48 +02:00
|
|
|
'calculateColorLuminance',
|
|
|
|
'showOpenDialog',
|
|
|
|
'showSaveDialog',
|
|
|
|
'getUserDataPath'
|
2020-09-07 00:12:25 +02:00
|
|
|
]),
|
|
|
|
|
|
|
|
...mapMutations([
|
|
|
|
'setProgressBarPercentage'
|
|
|
|
])
|
|
|
|
}
|
|
|
|
})
|