diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 5e4e8b3f2b0e5..0000000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -# These are supported funding model platforms - -liberapay: FreeTube diff --git a/.github/workflows/flatpak.yml b/.github/workflows/flatpak.yml index 9f926e9737198..dcb395c41cd27 100644 --- a/.github/workflows/flatpak.yml +++ b/.github/workflows/flatpak.yml @@ -77,25 +77,25 @@ jobs: date +"%Y-%m-%d" >> $GITHUB_ENV echo 'EOF' >> $GITHUB_ENV - name: Update x64 File Location in yml File - uses: mikefarah/yq@4.0.0-beta1 + uses: mikefarah/yq@v4.44.2 with: # The Command which should be run - cmd: yq w -i io.freetubeapp.FreeTube.yml modules[0].sources[0].url 'https://github.com/FreeTubeApp/FreeTube/releases/download/v${{ steps.sub.outputs.result }}-beta/freetube-${{ steps.sub.outputs.result }}-linux-portable-x64.zip' + cmd: yq -i '.modules[0].sources[0].url = "https://github.com/FreeTubeApp/FreeTube/releases/download/v${{ steps.sub.outputs.result }}-beta/freetube-${{ steps.sub.outputs.result }}-linux-portable-x64.zip"' io.freetubeapp.FreeTube.yml - name: Update x64 Hash in yml File - uses: mikefarah/yq@4.0.0-beta1 + uses: mikefarah/yq@v4.44.2 with: # The Command which should be run - cmd: yq w -i io.freetubeapp.FreeTube.yml modules[0].sources[0].sha256 ${{ env.HASH_X64 }} + cmd: yq -i '.modules[0].sources[0].sha256 = "${{ env.HASH_X64 }}"' io.freetubeapp.FreeTube.yml - name: Update ARM File Location in yml File - uses: mikefarah/yq@4.0.0-beta1 + uses: mikefarah/yq@v4.44.2 with: # The Command which should be run - cmd: yq w -i io.freetubeapp.FreeTube.yml modules[0].sources[1].url 'https://github.com/FreeTubeApp/FreeTube/releases/download/v${{ steps.sub.outputs.result }}-beta/freetube-${{ steps.sub.outputs.result }}-linux-portable-arm64.zip' + cmd: yq -i '.modules[0].sources[1].url = "https://github.com/FreeTubeApp/FreeTube/releases/download/v${{ steps.sub.outputs.result }}-beta/freetube-${{ steps.sub.outputs.result }}-linux-portable-arm64.zip"' io.freetubeapp.FreeTube.yml - name: Update ARM Hash in yml File - uses: mikefarah/yq@4.0.0-beta1 + uses: mikefarah/yq@v4.44.2 with: # The Command which should be run - cmd: yq w -i io.freetubeapp.FreeTube.yml modules[0].sources[1].sha256 ${{ env.HASH_ARM64 }} + cmd: yq -i '.modules[0].sources[1].sha256 = "${{ env.HASH_ARM64 }}"' io.freetubeapp.FreeTube.yml - name: Add Patch Notes to XML File run: xmlstarlet ed -L -i /application/releases/release[1] -t elem -n releaseTMP -v "" -i //releaseTMP -t attr -n version -v "${{ steps.sub.outputs.result }} Beta" -i //releaseTMP -t attr -n date -v "${{ env.CURRENT_DATE }}" -s //releaseTMP -t elem -n url -v "" -s //releaseTMP/url -t text -n "" -v "https://github.com/FreeTubeApp/FreeTube/releases/tag/v${{ steps.sub.outputs.result }}-beta" -r //releaseTMP -v "release" io.freetubeapp.FreeTube.metainfo.xml - name: Remove Release Files diff --git a/.github/workflows/report.yml b/.github/workflows/report.yml deleted file mode 100644 index 834da028dd957..0000000000000 --- a/.github/workflows/report.yml +++ /dev/null @@ -1,70 +0,0 @@ -# This is a basic workflow to help you get started with Actions - -name: Project Board Automation - -on: - issues: - types: [closed, deleted, reopened, opened] - -jobs: - assign-issues-to-projects: - runs-on: ubuntu-latest - steps: - - # For bug reports - - name: New bug issue - uses: alex-page/github-project-automation-plus@v0.9.0 - if: contains(github.event.issue.labels.*.name, 'bug') && github.event.action == 'opened' - with: - project: Bug Reports - column: To assign - repo-token: ${{ secrets.PUSH_TOKEN }} - action: update - - - name: Bug issue closed - uses: alex-page/github-project-automation-plus@v0.9.0 - if: github.event.action == 'closed' || github.event.action == 'deleted' - with: - action: delete - project: Bug Reports - column: To assign - repo-token: ${{ secrets.PUSH_TOKEN }} - - - name: Bug issue reopened - uses: alex-page/github-project-automation-plus@v0.9.0 - if: contains(github.event.issue.labels.*.name, 'bug') && github.event.action == 'reopened' - with: - project: Bug Reports - column: To assign - repo-token: ${{ secrets.PUSH_TOKEN }} - action: update - - # For feature requests - - name: New feature issue - uses: alex-page/github-project-automation-plus@v0.9.0 - if: contains(github.event.issue.labels.*.name, 'enhancement') && github.event.action == 'opened' - with: - project: Feature Requests - column: To assign - repo-token: ${{ secrets.PUSH_TOKEN }} - action: update - - - name: Feature request issue closed - uses: alex-page/github-project-automation-plus@v0.9.0 - if: github.event.action == 'closed' || github.event.action == 'deleted' - with: - action: delete - project: Feature Requests - column: To assign - repo-token: ${{ secrets.PUSH_TOKEN }} - - - name: Feature request issue reopened - uses: alex-page/github-project-automation-plus@v0.9.0 - if: contains(github.event.issue.labels.*.name, 'enhancement') && github.event.action == 'reopened' - with: - project: Feature Requests - column: To assign - repo-token: ${{ secrets.PUSH_TOKEN }} - action: update - - diff --git a/README.md b/README.md index 45101488864b8..5a42b6c49d877 100644 --- a/README.md +++ b/README.md @@ -150,14 +150,10 @@ If you ever have any questions, feel free to ask it on our [Discussions](https:/ > Don't forget to check out the [rules](https://docs.freetubeapp.io/community/matrix/) before joining. ## Donate -If you enjoy using FreeTube, you're welcome to leave a donation using the following methods. - -* [FreeTube on Liberapay](https://liberapay.com/FreeTube) +If you enjoy using FreeTube, you're welcome to leave a donation using the following method. * Bitcoin Address: `1Lih7Ho5gnxb1CwPD4o59ss78pwo2T91eS` -* Monero Address: `48WyAPdjwc6VokeXACxSZCFeKEXBiYPV6GjfvBsfg4CrUJ95LLCQSfpM9pvNKy5GE5H4hNaw99P8RZyzmaU9kb1pD7kzhCB` - While your donations are much appreciated, only donate if you really want to. Donations are used for keeping the website up and running and eventual code signing costs. > [!TIP] diff --git a/_scripts/webpack.renderer.config.js b/_scripts/webpack.renderer.config.js index 638d69a4c10a5..6d5c90c66d47e 100644 --- a/_scripts/webpack.renderer.config.js +++ b/_scripts/webpack.renderer.config.js @@ -150,8 +150,9 @@ const config = { resolve: { alias: { vue$: 'vue/dist/vue.runtime.esm.js', + 'portal-vue$': 'portal-vue/dist/portal-vue.esm.js', - 'DB_HANDLERS_ELECTRON_RENDERER_OR_WEB$': path.resolve(__dirname, '../src/datastores/handlers/electron.js'), + DB_HANDLERS_ELECTRON_RENDERER_OR_WEB$: path.resolve(__dirname, '../src/datastores/handlers/electron.js'), 'youtubei.js$': 'youtubei.js/web', diff --git a/_scripts/webpack.web.config.js b/_scripts/webpack.web.config.js index 792a099c4eed4..abbd517891e87 100644 --- a/_scripts/webpack.web.config.js +++ b/_scripts/webpack.web.config.js @@ -159,8 +159,9 @@ const config = { resolve: { alias: { vue$: 'vue/dist/vue.runtime.esm.js', + 'portal-vue$': 'portal-vue/dist/portal-vue.esm.js', - 'DB_HANDLERS_ELECTRON_RENDERER_OR_WEB$': path.resolve(__dirname, '../src/datastores/handlers/web.js'), + DB_HANDLERS_ELECTRON_RENDERER_OR_WEB$: path.resolve(__dirname, '../src/datastores/handlers/web.js'), // video.js's mpd-parser uses @xmldom/xmldom so that it can support both node and web browsers // As FreeTube only runs in electron and web browsers, we can use the native DOMParser class, instead of the "polyfill" diff --git a/package.json b/package.json index 02f4ad1a06d4f..60fed0161ddf4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "freetube", "productName": "FreeTube", "description": "A private YouTube client", - "version": "0.20.0", + "version": "0.21.1", "license": "AGPL-3.0-or-later", "main": "./dist/main.js", "private": true, @@ -61,9 +61,9 @@ "@seald-io/nedb": "^4.0.4", "@silvermine/videojs-quality-selector": "^1.3.1", "autolinker": "^4.0.0", - "electron-context-menu": "^4.0.0", + "electron-context-menu": "^4.0.1", "lodash.debounce": "^4.0.8", - "marked": "^12.0.2", + "marked": "^13.0.2", "path-browserify": "^1.0.1", "portal-vue": "^2.1.7", "process": "^0.11.10", @@ -79,55 +79,55 @@ "vue-observe-visibility": "^1.0.0", "vue-router": "^3.6.5", "vuex": "^3.6.2", - "youtubei.js": "^10.0.0" + "youtubei.js": "^10.1.0" }, "devDependencies": { - "@babel/core": "^7.24.7", - "@babel/eslint-parser": "^7.24.7", + "@babel/core": "^7.24.9", + "@babel/eslint-parser": "^7.24.8", "@babel/plugin-proposal-class-properties": "^7.18.6", - "@babel/preset-env": "^7.24.7", + "@babel/preset-env": "^7.24.8", "@double-great/stylelint-a11y": "^3.0.2", "@intlify/eslint-plugin-vue-i18n": "^3.0.0", "babel-loader": "^9.1.3", "copy-webpack-plugin": "^12.0.2", "css-loader": "^7.1.2", "css-minimizer-webpack-plugin": "^7.0.0", - "electron": "^31.0.1", + "electron": "^31.2.0", "electron-builder": "^24.13.3", "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "eslint-config-standard": "^17.1.0", "eslint-plugin-import": "^2.29.1", "eslint-plugin-jsonc": "^2.16.0", - "eslint-plugin-n": "^17.8.1", + "eslint-plugin-n": "^17.9.0", "eslint-plugin-prettier": "^5.1.3", - "eslint-plugin-promise": "^6.2.0", - "eslint-plugin-unicorn": "^53.0.0", - "eslint-plugin-vue": "^9.26.0", - "eslint-plugin-vuejs-accessibility": "^2.3.0", + "eslint-plugin-promise": "^6.4.0", + "eslint-plugin-unicorn": "^54.0.0", + "eslint-plugin-vue": "^9.27.0", + "eslint-plugin-vuejs-accessibility": "^2.3.1", "eslint-plugin-yml": "^1.14.0", "html-webpack-plugin": "^5.6.0", "js-yaml": "^4.1.0", "json-minimizer-webpack-plugin": "^5.0.0", - "lefthook": "^1.6.15", + "lefthook": "^1.7.2", "mini-css-extract-plugin": "^2.9.0", - "npm-run-all2": "^6.2.0", - "postcss": "^8.4.38", + "npm-run-all2": "^6.2.2", + "postcss": "^8.4.39", "postcss-scss": "^4.0.9", "prettier": "^2.8.8", - "rimraf": "^5.0.7", - "sass": "^1.77.4", + "rimraf": "^6.0.1", + "sass": "^1.77.8", "sass-loader": "^14.2.1", - "stylelint": "^16.6.1", - "stylelint-config-sass-guidelines": "^11.1.0", - "stylelint-config-standard": "^36.0.0", + "stylelint": "^16.7.0", + "stylelint-config-sass-guidelines": "^12.0.0", + "stylelint-config-standard": "^36.0.1", "stylelint-high-performance-animation": "^1.10.0", "stylelint-use-logical-spec": "^5.0.1", "tree-kill": "1.2.2", "vue-devtools": "^5.1.4", "vue-eslint-parser": "^9.4.3", "vue-loader": "^15.10.0", - "webpack": "^5.91.0", + "webpack": "^5.93.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4", "yaml-eslint-parser": "^1.2.3" diff --git a/src/constants.js b/src/constants.js index 36ad4ee471e17..7fc27d8e342ff 100644 --- a/src/constants.js +++ b/src/constants.js @@ -55,6 +55,11 @@ const DBActions = { UPDATE_PLAYLIST: 'db-action-history-update-playlist', }, + PROFILES: { + ADD_CHANNEL: 'db-action-profiles-add-channel', + REMOVE_CHANNEL: 'db-action-profiles-remove-channel' + }, + PLAYLISTS: { UPSERT_VIDEO: 'db-action-playlists-upsert-video-by-playlist-name', UPSERT_VIDEOS: 'db-action-playlists-upsert-videos-by-playlist-name', @@ -77,6 +82,11 @@ const SyncEvents = { UPDATE_PLAYLIST: 'sync-history-update-playlist', }, + PROFILES: { + ADD_CHANNEL: 'sync-profiles-add-channel', + REMOVE_CHANNEL: 'sync-profiles-remove-channel' + }, + PLAYLISTS: { UPSERT_VIDEO: 'sync-playlists-upsert-video', DELETE_VIDEO: 'sync-playlists-delete-video', @@ -98,9 +108,6 @@ const SEARCH_CHAR_LIMIT = 100 // Displayed on the about page and used in the main.js file to only allow bitcoin URLs with this wallet address to be opened const ABOUT_BITCOIN_ADDRESS = '1Lih7Ho5gnxb1CwPD4o59ss78pwo2T91eS' -// Displayed on the about page and used in the main.js file to only allow monero URLs with this wallet address to be opened -const ABOUT_MONERO_ADDRESS = '48WyAPdjwc6VokeXACxSZCFeKEXBiYPV6GjfvBsfg4CrUJ95LLCQSfpM9pvNKy5GE5H4hNaw99P8RZyzmaU9kb1pD7kzhCB' - export { IpcChannels, DBActions, @@ -110,5 +117,4 @@ export { PLAYLIST_HEIGHT_FORCE_LIST_THRESHOLD, SEARCH_CHAR_LIMIT, ABOUT_BITCOIN_ADDRESS, - ABOUT_MONERO_ADDRESS } diff --git a/src/datastores/handlers/base.js b/src/datastores/handlers/base.js index b630327899ee1..4a7db5cbb8c3d 100644 --- a/src/datastores/handlers/base.js +++ b/src/datastores/handlers/base.js @@ -86,6 +86,36 @@ class Profiles { return db.profiles.updateAsync({ _id: profile._id }, profile, { upsert: true }) } + static addChannelToProfiles(channel, profileIds) { + if (profileIds.length === 1) { + return db.profiles.updateAsync( + { _id: profileIds[0] }, + { $push: { subscriptions: channel } } + ) + } else { + return db.profiles.updateAsync( + { _id: { $in: profileIds } }, + { $push: { subscriptions: channel } }, + { multi: true } + ) + } + } + + static removeChannelFromProfiles(channelId, profileIds) { + if (profileIds.length === 1) { + return db.profiles.updateAsync( + { _id: profileIds[0] }, + { $pull: { subscriptions: { id: channelId } } } + ) + } else { + return db.profiles.updateAsync( + { _id: { $in: profileIds } }, + { $pull: { subscriptions: { id: channelId } } }, + { multi: true } + ) + } + } + static delete(id) { return db.profiles.removeAsync({ _id: id }) } diff --git a/src/datastores/handlers/electron.js b/src/datastores/handlers/electron.js index 6f57a7d83ba10..cc0b473a3b990 100644 --- a/src/datastores/handlers/electron.js +++ b/src/datastores/handlers/electron.js @@ -89,6 +89,26 @@ class Profiles { ) } + static addChannelToProfiles(channel, profileIds) { + return ipcRenderer.invoke( + IpcChannels.DB_PROFILES, + { + action: DBActions.PROFILES.ADD_CHANNEL, + data: { channel, profileIds } + } + ) + } + + static removeChannelFromProfiles(channelId, profileIds) { + return ipcRenderer.invoke( + IpcChannels.DB_PROFILES, + { + action: DBActions.PROFILES.REMOVE_CHANNEL, + data: { channelId, profileIds } + } + ) + } + static delete(id) { return ipcRenderer.invoke( IpcChannels.DB_PROFILES, diff --git a/src/datastores/handlers/web.js b/src/datastores/handlers/web.js index d6073dc7039f3..93ffa3d68c8ff 100644 --- a/src/datastores/handlers/web.js +++ b/src/datastores/handlers/web.js @@ -59,6 +59,14 @@ class Profiles { return baseHandlers.profiles.upsert(profile) } + static addChannelToProfiles(channel, profileIds) { + return baseHandlers.profiles.addChannelToProfiles(channel, profileIds) + } + + static removeChannelFromProfiles(channelId, profileIds) { + return baseHandlers.profiles.removeChannelFromProfiles(channelId, profileIds) + } + static delete(id) { return baseHandlers.profiles.delete(id) } diff --git a/src/main/index.js b/src/main/index.js index 3b88eaab24429..07ed29a0ae565 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -11,7 +11,6 @@ import { DBActions, SyncEvents, ABOUT_BITCOIN_ADDRESS, - ABOUT_MONERO_ADDRESS } from '../constants' import * as baseHandlers from '../datastores/handlers/base' import { extractExpiryTimestamp, ImageCache } from './ImageCache' @@ -410,6 +409,8 @@ function runApp() { if (url.startsWith('https://www.youtube.com/youtubei/')) { requestHeaders['Sec-Fetch-Site'] = 'same-origin' + requestHeaders['Sec-Fetch-Mode'] = 'same-origin' + requestHeaders['X-Youtube-Bootstrap-Logged-In'] = 'false' } else { // YouTube doesn't send the Content-Type header for the media requests, so we shouldn't either delete requestHeaders['Content-Type'] @@ -882,8 +883,7 @@ function runApp() { parsedURL.protocol === 'tel:' || // Donation links on the about page - (parsedURL.protocol === 'bitcoin:' && parsedURL.pathname === ABOUT_BITCOIN_ADDRESS) || - (parsedURL.protocol === 'monero:' && parsedURL.pathname === ABOUT_MONERO_ADDRESS) + (parsedURL.protocol === 'bitcoin:' && parsedURL.pathname === ABOUT_BITCOIN_ADDRESS) ) { shell.openExternal(url) return true @@ -976,6 +976,13 @@ function runApp() { try { const contents = await asyncFs.readFile(filePath) + + // Probably a corrupted/broken cache entry, pretend it's absent + // A valid entry should be a few KB large + if (contents.byteLength < 500) { + return undefined + } + return contents.buffer } catch (e) { console.error(e) @@ -1124,6 +1131,24 @@ function runApp() { ) return null + case DBActions.PROFILES.ADD_CHANNEL: + await baseHandlers.profiles.addChannelToProfiles(data.channel, data.profileIds) + syncOtherWindows( + IpcChannels.SYNC_PROFILES, + event, + { event: SyncEvents.PROFILES.ADD_CHANNEL, data } + ) + return null + + case DBActions.PROFILES.REMOVE_CHANNEL: + await baseHandlers.profiles.removeChannelFromProfiles(data.channelId, data.profileIds) + syncOtherWindows( + IpcChannels.SYNC_PROFILES, + event, + { event: SyncEvents.PROFILES.REMOVE_CHANNEL, data } + ) + return null + case DBActions.GENERAL.DELETE: await baseHandlers.profiles.delete(data) syncOtherWindows( diff --git a/src/renderer/App.css b/src/renderer/App.css index 28607f039e3ff..866c01197bf6f 100644 --- a/src/renderer/App.css +++ b/src/renderer/App.css @@ -1,6 +1,6 @@ @font-face { font-family: Roboto; - src: url("assets/font/Roboto-Regular.ttf"); + src: url('assets/font/Roboto-Regular.ttf'); } .app { @@ -45,11 +45,13 @@ overflow-wrap: break-word; } -.fade-enter-active, .fade-leave-active { - transition: opacity .15s; +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.15s; } -.fade-enter, .fade-leave-to /* .fade-leave-active below version 2.1.8 */ { +.fade-enter, +.fade-leave-to /* .fade-leave-active below version 2.1.8 */ { opacity: 0; } diff --git a/src/renderer/App.js b/src/renderer/App.js index 95a780ca9d51f..e2ecc75e4140b 100644 --- a/src/renderer/App.js +++ b/src/renderer/App.js @@ -1,5 +1,5 @@ import { defineComponent } from 'vue' -import { mapActions, mapMutations } from 'vuex' +import { mapActions } from 'vuex' import FtFlexBox from './components/ft-flex-box/ft-flex-box.vue' import TopNav from './components/top-nav/top-nav.vue' import SideNav from './components/side-nav/side-nav.vue' @@ -536,10 +536,6 @@ export default defineComponent({ } }, - ...mapMutations([ - 'setInvidiousInstancesList' - ]), - ...mapActions([ 'grabUserSettings', 'grabAllProfiles', diff --git a/src/renderer/App.vue b/src/renderer/App.vue index fb54f93110337..759c91240926e 100644 --- a/src/renderer/App.vue +++ b/src/renderer/App.vue @@ -12,6 +12,7 @@ > + + + + + + + + + + + + + + + + + + + +