This commit is contained in:
RockinChaos 2024-08-06 17:56:57 -07:00
commit d576ccded4
18 changed files with 655 additions and 386 deletions

View file

@ -39,4 +39,9 @@ jobs:
- name: Build and Publish
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# CSC_LINK: ${{ secrets.APPLE_SIGNING_CERT }}
# API_KEY: ${{ secrets.APPLE_API_KEY }}
# APPLE_API_KEY: apple.p8
# APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
# APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
run: cd electron && npm run publish

View file

@ -1,3 +1,4 @@
{
"npm.exclude": "**/git_modules/**"
"npm.exclude": "**/git_modules/**",
"java.configuration.updateBuildConfiguration": "interactive"
}

View file

@ -9,6 +9,7 @@ android/app/src/*
!android/app/src/main
android/app/src/main/*
!android/app/src/main/AndroidManifest.xml
!android/app/src/main/java
ios/
*.jks
*.pepk

View file

@ -13,7 +13,7 @@ versions.each (code) -> {
android {
namespace "watch.miru"
compileSdkVersion rootProject.ext.compileSdkVersion
compileSdk rootProject.ext.compileSdkVersion
defaultConfig {
applicationId "watch.miru"
minSdkVersion rootProject.ext.minSdkVersion

View file

@ -53,6 +53,7 @@
<!-- Permissions -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-feature android:name="android.software.leanback" android:required="false" />
<uses-feature android:name="android.hardware.touchscreen" android:required="false" />
</manifest>

View file

@ -0,0 +1,31 @@
package watch.miru;
import android.os.Bundle;
import android.webkit.ServiceWorkerClient;
import android.webkit.ServiceWorkerController;
import android.webkit.WebResourceRequest;
import android.webkit.WebResourceResponse;
import com.getcapacitor.BridgeActivity;
public class MainActivity extends BridgeActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
ServiceWorkerController swController = null;
swController = ServiceWorkerController.getInstance();
swController.setServiceWorkerClient(new ServiceWorkerClient() {
@Override
public WebResourceResponse shouldInterceptRequest(WebResourceRequest request) {
if (request.getUrl().toString().contains("index.html")) {
request.getRequestHeaders().put("Accept", "text/html");
}
return bridge.getLocalServer().shouldInterceptRequest(request);
}
});
}
}
}

View file

@ -2,13 +2,13 @@ ext {
minSdkVersion = 22
compileSdkVersion = 34
targetSdkVersion = 34
androidxActivityVersion = '1.7.0'
androidxActivityVersion = '1.8.0'
androidxAppCompatVersion = '1.6.1'
androidxCoordinatorLayoutVersion = '1.2.0'
androidxCoreVersion = '1.10.0'
androidxFragmentVersion = '1.5.6'
coreSplashScreenVersion = '1.0.0'
androidxWebkitVersion = '1.6.1'
androidxCoreVersion = '1.12.0'
androidxFragmentVersion = '1.6.2'
coreSplashScreenVersion = '1.0.1'
androidxWebkitVersion = '1.9.0'
junitVersion = '4.13.2'
androidxJunitVersion = '1.1.5'
androidxEspressoCoreVersion = '3.5.1'

View file

@ -9,7 +9,8 @@ const config = {
keystorePath: './watch.miru',
keystorePassword: '',
keystoreAlias: 'watch.miru'
}
},
webContentsDebuggingEnabled: true
},
plugins: {
SplashScreen: { launchShowDuration: 0 },

View file

@ -18,6 +18,7 @@
},
"devDependencies": {
"@capacitor/assets": "github:thaunknown/capacitor-assets",
"@capacitor/cli": "^6.1.1",
"cordova-res": "^0.15.4",
"nodejs-mobile-gyp": "^0.3.1",
"npm-run-all": "^4.1.5",
@ -25,16 +26,15 @@
"webpack-merge": "^5.10.0"
},
"dependencies": {
"@capacitor/android": "^5.5.1",
"@capacitor/app": "^5.0.6",
"@capacitor/browser": "^5.1.0",
"@capacitor/cli": "^5.5.1",
"@capacitor/core": "^5.5.1",
"@capacitor/ios": "^5.5.1",
"@capacitor/local-notifications": "5.0.8",
"@capacitor/status-bar": "^5.0.6",
"@capacitor/android": "^6.1.1",
"@capacitor/app": "^6.0.0",
"@capacitor/browser": "^6.0.1",
"@capacitor/core": "^6.1.1",
"@capacitor/ios": "^6.1.1",
"@capacitor/local-notifications": "^6.0.0",
"@capacitor/status-bar": "^6.0.0",
"capacitor-nodejs": "https://github.com/funniray/Capacitor-NodeJS/releases/download/nodejs-18/capacitor-nodejs-1.0.0-beta.6.tgz",
"capacitor-plugin-safe-area": "^2.0.5",
"capacitor-plugin-safe-area": "^2.0.6",
"common": "workspace:*",
"cordova-plugin-navigationbar": "^1.0.31",
"cordova-plugin-pip": "^0.0.2",

View file

@ -284,19 +284,19 @@ class AnilistClient {
// isAdult doesn't need an extra variable, as the title is the same regardless of type, so we re-use the same variable for adult and non-adult requests
/** @type {Record<`v${number}`, string>} */
const requestVariables = flattenedTitles.reduce((obj, { title, isAdult }, i) => {
if (isAdult) return obj
if (isAdult && i !== 0) return obj
obj[`v${i}`] = title
return obj
}, {})
const queryVariables = flattenedTitles.reduce((arr, { isAdult }, i) => {
if (isAdult) return arr
if (isAdult && i !== 0) return arr
arr.push(`$v${i}: String`)
return arr
}, []).join(', ')
const fragmentQueries = flattenedTitles.map(({ year, isAdult }, i) => /* js */`
v${i}: Page(perPage: 10) {
media(type: ANIME, search: $v${isAdult ? i - 1 : i}, status_in: [RELEASING, FINISHED], isAdult: ${!!isAdult} ${year ? `, seasonYear: ${year}` : ''}) {
media(type: ANIME, search: $v${(isAdult && i !== 0) ? i - 1 : i}, status_in: [RELEASING, FINISHED], isAdult: ${!!isAdult} ${year ? `, seasonYear: ${year}` : ''}) {
...med
}
}`)

View file

@ -25,7 +25,7 @@ export default new class AnimeResolver {
* @returns {string[]}
*/
alternativeTitles (title) {
const titles = []
const titles = new Set()
let modified = title
// preemptively change S2 into Season 2 or 2nd Season, otherwise this will have accuracy issues
@ -33,31 +33,31 @@ export default new class AnimeResolver {
if (seasonMatch) {
if (Number(seasonMatch[1]) === 1) { // if this is S1, remove the " S1" or " S01"
modified = title.replace(/ S(\d+)/, '')
titles.push(modified)
titles.add(modified)
} else {
modified = title.replace(/ S(\d+)/, ` ${Number(seasonMatch[1])}${postfix[Number(seasonMatch[1])] || 'th'} Season`)
titles.push(modified)
titles.push(title.replace(/ S(\d+)/, ` Season ${Number(seasonMatch[1])}`))
titles.add(modified)
titles.add(title.replace(/ S(\d+)/, ` Season ${Number(seasonMatch[1])}`))
}
} else {
titles.push(title)
titles.add(title)
}
// remove - :
const specialMatch = modified.match(/[-:]/g)
if (specialMatch) {
modified = modified.replace(/[-:]/g, '')
titles.push(modified)
modified = modified.replace(/[-:]/g, '').replace(/[ ]{2,}/, ' ')
titles.add(modified)
}
// remove (TV)
const tvMatch = modified.match(/\(TV\)/)
if (tvMatch) {
modified = modified.replace('(TV)', '')
titles.push(modified)
titles.add(modified)
}
return titles
return [...titles]
}
/**
@ -73,8 +73,10 @@ export default new class AnimeResolver {
return titleObjects
}).flat()
for (const [key, media] of await anilistClient.alSearchCompound(titleObjects)) {
this.animeNameCache[key] = media
for (const chunk of chunks(titleObjects, 62)) { // single title has a complexity of 8.1, al limits complexity to 500
for (const [key, media] of await anilistClient.alSearchCompound(chunk)) {
this.animeNameCache[key] = media
}
}
}
@ -97,16 +99,17 @@ export default new class AnimeResolver {
if (!fileName) return [{}]
const parseObjs = await anitomyscript(fileName)
const TYPE_EXCLUSIONS = ['ED', 'ENDING', 'NCED', 'NCOP', 'OP', 'OPENING', 'PREVIEW', 'PV']
/** @type {Record<string, import('anitomyscript').AnitomyResult>} */
const uniq = {}
for (const obj of parseObjs) {
const key = this.getCacheKeyForTitle(obj)
if (key in this.animeNameCache) continue
if (key in this.animeNameCache) continue // skip already resolved
if (obj.anime_type && TYPE_EXCLUSIONS.includes(obj.anime_type.toUpperCase())) continue // skip non-episode media
uniq[key] = obj
}
for (const chunk of chunks(Object.values(uniq), 50)) {
await this.findAnimesByTitle(chunk)
}
await this.findAnimesByTitle(Object.values(uniq))
const fileAnimes = []
for (const parseObj of parseObjs) {

View file

@ -231,11 +231,12 @@ export default class TorrentClient extends WebTorrent {
return
}
localStorage.setItem('lastFinished', 'false')
if (this.torrents.length) await this.remove(this.torrents[0])
if (this.torrents.length) {
await this.remove(this.torrents[0], { destroyStore: !this.settings.torrentPersist })
}
const torrent = await this.add(data, {
private: this.settings.torrentPeX,
path: this.torrentPath || undefined,
destroyStoreOnDestroy: !this.settings.torrentPersist,
skipVerify,
announce,
deselect: this.settings.torrentStreamedDownload

View file

@ -1524,6 +1524,12 @@
.seekbar {
font-size: 2rem !important;
}
.miniplayer .mobile-focus-target {
display: block !important;
}
.miniplayer .mobile-focus-target:focus-visible {
background: hsla(209, 100%, 55%, 0.3);
}
@media (pointer: none), (pointer: coarse) {
.bottom .ctrl[data-name='playPause'],
@ -1548,12 +1554,6 @@
.toggle-fullscreen {
display: none !important;
}
.miniplayer .mobile-focus-target {
display: block !important;
}
.miniplayer .mobile-focus-target:focus-visible {
background: hsla(209, 100%, 55%, 0.3);
}
}
</style>

View file

@ -1,30 +0,0 @@
const { notarize } = require('@electron/notarize')
const path = require('path')
exports.default = async function notarizing (context) {
if (context.electronPlatformName !== 'darwin' || process.env.CSC_IDENTITY_AUTO_DISCOVERY === 'false') {
console.log('Skipping notarization')
return
}
console.log('Notarizing...')
const appBundleId = context.packager.appInfo.info._configuration.appId
const appName = context.packager.appInfo.productFilename
const appPath = path.normalize(path.join(context.appOutDir, `${appName}.app`))
const appleId = process.env.APPLE_ID
const appleIdPassword = process.env.APPLE_ID_PASSWORD
if (!appleId) {
console.warn('Not notarizing: Missing APPLE_ID environment variable')
return
}
if (!appleIdPassword) {
console.warn('Not notarizing: Missing APPLE_ID_PASSWORD environment variable')
return
}
return notarize({
appBundleId,
appPath,
appleId,
appleIdPassword
})
}

View file

@ -1,6 +1,6 @@
{
"name": "Miru",
"version": "5.2.7",
"version": "5.2.14",
"private": true,
"author": "ThaUnknown_ <ThaUnknown@users.noreply.github.com>",
"description": "Stream anime torrents, real-time with no waiting for downloads.",
@ -20,9 +20,9 @@
"discord-rpc": "4.0.1",
"electron": "29.1.4",
"electron-builder": "^24.13.3",
"electron-log": "^5.1.5",
"electron-log": "^5.1.7",
"electron-updater": "^6.2.1",
"webpack-merge": "^5.10.0"
"webpack-merge": "^6.0.1"
},
"dependencies": {
"@paymoapp/electron-shutdown-handler": "^1.1.2",
@ -61,7 +61,6 @@
"repo": "miru"
}
],
"afterSign": "./buildResources/notarize.js",
"appId": "com.github.thaunknown.miru",
"productName": "Miru",
"files": [
@ -73,8 +72,10 @@
"defaultArch": "universal",
"singleArchFiles": "node_modules/+(register-scheme|utp-native|fs-native-extensions)/**",
"category": "public.app-category.video",
"darkModeSupport": true,
"icon": "buildResources/icon.icns",
"hardenedRuntime": true,
"notarize": false,
"entitlements": "buildResources/entitlements.mac.plist",
"target": [
{

View file

@ -48,7 +48,7 @@ export default class App {
discord = new Discord(this.mainWindow)
protocol = new Protocol(this.mainWindow)
updater = new Updater(this.mainWindow)
updater = new Updater(this.mainWindow, this.webtorrentWindow)
dialog = new Dialog(this.webtorrentWindow)
constructor () {
@ -134,11 +134,17 @@ export default class App {
this.webtorrentWindow.webContents.postMessage('torrentPath', store.get('torrentPath'))
sender.postMessage('port', null, [port2])
})
ipcMain.on('quit-and-install', () => {
if (this.updater.hasUpdate) {
this.destroy(true)
}
})
}
destroyed = false
async destroy () {
async destroy (forceRunAfter = false) {
if (this.destroyed) return
this.webtorrentWindow.webContents.postMessage('destroy', null)
await new Promise(resolve => {
@ -146,6 +152,6 @@ export default class App {
setTimeout(resolve, 5000).unref?.()
})
this.destroyed = true
if (!this.updater.install()) app.quit()
if (!this.updater.install(forceRunAfter)) app.quit()
}
}

View file

@ -1,6 +1,6 @@
import log from 'electron-log'
import { autoUpdater } from 'electron-updater'
import { ipcMain } from 'electron'
import { ipcMain, shell } from 'electron'
let hasUpdate = false
@ -13,10 +13,15 @@ ipcMain.on('update', () => {
autoUpdater.checkForUpdatesAndNotify()
export default class Updater {
window
torrentWindow
/**
* @param {import('electron').BrowserWindow} window
* @param {import('electron').BrowserWindow} torrentWindow
*/
constructor (window) {
constructor (window, torrentWindow) {
this.window = window
this.torrentWindow = torrentWindow
autoUpdater.on('update-available', () => {
window.webContents.send('update-available', true)
})
@ -24,19 +29,18 @@ export default class Updater {
hasUpdate = true
window.webContents.send('update-downloaded', true)
})
ipcMain.on('quit-and-install', () => {
if (hasUpdate) {
autoUpdater.quitAndInstall()
hasUpdate = false
}
})
}
install () {
install (forceRunAfter = false) {
if (hasUpdate) {
autoUpdater.quitAndInstall()
setImmediate(() => {
this.window.close()
this.torrentWindow.close()
autoUpdater.quitAndInstall(true, forceRunAfter)
})
if (process.platform === 'darwin') shell.openExternal('https://miru.watch/download')
hasUpdate = false
return true
}
}
}
}

File diff suppressed because it is too large Load diff