mirror of
https://git.stupid.fish/teidesu/scripts.git
synced 2025-07-28 02:32:11 +10:00
chore: update public repo
This commit is contained in:
parent
e0109980c0
commit
e7c9507247
25 changed files with 5364 additions and 0 deletions
20
scripts/auth/mtcute-login.ts
Normal file
20
scripts/auth/mtcute-login.ts
Normal file
|
@ -0,0 +1,20 @@
|
|||
import qrTerminal from 'qrcode-terminal'
|
||||
import { createTg } from '../../utils/telegram.ts'
|
||||
|
||||
const sessionName = process.argv[2]
|
||||
if (!sessionName) {
|
||||
console.error('Usage: mtcute-login.ts <session name>')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const tg = createTg(sessionName)
|
||||
|
||||
const self = await tg.start({
|
||||
qrCodeHandler(url, expires) {
|
||||
console.log(qrTerminal.generate(url, { small: true }))
|
||||
},
|
||||
})
|
||||
|
||||
console.log(`Logged in as ${self.displayName} (${self.id})`)
|
||||
|
||||
await tg.close()
|
105
scripts/infra/navidrome-find-duplicates.ts
Normal file
105
scripts/infra/navidrome-find-duplicates.ts
Normal file
|
@ -0,0 +1,105 @@
|
|||
import type { NavidromeSong } from '../../utils/navidrome.ts'
|
||||
import { createRequire } from 'node:module'
|
||||
|
||||
import { join } from 'node:path'
|
||||
import kuromoji from 'kuromoji'
|
||||
import { isKana, toRomaji } from 'wanakana'
|
||||
|
||||
import { fetchSongs, navidromeFfetch as ffetch } from '../../utils/navidrome.ts'
|
||||
|
||||
const WHITELIST_KEYS = new Set([
|
||||
// actual different tracks with the same title
|
||||
'["sorry about my face","untitled track"]',
|
||||
'["kooeetekumogeemusu","neko bushou sengoku emaki"]',
|
||||
'["eve","merufuakutorii"]',
|
||||
// todo
|
||||
'["arm","legend of zelda"]',
|
||||
'["arm","tomorrow heart beat ~ ashita anata ni dokkidoki☆ ~"]',
|
||||
'["dwat","rotladatormarf"]',
|
||||
'["fujiwara mari sai","zenbuatashinokawaiino"]',
|
||||
])
|
||||
|
||||
const moji = await new Promise<any>((resolve, reject) => {
|
||||
kuromoji.builder({
|
||||
dicPath: join(createRequire(import.meta.url).resolve('kuromoji/'), '../../dict'),
|
||||
}).build((err, tokenizer) => {
|
||||
if (err) return reject(err)
|
||||
resolve(tokenizer)
|
||||
})
|
||||
})
|
||||
|
||||
function clean(s: string) {
|
||||
const str = s.toLowerCase()
|
||||
.replace(/\(Explicit\)/i, '')
|
||||
.replace(/[!@#$%^&*()_+=[\]{}\\|/,.;':"<>`~-]/g, '')
|
||||
|
||||
if (str.match(/[\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\uFF00-\uFF9F\u4E00-\u9FAF\u3400-\u4DBF]/)) {
|
||||
// has japanese
|
||||
const tokens = moji.tokenize(str)
|
||||
|
||||
let res = ''
|
||||
|
||||
for (const token of tokens) {
|
||||
if (token.word_type === 'UNKNOWN') {
|
||||
res += isKana(token.surface_form) ? toRomaji(token.surface_form) : token.surface_form
|
||||
} else if (token.word_type === 'KNOWN') {
|
||||
res += `${toRomaji(token.reading)} `
|
||||
}
|
||||
}
|
||||
|
||||
return res.trimEnd()
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
const CHUNK_SIZE = 1000
|
||||
|
||||
function getSongKey(song: NavidromeSong) {
|
||||
return JSON.stringify([
|
||||
clean(song.artist),
|
||||
clean(song.title),
|
||||
])
|
||||
}
|
||||
|
||||
const seen = new Map<string, NavidromeSong[]>()
|
||||
|
||||
for (let offset = 0; ; offset += CHUNK_SIZE) {
|
||||
const songs = await fetchSongs(offset, CHUNK_SIZE)
|
||||
if (songs.length === 0) break
|
||||
|
||||
for (const song of songs) {
|
||||
const key = getSongKey(song)
|
||||
if (WHITELIST_KEYS.has(key)) continue
|
||||
let arr = seen.get(key)
|
||||
if (!arr) {
|
||||
arr = []
|
||||
seen.set(key, arr)
|
||||
}
|
||||
|
||||
arr.push(song)
|
||||
}
|
||||
|
||||
console.log('⌛ fetched chunk %d (%d items)', Math.floor(offset / CHUNK_SIZE), songs.length)
|
||||
}
|
||||
|
||||
const keysSorted = Array.from(seen.keys()).sort()
|
||||
|
||||
let duplicates = 0
|
||||
for (const key of keysSorted) {
|
||||
const arr = seen.get(key)!
|
||||
if (arr.length === 1) continue
|
||||
|
||||
duplicates += 1
|
||||
console.log()
|
||||
console.log('found duplicates for %s:', key)
|
||||
for (const song of arr) {
|
||||
console.log(' %s - %s (from %s - %s) (at %s)', song.artist, song.title, song.albumArtist, song.album, song.path)
|
||||
}
|
||||
}
|
||||
|
||||
if (duplicates === 0) {
|
||||
console.log('✅ no duplicates found')
|
||||
} else {
|
||||
console.log('🚨 %d duplicates found', duplicates)
|
||||
}
|
66
scripts/infra/navidrome-remux-m4a.ts
Normal file
66
scripts/infra/navidrome-remux-m4a.ts
Normal file
|
@ -0,0 +1,66 @@
|
|||
import { readFile, rm } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import { $ } from 'zx'
|
||||
import { downloadStream } from '../../utils/fetch.ts'
|
||||
import { getEnv } from '../../utils/misc.ts'
|
||||
import { fetchSongs } from '../../utils/navidrome.ts'
|
||||
import { WebdavClient } from '../../utils/webdav.ts'
|
||||
|
||||
const webdav = new WebdavClient({
|
||||
baseUrl: getEnv('NAVIDROME_WEBDAV_ENDPOINT'),
|
||||
username: getEnv('NAVIDROME_WEBDAV_USERNAME'),
|
||||
password: getEnv('NAVIDROME_WEBDAV_PASSWORD'),
|
||||
})
|
||||
|
||||
const CHUNK_SIZE = 1000
|
||||
for (let offset = 0; ; offset += CHUNK_SIZE) {
|
||||
const songs = await fetchSongs(offset, CHUNK_SIZE)
|
||||
if (songs.length === 0) break
|
||||
|
||||
for (const song of songs) {
|
||||
const ext = song.path.split('.').pop()!
|
||||
if (ext !== 'm4a') continue
|
||||
|
||||
console.log('❌ song %s is m4a, remuxing...', song.path)
|
||||
const webdavPath = song.path.replace('/music/s3/', '/')
|
||||
const res = await webdav.get(webdavPath).catch(() => null)
|
||||
|
||||
if (!res) {
|
||||
console.log(' ❌ failed to get %s', webdavPath)
|
||||
continue
|
||||
}
|
||||
|
||||
const tmpfile = join('assets', `${song.id}.m4a`)
|
||||
await downloadStream(res.body!, tmpfile)
|
||||
console.log(' - downloaded to %s', tmpfile)
|
||||
|
||||
const probe = await $`ffprobe -v error -show_entries stream=codec_type,codec_name,index:stream_tags=title,language -of json ${tmpfile}`.json()
|
||||
const audioStream = probe.streams.find(stream => stream.codec_type === 'audio')
|
||||
if (!audioStream) {
|
||||
console.log(' ❌ no audio stream found')
|
||||
await rm(tmpfile)
|
||||
continue
|
||||
}
|
||||
const codec = audioStream.codec_name
|
||||
|
||||
if (codec !== 'flac') {
|
||||
console.log(` ❌ audio stream is ${codec}, not flac, skipping`)
|
||||
await rm(tmpfile)
|
||||
continue
|
||||
}
|
||||
|
||||
console.log(' - audio stream is flac, remuxing')
|
||||
|
||||
// remux
|
||||
const remuxed = join('assets', `${song.id}.flac`)
|
||||
await rm(remuxed, { force: true })
|
||||
await $`ffmpeg -i ${tmpfile} -c:a copy ${remuxed}`.quiet(true)
|
||||
console.log(' - remuxed to %s', remuxed)
|
||||
await rm(tmpfile)
|
||||
|
||||
await webdav.put(webdavPath.replace('.m4a', '.flac'), await readFile(remuxed))
|
||||
await webdav.delete(webdavPath)
|
||||
console.log(' - uploaded to %s', webdavPath.replace('.m4a', '.flac'))
|
||||
await rm(remuxed)
|
||||
}
|
||||
}
|
39
scripts/infra/slskd-total-upload.ts
Normal file
39
scripts/infra/slskd-total-upload.ts
Normal file
|
@ -0,0 +1,39 @@
|
|||
import { filesize } from 'filesize'
|
||||
import { z } from 'zod'
|
||||
|
||||
import { ffetch } from '../../utils/fetch.ts'
|
||||
import { getEnv } from '../../utils/misc.ts'
|
||||
|
||||
const res = await ffetch('/api/v0/transfers/uploads', {
|
||||
baseUrl: getEnv('SLSKD_ENDPOINT'),
|
||||
headers: {
|
||||
cookie: getEnv('SLSKD_COOKIE'),
|
||||
},
|
||||
}).parsedJson(z.array(
|
||||
z.object({
|
||||
username: z.string(),
|
||||
directories: z.array(z.object({
|
||||
directory: z.string(),
|
||||
fileCount: z.number(),
|
||||
files: z.array(z.object({
|
||||
id: z.string(),
|
||||
filename: z.string(),
|
||||
state: z.string(),
|
||||
bytesTransferred: z.number(),
|
||||
})),
|
||||
})),
|
||||
}),
|
||||
))
|
||||
|
||||
let total = 0
|
||||
|
||||
for (const user of res) {
|
||||
for (const dir of user.directories) {
|
||||
for (const file of dir.files) {
|
||||
if (file.state !== 'Completed, Succeeded') continue
|
||||
total += file.bytesTransferred
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log(filesize(total))
|
58
scripts/media/deezer-art-fetcher.ts
Normal file
58
scripts/media/deezer-art-fetcher.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
import { iter } from '@fuman/utils'
|
||||
import { z } from 'zod'
|
||||
import { minimist, question } from 'zx'
|
||||
|
||||
import { downloadFile, ffetch } from '../../utils/fetch.ts'
|
||||
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
string: ['filename'],
|
||||
})
|
||||
|
||||
const query = args._[0] ?? await question('Search query (Artist - Album): ')
|
||||
|
||||
const data = await ffetch('https://api.deezer.com/search', {
|
||||
query: {
|
||||
q: query,
|
||||
limit: 15,
|
||||
},
|
||||
}).parsedJson(z.object({
|
||||
data: z.array(z.object({
|
||||
type: z.literal('track'),
|
||||
title: z.string(),
|
||||
artist: z.object({
|
||||
name: z.string(),
|
||||
}),
|
||||
album: z.object({
|
||||
id: z.number(),
|
||||
title: z.string(),
|
||||
cover_xl: z.string(),
|
||||
}),
|
||||
})),
|
||||
}))
|
||||
|
||||
const groupedByAlbum = new Map<number, typeof data['data']>()
|
||||
for (const result of data.data) {
|
||||
const albumId = result.album.id
|
||||
if (!groupedByAlbum.has(albumId)) {
|
||||
groupedByAlbum.set(albumId, [])
|
||||
}
|
||||
|
||||
groupedByAlbum.get(albumId)!.push(result)
|
||||
}
|
||||
|
||||
const idxToAlbum = new Map<number, number>()
|
||||
for (const [idx, [id, tracks]] of iter.enumerate(groupedByAlbum.entries())) {
|
||||
idxToAlbum.set(idx, id)
|
||||
console.log(`${idx + 1}. ${tracks[0].artist.name} - ${tracks[0].album.title}`)
|
||||
for (const track of tracks) {
|
||||
console.log(` ${track.title}`)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Enter number to download album art:')
|
||||
|
||||
const number = Number.parseInt(await question('[1] > ') || '1')
|
||||
|
||||
const artworkUrl = groupedByAlbum.get(idxToAlbum.get(number - 1)!)![0].album.cover_xl
|
||||
|
||||
await downloadFile(artworkUrl, args.filename ?? `assets/${query.replace(/\s/g, '_')}.jpg`)
|
129
scripts/media/ffmpeg-clipper.ts
Normal file
129
scripts/media/ffmpeg-clipper.ts
Normal file
|
@ -0,0 +1,129 @@
|
|||
import { rm } from 'node:fs/promises'
|
||||
|
||||
import { $, question } from 'zx'
|
||||
|
||||
import { fileExists } from '../../utils/fs.ts'
|
||||
|
||||
let filename = await question('filename >')!
|
||||
const startTs = await question('start timestamp >')
|
||||
const endTs = await question('end timestamp >')
|
||||
const outputFilename = await question('output filename [output.mp4] >') || 'assets/output.mp4'
|
||||
|
||||
if (filename[0] === '\'' && filename[filename.length - 1] === '\'') {
|
||||
filename = filename.slice(1, -1)
|
||||
}
|
||||
|
||||
const ffprobe = await $`ffprobe -v error -show_entries stream=codec_type,codec_name,index:stream_tags=title,language -of json ${filename}`.json()
|
||||
|
||||
async function chooseStream(type: string, options: any[], allowNone = false) {
|
||||
console.log(`Found ${type} streams:`)
|
||||
for (let i = 0; i < options.length; i++) {
|
||||
const stream = options[i]
|
||||
console.log(`[${i + 1}] (${stream.codec_name}, ${stream.tags.language}) ${stream.tags.title}`)
|
||||
}
|
||||
|
||||
if (allowNone) {
|
||||
console.log(`[0] No ${type}`)
|
||||
}
|
||||
|
||||
const res = await question(`select ${type} >`) || '0'
|
||||
if (res === '0' && allowNone) {
|
||||
return null
|
||||
}
|
||||
|
||||
const streamIndex = Number.parseInt(res)
|
||||
if (Number.isNaN(streamIndex) || streamIndex < 1 || streamIndex > options.length) {
|
||||
console.error('Invalid input')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
return streamIndex - 1
|
||||
}
|
||||
|
||||
const allVideos = ffprobe.streams.filter(stream => stream.codec_type === 'video')
|
||||
const allAudios = ffprobe.streams.filter(stream => stream.codec_type === 'audio')
|
||||
const allSubtitles = ffprobe.streams.filter(stream => stream.codec_type === 'subtitle')
|
||||
|
||||
let videoStream: number | null = null
|
||||
let audioStream: number | null = null
|
||||
let subtitleStream: number | null = null
|
||||
|
||||
if (allVideos.length > 1) {
|
||||
videoStream = await chooseStream('video', allVideos)
|
||||
} else if (allVideos.length > 0) {
|
||||
videoStream = 0
|
||||
} else {
|
||||
console.error('No video streams found')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
if (allAudios.length > 1) {
|
||||
audioStream = await chooseStream('audio', allAudios)
|
||||
} else if (allAudios.length > 0) {
|
||||
audioStream = 0
|
||||
} else {
|
||||
console.warn('No audio streams found, proceeding without audio')
|
||||
}
|
||||
|
||||
if (allSubtitles.length > 0) {
|
||||
subtitleStream = await chooseStream('subtitle', allSubtitles, true)
|
||||
}
|
||||
|
||||
const args: string[] = [
|
||||
'-i',
|
||||
filename,
|
||||
'-c:v',
|
||||
'libx264',
|
||||
'-map',
|
||||
`0:v:${videoStream}`,
|
||||
'-c:v',
|
||||
'libx264',
|
||||
]
|
||||
|
||||
if (audioStream !== null) {
|
||||
args.push('-map', `0:a:${audioStream}`)
|
||||
}
|
||||
|
||||
if (subtitleStream !== null) {
|
||||
const filenameEscaped = filename.replace(/'/g, "'\\\\\\''")
|
||||
args.push('-vf', `format=yuv420p,subtitles='${filenameEscaped}':si=${subtitleStream}`)
|
||||
} else {
|
||||
args.push('-vf', 'format=yuv420p')
|
||||
}
|
||||
|
||||
if (audioStream !== null) {
|
||||
args.push('-c:a', 'libopus')
|
||||
|
||||
if (allAudios[audioStream].codec_name === 'flac') {
|
||||
args.push('-b:a', '320k')
|
||||
}
|
||||
}
|
||||
|
||||
args.push(
|
||||
'-ss',
|
||||
startTs!,
|
||||
'-to',
|
||||
endTs!,
|
||||
outputFilename,
|
||||
)
|
||||
|
||||
if (await fileExists(outputFilename)) {
|
||||
const overwrite = await question('Output file already exists, overwrite? [y/N] >')
|
||||
if (overwrite?.toLowerCase() !== 'y') {
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
await rm(outputFilename)
|
||||
}
|
||||
|
||||
try {
|
||||
$.env.AV_LOG_FORCE_COLOR = 'true'
|
||||
await $`ffmpeg ${args}`
|
||||
} catch (e) {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const openDir = await question('open output directory? [Y/n] >')
|
||||
if (!openDir || openDir?.toLowerCase() === 'y') {
|
||||
await $`open -R ${outputFilename}`
|
||||
}
|
46
scripts/media/itunes-art-fetcher.ts
Normal file
46
scripts/media/itunes-art-fetcher.ts
Normal file
|
@ -0,0 +1,46 @@
|
|||
import { iter } from '@fuman/utils'
|
||||
import { z } from 'zod'
|
||||
import { minimist, question } from 'zx'
|
||||
|
||||
import { downloadFile, ffetch } from '../../utils/fetch.ts'
|
||||
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
string: ['entity', 'filename'],
|
||||
})
|
||||
|
||||
const entity = args.entity ?? 'album'
|
||||
const query = args._[0] ?? await question('Search query (Artist - Album): ')
|
||||
|
||||
const data = await ffetch('https://itunes.apple.com/search', {
|
||||
query: {
|
||||
term: query,
|
||||
entity,
|
||||
limit: 15,
|
||||
},
|
||||
}).parsedJson(z.object({
|
||||
results: z.array(z.object({
|
||||
kind: z.literal('song').optional(),
|
||||
artistName: z.string(),
|
||||
collectionName: z.string(),
|
||||
artworkUrl100: z.string(),
|
||||
releaseDate: z.string(),
|
||||
trackName: z.string().optional(),
|
||||
}).passthrough()),
|
||||
}))
|
||||
|
||||
for (const [i, result] of iter.enumerate(data.results)) {
|
||||
if (result.kind === 'song') {
|
||||
console.log(`${i + 1}. ${result.artistName} - ${result.trackName} (${result.collectionName}, ${new Date(result.releaseDate).toLocaleDateString('ru-RU')})`)
|
||||
continue
|
||||
}
|
||||
|
||||
console.log(`${i + 1}. ${result.artistName} - ${result.collectionName} (${new Date(result.releaseDate).toLocaleDateString('ru-RU')})`)
|
||||
}
|
||||
|
||||
console.log('Enter number to download album art:')
|
||||
|
||||
const number = Number.parseInt(await question('[1] > ') || '1')
|
||||
|
||||
const artworkUrl = data.results[number - 1].artworkUrl100.replace('100x100', '1500x1500')
|
||||
|
||||
await downloadFile(artworkUrl, args.filename ?? `assets/${query.replace(/\s/g, '_')}.jpg`)
|
63
scripts/media/itunes-artist-art-fetcher.ts
Normal file
63
scripts/media/itunes-artist-art-fetcher.ts
Normal file
|
@ -0,0 +1,63 @@
|
|||
import { iter } from '@fuman/utils'
|
||||
import { z } from 'zod'
|
||||
import { minimist, question } from 'zx'
|
||||
|
||||
import { downloadFile, ffetch } from '../../utils/fetch.ts'
|
||||
|
||||
const args = minimist(process.argv.slice(2), {
|
||||
string: ['filename'],
|
||||
})
|
||||
|
||||
const query = args._[0] ?? await question('Search query: ')
|
||||
|
||||
const data = await ffetch('https://itunes.apple.com/search', {
|
||||
query: {
|
||||
term: query,
|
||||
entity: 'musicArtist',
|
||||
limit: 15,
|
||||
},
|
||||
}).parsedJson(z.object({
|
||||
results: z.array(z.object({
|
||||
wrapperType: z.literal('artist'),
|
||||
artistName: z.string(),
|
||||
artistLinkUrl: z.string(),
|
||||
primaryGenreName: z.string().default('Unknown'),
|
||||
}).passthrough()),
|
||||
}))
|
||||
|
||||
for (const [i, result] of iter.enumerate(data.results)) {
|
||||
console.log(`${i + 1}. ${result.artistName} (${result.primaryGenreName})`)
|
||||
continue
|
||||
}
|
||||
|
||||
console.log('Enter number to download artist art:')
|
||||
|
||||
const number = Number.parseInt(await question('[1] > ') || '1')
|
||||
|
||||
const pageUrl = data.results[number - 1].artistLinkUrl
|
||||
const $ = await ffetch(pageUrl).cheerio()
|
||||
const pageData = JSON.parse($('#serialized-server-data').html()!)
|
||||
|
||||
const pageDataValidated = z.tuple([
|
||||
z.object({
|
||||
data: z.object({
|
||||
seoData: z.object({
|
||||
artworkUrl: z.string(),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
]).parse(pageData)
|
||||
|
||||
// {w}x{h}{c}.{f}
|
||||
const artworkUrl = pageDataValidated[0].data.seoData.artworkUrl
|
||||
.replace('{w}', '2500')
|
||||
.replace('{h}', '2500')
|
||||
.replace('{c}', 'cc')
|
||||
.replace('{f}', 'jpg')
|
||||
|
||||
if (artworkUrl === '/assets/meta/apple-music.png') {
|
||||
console.log('No artwork available')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
await downloadFile(artworkUrl, args.filename ?? `assets/${query.replace(/\s/g, '_')}.jpg`)
|
51
scripts/misc/update-forkgram.ts
Normal file
51
scripts/misc/update-forkgram.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
import { readFile } from 'node:fs/promises'
|
||||
import { join } from 'node:path'
|
||||
import plist from 'plist'
|
||||
import { z } from 'zod'
|
||||
import { $ } from 'zx'
|
||||
import { ffetch } from '../../utils/fetch.ts'
|
||||
|
||||
const latestVerInfo = await ffetch('https://api.github.com/repos/forkgram/tdesktop/releases/latest').parsedJson(
|
||||
z.object({
|
||||
tag_name: z.string().transform(v => v.replace(/^v/, '')),
|
||||
assets: z.array(z.object({
|
||||
name: z.string(),
|
||||
browser_download_url: z.string(),
|
||||
})),
|
||||
}),
|
||||
)
|
||||
|
||||
const INSTALL_PATH = '/Applications/Forkgram.app'
|
||||
|
||||
console.log('latest version:', latestVerInfo.tag_name)
|
||||
|
||||
const installedPlist = await readFile(join(INSTALL_PATH, 'Contents/Info.plist'), 'utf8')
|
||||
const installedPlistParsed = z.object({
|
||||
CFBundleShortVersionString: z.string(),
|
||||
}).parse(plist.parse(installedPlist))
|
||||
|
||||
console.log('installed version:', installedPlistParsed.CFBundleShortVersionString)
|
||||
|
||||
if (installedPlistParsed.CFBundleShortVersionString === latestVerInfo.tag_name) {
|
||||
console.log('✅ no update needed')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const arm64Asset = latestVerInfo.assets.find(asset => asset.name === 'Forkgram.macOS.no.auto-update_arm64.zip')
|
||||
if (!arm64Asset) {
|
||||
console.error('❌ no arm64 asset found')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
console.log('installing new version...')
|
||||
await $`curl -L ${arm64Asset.browser_download_url} -o /tmp/forkgram.zip`
|
||||
await $`unzip -o /tmp/forkgram.zip -d /tmp/forkgram`
|
||||
await $`kill -9 $(pgrep -f /Applications/Forkgram.app/Contents/MacOS/Telegram)`
|
||||
await $`rm -rf ${INSTALL_PATH}`
|
||||
await $`mv /tmp/forkgram/Telegram.app ${INSTALL_PATH}`
|
||||
await $`rm -rf /tmp/forkgram`
|
||||
await $`xattr -cr ${INSTALL_PATH}`
|
||||
|
||||
await $`open ${INSTALL_PATH}`
|
||||
|
||||
console.log('✅ done')
|
Loading…
Add table
Add a link
Reference in a new issue