Compare commits
No commits in common. "master" and "legacy" have entirely different histories.
|
@ -1,4 +1,4 @@
|
||||||
{{- $pkg := env "PWD" | base | coalesce Package -}}
|
{{- $pkg := env "PWD" | base | coalesce Package -}}
|
||||||
package {{$pkg}}
|
package {{$pkg}}
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"go.uber.org/fx"
|
"go.uber.org/fx"
|
||||||
|
"gfx.cafe/util/go/fxplus"
|
||||||
)
|
)
|
||||||
|
|
||||||
type {{title $pkg}} struct {
|
type {{title $pkg}} struct {
|
||||||
|
@ -25,6 +26,7 @@ type Result struct {
|
||||||
fx.Out
|
fx.Out
|
||||||
|
|
||||||
Output *{{title $pkg}}
|
Output *{{title $pkg}}
|
||||||
|
Healther fxplus.Healther `group:"fxplus"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(p Params) (r Result, err error) {
|
func New(p Params) (r Result, err error) {
|
||||||
|
@ -32,5 +34,10 @@ func New(p Params) (r Result, err error) {
|
||||||
o.log = p.Log
|
o.log = p.Log
|
||||||
|
|
||||||
r.Output = o
|
r.Output = o
|
||||||
|
r.Healther = o
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *{{title $pkg}}) Health(ctx context.Context) (error) {
|
||||||
|
return nil
|
||||||
|
}
|
0
private_dot_local/script/executable_gamermode → .local/script/gamermode
Normal file → Executable file
0
private_dot_local/script/executable_gamermode → .local/script/gamermode
Normal file → Executable file
0
private_dot_local/script/executable_goinstallbinaries → .local/script/goinstallbinaries
Normal file → Executable file
0
private_dot_local/script/executable_goinstallbinaries → .local/script/goinstallbinaries
Normal file → Executable file
0
private_dot_local/script/executable_inotify-consumers → .local/script/inotify-consumers
Normal file → Executable file
0
private_dot_local/script/executable_inotify-consumers → .local/script/inotify-consumers
Normal file → Executable file
0
private_dot_local/script/executable_kubectl-ssh → .local/script/kubectl-ssh
Normal file → Executable file
0
private_dot_local/script/executable_kubectl-ssh → .local/script/kubectl-ssh
Normal file → Executable file
0
private_dot_local/script/executable_replace-subdir → .local/script/replace-subdir
Normal file → Executable file
0
private_dot_local/script/executable_replace-subdir → .local/script/replace-subdir
Normal file → Executable file
|
@ -1,2 +1,3 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
scrot --freeze '/home/a/shots/%F_%T_$wx$h.png' -s -e 'xclip -selection clipboard -t image/png -i $f'
|
scrot --freeze '/home/a/shots/%F_%T_$wx$h.png' -s -e 'xclip -selection clipboard -t image/png -i $f'
|
||||||
|
|
0
private_dot_local/script/executable_yadmsync → .local/script/yadmsync
Normal file → Executable file
0
private_dot_local/script/executable_yadmsync → .local/script/yadmsync
Normal file → Executable file
|
@ -1,965 +0,0 @@
|
||||||
-- mpv oscf modern
|
|
||||||
-- by maoiscat
|
|
||||||
-- github.com/maoiscat/
|
|
||||||
package.path = package.path .. ";".. os.getenv("HOME") .. "/.config/mpv/lib/oscf/?.lua"
|
|
||||||
require("expansion")
|
|
||||||
local assdraw = require 'mp.assdraw'
|
|
||||||
|
|
||||||
-- user options
|
|
||||||
opts = {
|
|
||||||
scale = 1, -- osc render scale
|
|
||||||
fixedHeight = false, -- true to allow osc scale with window
|
|
||||||
hideTimeout = 1, -- seconds untile osc hides, negative means never
|
|
||||||
fadeDuration = 0.5, -- seconds during fade out, negative means never
|
|
||||||
}
|
|
||||||
|
|
||||||
-- logo and message works out of box
|
|
||||||
addToIdleLayout('logo')
|
|
||||||
|
|
||||||
-- define styles
|
|
||||||
local styles = {
|
|
||||||
background = {
|
|
||||||
color = {'0', '0', '0', '0'},
|
|
||||||
alpha = {255, 255, 0, 0},
|
|
||||||
border = 140,
|
|
||||||
blur = 140,
|
|
||||||
},
|
|
||||||
tooltip = {
|
|
||||||
color = {'FFFFFF', 'FFFFFF', '0', '0'},
|
|
||||||
border = 0.5,
|
|
||||||
blur = 1,
|
|
||||||
fontsize = 18,
|
|
||||||
wrap = 2,
|
|
||||||
},
|
|
||||||
button1 = {
|
|
||||||
color1 = {'FFFFFF', 'FFFFFF', 'FFFFFF', 'FFFFFF'},
|
|
||||||
color2 = {'999999', '999999', '999999', '999999'},
|
|
||||||
fontsize = 36,
|
|
||||||
border = 0,
|
|
||||||
blur = 0,
|
|
||||||
font = 'material-design-iconic-font',
|
|
||||||
wrap = 2,
|
|
||||||
},
|
|
||||||
button2 = {
|
|
||||||
color1 = {'FFFFFF', 'FFFFFF', 'FFFFFF', 'FFFFFF'},
|
|
||||||
color2 = {'999999', '999999', '999999', '999999'},
|
|
||||||
border = 0,
|
|
||||||
blur = 0,
|
|
||||||
fontsize = 24,
|
|
||||||
font = 'material-design-iconic-font',
|
|
||||||
wrap = 2,
|
|
||||||
},
|
|
||||||
seekbarFg = {
|
|
||||||
color1 = {'E39C42', 'E39C42', '0', '0'},
|
|
||||||
color2 = {'999999', '999999', '0', '0'},
|
|
||||||
border = 0.5,
|
|
||||||
blur = 1,
|
|
||||||
},
|
|
||||||
seekbarBg = {
|
|
||||||
color = {'eeeeee', 'eeeeee', '0', '0'},
|
|
||||||
border = 0,
|
|
||||||
blur = 0,
|
|
||||||
},
|
|
||||||
volumeSlider = {
|
|
||||||
color = {'ffffff', '0', '0', '0'},
|
|
||||||
border = 0,
|
|
||||||
blur = 0,
|
|
||||||
},
|
|
||||||
time = {
|
|
||||||
color1 = {'ffffff', 'ffffff', '0', '0'},
|
|
||||||
color2 = {'eeeeee', 'eeeeee', '0', '0'},
|
|
||||||
border = 0,
|
|
||||||
blur = 0,
|
|
||||||
fontsize = 17,
|
|
||||||
},
|
|
||||||
title = {
|
|
||||||
color = {'ffffff', '0', '0', '0'},
|
|
||||||
border = 0.5,
|
|
||||||
blur = 1,
|
|
||||||
fontsize = 48,
|
|
||||||
wrap = 2,
|
|
||||||
},
|
|
||||||
winControl = {
|
|
||||||
color1 = {'ffffff', 'ffffff', '0', '0'},
|
|
||||||
color2 = {'eeeeee', 'eeeeee', '0', '0'},
|
|
||||||
border = 0.5,
|
|
||||||
blur = 1,
|
|
||||||
font = 'mpv-osd-symbols',
|
|
||||||
fontsize = 20,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
-- enviroment updater
|
|
||||||
-- this element updates shared vairables, sets active areas and starts event generators
|
|
||||||
local env
|
|
||||||
env = newElement('env')
|
|
||||||
env.layer = 1000
|
|
||||||
env.visible = false
|
|
||||||
env.updateTime = function()
|
|
||||||
dispatchEvent('time')
|
|
||||||
end
|
|
||||||
env.init = function(self)
|
|
||||||
self.slowTimer = mp.add_periodic_timer(0.25, self.updateTime) --use a slower timer to update playtime
|
|
||||||
-- event generators
|
|
||||||
mp.observe_property('track-list/count', 'native',
|
|
||||||
function(name, val)
|
|
||||||
if val==0 then return end
|
|
||||||
player.tracks = getTrackList()
|
|
||||||
player.playlist = getPlaylist()
|
|
||||||
player.chapters = getChapterList()
|
|
||||||
player.playlistPos = getPlaylistPos()
|
|
||||||
player.duration = mp.get_property_number('duration')
|
|
||||||
showOsc()
|
|
||||||
dispatchEvent('file-loaded')
|
|
||||||
end)
|
|
||||||
mp.observe_property('pause', 'bool',
|
|
||||||
function(name, val)
|
|
||||||
player.paused = val
|
|
||||||
dispatchEvent('pause')
|
|
||||||
end)
|
|
||||||
mp.observe_property('fullscreen', 'bool',
|
|
||||||
function(name, val)
|
|
||||||
player.fullscreen = val
|
|
||||||
dispatchEvent('fullscreen')
|
|
||||||
end)
|
|
||||||
mp.observe_property('window-maximized', 'bool',
|
|
||||||
function(name, val)
|
|
||||||
player.maximized = val
|
|
||||||
dispatchEvent('window-maximized')
|
|
||||||
end)
|
|
||||||
mp.observe_property('current-tracks/audio/id', 'number',
|
|
||||||
function(name, val)
|
|
||||||
if val then player.audioTrack = val
|
|
||||||
else player.audioTrack = 0
|
|
||||||
end
|
|
||||||
dispatchEvent('audio-changed')
|
|
||||||
end)
|
|
||||||
mp.observe_property('current-tracks/sub/id', 'number',
|
|
||||||
function(name, val)
|
|
||||||
if val then player.subTrack = val
|
|
||||||
else player.subTrack = 0
|
|
||||||
end
|
|
||||||
dispatchEvent('sub-changed')
|
|
||||||
end)
|
|
||||||
mp.observe_property('mute', 'bool',
|
|
||||||
function(name, val)
|
|
||||||
player.muted = val
|
|
||||||
dispatchEvent('mute')
|
|
||||||
end)
|
|
||||||
mp.observe_property('volume', 'number',
|
|
||||||
function(name, val)
|
|
||||||
player.volume = val
|
|
||||||
dispatchEvent('volume')
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
env.tick = function(self)
|
|
||||||
player.percentPos = mp.get_property_number('percent-pos')
|
|
||||||
player.timePos = mp.get_property_number('time-pos')
|
|
||||||
player.timeRem = mp.get_property_number('time-remaining')
|
|
||||||
return ''
|
|
||||||
end
|
|
||||||
env.responder['resize'] = function(self)
|
|
||||||
player.geo.refX = player.geo.width / 2
|
|
||||||
player.geo.refY = player.geo.height - 40
|
|
||||||
setPlayActiveArea('bg1', 0, player.geo.height - 120, player.geo.width, player.geo.height)
|
|
||||||
if player.fullscreen then
|
|
||||||
setPlayActiveArea('wc1', player.geo.width - 200, 0, player.geo.width, 48)
|
|
||||||
else
|
|
||||||
setPlayActiveArea('wc1', -1, -1, -1, -1)
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
env.responder['pause'] = function(self)
|
|
||||||
if player.idle then return end
|
|
||||||
if player.paused then
|
|
||||||
setVisibility('always')
|
|
||||||
else
|
|
||||||
setVisibility('normal')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
env.responder['idle'] = function(self)
|
|
||||||
if player.idle then
|
|
||||||
setVisibility('always')
|
|
||||||
else
|
|
||||||
setVisibility('normal')
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
env:init()
|
|
||||||
addToPlayLayout('env')
|
|
||||||
|
|
||||||
-- background
|
|
||||||
local ne
|
|
||||||
ne = newElement('background', 'box')
|
|
||||||
ne.geo.h = 1
|
|
||||||
ne.geo.an = 8
|
|
||||||
ne.layer = 5
|
|
||||||
-- DO NOT directly assign a shared style tabe!!
|
|
||||||
ne.style = clone(styles.background)
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.x = player.geo.refX
|
|
||||||
self.geo.y = player.geo.height
|
|
||||||
self.geo.w = player.geo.width
|
|
||||||
self.setPos(self)
|
|
||||||
self.render(self)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('background')
|
|
||||||
|
|
||||||
-- a shared tooltip
|
|
||||||
ne = newElement('tip', 'tooltip')
|
|
||||||
ne.layer = 20
|
|
||||||
ne.style = clone(styles.tooltip)
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('tip')
|
|
||||||
local tooltip = ne
|
|
||||||
|
|
||||||
-- playpause button
|
|
||||||
ne = newElement('btnPlay', 'button')
|
|
||||||
ne.layer = 10
|
|
||||||
ne.style = clone(styles.button1)
|
|
||||||
ne.geo.w = 45
|
|
||||||
ne.geo.h = 45
|
|
||||||
ne.geo.an = 5
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.x = player.geo.refX
|
|
||||||
self.geo.y = player.geo.refY
|
|
||||||
self:setPos()
|
|
||||||
self:setHitBox()
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_left_up'] = function(self, pos)
|
|
||||||
if self.enabled and self:isInside(pos) then
|
|
||||||
mp.commandv('cycle', 'pause')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['pause'] = function(self)
|
|
||||||
if player.paused then
|
|
||||||
self.text = '\xEF\x8E\xAA'
|
|
||||||
else
|
|
||||||
self.text = '\xEF\x8E\xA7'
|
|
||||||
end
|
|
||||||
self:render()
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('btnPlay')
|
|
||||||
|
|
||||||
|
|
||||||
-- skip back button
|
|
||||||
ne = newElement('btnBack', 'button')
|
|
||||||
ne.layer = 10
|
|
||||||
ne.style = clone(styles.button2)
|
|
||||||
ne.geo.w = 30
|
|
||||||
ne.geo.h = 24
|
|
||||||
ne.geo.an = 5
|
|
||||||
ne.text = '\xEF\x8E\xA0'
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.x = player.geo.refX - 60
|
|
||||||
self.geo.y = player.geo.refY
|
|
||||||
self:setPos()
|
|
||||||
self:setHitBox()
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_left_up'] = function(self, pos)
|
|
||||||
if self.enabled and self:isInside(pos) then
|
|
||||||
mp.commandv('seek', -5, 'relative', 'keyframes')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('btnBack')
|
|
||||||
|
|
||||||
|
|
||||||
-- skip forward button
|
|
||||||
ne = newElement('btnForward', 'btnBack')
|
|
||||||
ne.text = '\xEF\x8E\x9F'
|
|
||||||
ne.responder['mbtn_left_up'] = function(self, pos)
|
|
||||||
if self.enabled and self:isInside(pos) then
|
|
||||||
mp.commandv('seek', 5, 'relative', 'keyframes')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.x = player.geo.refX + 60
|
|
||||||
self.geo.y = player.geo.refY
|
|
||||||
self:setPos()
|
|
||||||
self:setHitBox()
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('btnForward')
|
|
||||||
|
|
||||||
|
|
||||||
-- play previous file button
|
|
||||||
ne = newElement('btnPrev', 'button')
|
|
||||||
ne.layer = 10
|
|
||||||
ne.style = clone(styles.button2)
|
|
||||||
ne.geo.w = 30
|
|
||||||
ne.geo.h = 24
|
|
||||||
ne.geo.an = 5
|
|
||||||
ne.text = '\xEF\x8E\xB5'
|
|
||||||
ne.responder['mbtn_left_up'] = function(self, pos)
|
|
||||||
if self.enabled and self:isInside(pos) then
|
|
||||||
mp.commandv('playlist-prev', 'weak')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.x = player.geo.refX - 120
|
|
||||||
self.geo.y = player.geo.refY
|
|
||||||
self:setPos()
|
|
||||||
self:setHitBox()
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['file-loaded'] = function(self)
|
|
||||||
if player.playlistPos <= 1 and player.loopPlaylist == 'no' then
|
|
||||||
self:disable()
|
|
||||||
else
|
|
||||||
self:enable()
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('btnPrev')
|
|
||||||
|
|
||||||
|
|
||||||
-- play next file button
|
|
||||||
ne = newElement('btnNext', 'btnPrev')
|
|
||||||
ne.text = '\xEF\x8E\xB4'
|
|
||||||
ne.responder['mbtn_left_up'] = function(self, pos)
|
|
||||||
if self.enabled and self:isInside(pos) then
|
|
||||||
mp.commandv('playlist-next', 'weak')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.x = player.geo.refX + 120
|
|
||||||
self.geo.y = player.geo.refY
|
|
||||||
self:setPos()
|
|
||||||
self:setHitBox()
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['file-loaded'] = function(self)
|
|
||||||
if player.playlistPos >= #player.playlist
|
|
||||||
and player.loopPlaylist == 'no' then
|
|
||||||
self:disable()
|
|
||||||
else
|
|
||||||
self:enable()
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('btnNext')
|
|
||||||
|
|
||||||
-- cycle audio button
|
|
||||||
ne = newElement('cycleAudio', 'button')
|
|
||||||
ne.layer = 10
|
|
||||||
ne.style = clone(styles.button2)
|
|
||||||
ne.geo.w = 30
|
|
||||||
ne.geo.h = 24
|
|
||||||
ne.geo.an = 5
|
|
||||||
ne.text = '\xEF\x8E\xB7'
|
|
||||||
ne.tipText = ''
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.x = 37
|
|
||||||
self.geo.y = player.geo.refY
|
|
||||||
self.visible = player.geo.width >= 540
|
|
||||||
self:setPos()
|
|
||||||
self:setHitBox()
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['mouse_move'] = function(self, pos)
|
|
||||||
if self.enabled and self:isInside(pos) then
|
|
||||||
tooltip:show(self.tipText, {self.geo.x, self.geo.y+30}, self)
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
tooltip:hide(self)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
ne.responder['file-loaded'] = function(self)
|
|
||||||
if #player.tracks.audio > 0 then
|
|
||||||
self:enable()
|
|
||||||
else
|
|
||||||
self:disable()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
ne.responder['audio-changed'] = function(self)
|
|
||||||
if player.tracks then
|
|
||||||
local lang
|
|
||||||
if player.audioTrack == 0 then
|
|
||||||
lang = 'OFF'
|
|
||||||
else
|
|
||||||
lang = player.tracks.audio[player.audioTrack].lang
|
|
||||||
end
|
|
||||||
if not lang then lang = 'unknown' end
|
|
||||||
self.tipText = string.format('[%s/%s][%s]',
|
|
||||||
player.audioTrack, #player.tracks.audio, lang)
|
|
||||||
tooltip:update(self.tipText, self)
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_left_up'] = function(self, pos)
|
|
||||||
if self.enabled and self:isInside(pos) then
|
|
||||||
cycleTrack('audio')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_right_up'] = function(self, pos)
|
|
||||||
if self.enabled and self:isInside(pos) then
|
|
||||||
cycleTrack('audio', 'prev')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('cycleAudio')
|
|
||||||
|
|
||||||
|
|
||||||
-- cycle sub button
|
|
||||||
ne = newElement('cycleSub', 'cycleAudio')
|
|
||||||
ne.text = '\xEF\x8F\x93'
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.x = 87
|
|
||||||
self.geo.y = player.geo.refY
|
|
||||||
self.visible = player.geo.width >= 600
|
|
||||||
self:setPos()
|
|
||||||
self:setHitBox()
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['file-loaded'] = function(self)
|
|
||||||
if #player.tracks.sub > 0 then
|
|
||||||
self:enable()
|
|
||||||
else
|
|
||||||
self:disable()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
ne.responder['audio-changed'] = nil
|
|
||||||
ne.responder['sub-changed'] = function(self)
|
|
||||||
if player.tracks then
|
|
||||||
local title
|
|
||||||
if player.subTrack == 0 then
|
|
||||||
title = 'OFF'
|
|
||||||
else
|
|
||||||
title = player.tracks.sub[player.subTrack].title
|
|
||||||
end
|
|
||||||
if not title then title = 'unknown' end
|
|
||||||
self.tipText = string.format('[%s/%s][%s]',
|
|
||||||
player.subTrack, #player.tracks.sub, title)
|
|
||||||
tooltip:update(self.tipText, self)
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_left_up'] = function(self, pos)
|
|
||||||
if self.enabled and self:isInside(pos) then
|
|
||||||
cycleTrack('sub')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_right_up'] = function(self, pos)
|
|
||||||
if self.enabled and self:isInside(pos) then
|
|
||||||
cycleTrack('sub', 'prev')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('cycleSub')
|
|
||||||
|
|
||||||
-- toggle mute
|
|
||||||
ne = newElement('togMute', 'button')
|
|
||||||
ne.layer = 10
|
|
||||||
ne.style = clone(styles.button2)
|
|
||||||
ne.geo.x = 137
|
|
||||||
ne.geo.w = 30
|
|
||||||
ne.geo.h = 24
|
|
||||||
ne.geo.an = 5
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.y = player.geo.refY
|
|
||||||
self.visible = player.geo.width >= 700
|
|
||||||
self:setPos()
|
|
||||||
self:setHitBox()
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_left_up'] = function(self, pos)
|
|
||||||
if self.enabled and self:isInside(pos) then
|
|
||||||
mp.commandv('cycle', 'mute')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['mute'] = function(self)
|
|
||||||
if player.muted then
|
|
||||||
self.text = '\xEF\x8E\xBB'
|
|
||||||
else
|
|
||||||
self.text = '\xEF\x8E\xBC'
|
|
||||||
end
|
|
||||||
self:render()
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('togMute', 'button')
|
|
||||||
|
|
||||||
-- volume slider
|
|
||||||
-- background
|
|
||||||
ne = newElement('volumeSliderBg', 'box')
|
|
||||||
ne.layer = 9
|
|
||||||
ne.style = clone(styles.volumeSlider)
|
|
||||||
ne.geo.r = 0
|
|
||||||
ne.geo.h = 1
|
|
||||||
ne.geo.an = 4
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.visible = player.geo.width > 740
|
|
||||||
self.geo.x = 156
|
|
||||||
self.geo.y = player.geo.refY
|
|
||||||
self.geo.w = 80
|
|
||||||
self:init()
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('volumeSliderBg')
|
|
||||||
|
|
||||||
-- seekbar
|
|
||||||
ne = newElement('volumeSlider', 'slider')
|
|
||||||
ne.layer = 10
|
|
||||||
ne.style = clone(styles.volumeSlider)
|
|
||||||
ne.geo.an = 4
|
|
||||||
ne.geo.h = 14
|
|
||||||
ne.barHeight = 2
|
|
||||||
ne.barRadius = 0
|
|
||||||
ne.nobRadius = 4
|
|
||||||
ne.allowDrag = false
|
|
||||||
ne.lastSeek = nil
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.visible = player.geo.width > 740
|
|
||||||
self.geo.an = 4
|
|
||||||
self.geo.x = 152
|
|
||||||
self.geo.y = player.geo.refY
|
|
||||||
self.geo.w = 88
|
|
||||||
self:setParam() -- setParam may change geo settings
|
|
||||||
self:setPos()
|
|
||||||
self:render()
|
|
||||||
end
|
|
||||||
ne.responder['volume'] = function(self)
|
|
||||||
local val = player.volume
|
|
||||||
if val then
|
|
||||||
if val > 140 then val = 140
|
|
||||||
elseif val < 0 then val = 0 end
|
|
||||||
self.value = val/1.4
|
|
||||||
self.xValue = val/140 * self.xLength
|
|
||||||
self:render()
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['idle'] = ne.responder['volume']
|
|
||||||
ne.responder['mouse_move'] = function(self, pos)
|
|
||||||
if not self.enabled then return false end
|
|
||||||
local vol = self:getValueAt(pos)
|
|
||||||
if self.allowDrag then
|
|
||||||
if vol then
|
|
||||||
mp.commandv('set', 'volume', vol*1.4)
|
|
||||||
env.updateTime()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if self:isInside(pos) then
|
|
||||||
local tipText
|
|
||||||
if vol then
|
|
||||||
tipText = string.format('%d', vol*1.4)
|
|
||||||
else
|
|
||||||
tipText = 'N/A'
|
|
||||||
end
|
|
||||||
tooltip:show(tipText, {pos[1], self.geo.y}, self)
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
tooltip:hide(self)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_left_down'] = function(self, pos)
|
|
||||||
if not self.enabled then return false end
|
|
||||||
if self:isInside(pos) then
|
|
||||||
self.allowDrag = true
|
|
||||||
local vol = self:getValueAt(pos)
|
|
||||||
if vol then
|
|
||||||
mp.commandv('set', 'volume', vol*1.4)
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_left_up'] = function(self, pos)
|
|
||||||
self.allowDrag = false
|
|
||||||
self.lastSeek = nil
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('volumeSlider')
|
|
||||||
|
|
||||||
|
|
||||||
-- toggle info
|
|
||||||
ne = newElement('togInfo', 'button')
|
|
||||||
ne.layer = 10
|
|
||||||
ne.style = clone(styles.button2)
|
|
||||||
ne.geo.w = 30
|
|
||||||
ne.geo.h = 24
|
|
||||||
ne.geo.an = 5
|
|
||||||
ne.text = '\xEF\x87\xB7'
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.x = player.geo.width - 87
|
|
||||||
self.geo.y = player.geo.refY
|
|
||||||
self.visible = player.geo.width >= 640
|
|
||||||
self:setPos()
|
|
||||||
self:setHitBox()
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_left_up'] = function(self, pos)
|
|
||||||
if self.enabled and self:isInside(pos) then
|
|
||||||
mp.commandv('script-binding', 'stats/display-stats-toggle')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('togInfo')
|
|
||||||
|
|
||||||
|
|
||||||
-- toggle fullscreen
|
|
||||||
ne = newElement('togFs', 'togInfo')
|
|
||||||
ne.text = '\xEF\x85\xAD'
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.x = player.geo.width - 37
|
|
||||||
self.geo.y = player.geo.refY
|
|
||||||
self.visible = player.geo.width >= 600
|
|
||||||
if (player.fullscreen) then
|
|
||||||
self.text = '\xEF\x85\xAC'
|
|
||||||
else
|
|
||||||
self.text = '\xEF\x85\xAD'
|
|
||||||
end
|
|
||||||
self:render()
|
|
||||||
self:setPos()
|
|
||||||
self:setHitBox()
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_left_up'] = function(self, pos)
|
|
||||||
if self.enabled and self:isInside(pos) then
|
|
||||||
mp.commandv('cycle', 'fullscreen')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('togFs')
|
|
||||||
|
|
||||||
-- seekbar background
|
|
||||||
ne = newElement('seekbarBg', 'box')
|
|
||||||
ne.layer = 9
|
|
||||||
ne.style = clone(styles.seekbarBg)
|
|
||||||
ne.geo.r = 0
|
|
||||||
ne.geo.h = 2
|
|
||||||
ne.geo.an = 5
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.x = player.geo.refX
|
|
||||||
self.geo.y = player.geo.refY - 56
|
|
||||||
self.geo.w = player.geo.width - 50
|
|
||||||
self:init()
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('seekbarBg')
|
|
||||||
|
|
||||||
-- seekbar
|
|
||||||
ne = newElement('seekbar', 'slider')
|
|
||||||
ne.layer = 10
|
|
||||||
ne.style = clone(styles.seekbarFg)
|
|
||||||
ne.geo.an = 5
|
|
||||||
ne.geo.h = 20
|
|
||||||
ne.barHeight = 2
|
|
||||||
ne.barRadius = 0
|
|
||||||
ne.nobRadius = 8
|
|
||||||
ne.allowDrag = false
|
|
||||||
ne.lastSeek = nil
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.an = 5
|
|
||||||
self.geo.x = player.geo.refX
|
|
||||||
self.geo.y = player.geo.refY - 56
|
|
||||||
self.geo.w = player.geo.width - 34
|
|
||||||
self:setParam() -- setParam may change geo settings
|
|
||||||
self:setPos()
|
|
||||||
self:render()
|
|
||||||
end
|
|
||||||
ne.responder['time'] = function(self)
|
|
||||||
local val = player.percentPos
|
|
||||||
if val and not self.enabled then
|
|
||||||
self:enable()
|
|
||||||
elseif not val and self.enabled then
|
|
||||||
tooltip:hide(self)
|
|
||||||
self:disable()
|
|
||||||
end
|
|
||||||
if val then
|
|
||||||
self.value = val
|
|
||||||
self.xValue = val/100 * self.xLength
|
|
||||||
self:render()
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['mouse_move'] = function(self, pos)
|
|
||||||
if not self.enabled then return false end
|
|
||||||
if self.allowDrag then
|
|
||||||
local seekTo = self:getValueAt(pos)
|
|
||||||
if seekTo then
|
|
||||||
mp.commandv('seek', seekTo, 'absolute-percent')
|
|
||||||
env.updateTime()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if self:isInside(pos) then
|
|
||||||
local tipText
|
|
||||||
if player.duration then
|
|
||||||
local seconds = self:getValueAt(pos)/100 * player.duration
|
|
||||||
if #player.chapters > 0 then
|
|
||||||
local ch = #player.chapters
|
|
||||||
for i, v in ipairs(player.chapters) do
|
|
||||||
if seconds < v.time then
|
|
||||||
ch = i - 1
|
|
||||||
break
|
|
||||||
end
|
|
||||||
end
|
|
||||||
if ch == 0 then
|
|
||||||
tipText = string.format('[0/%d][unknown]\\N%s',
|
|
||||||
#player.chapters, mp.format_time(seconds))
|
|
||||||
else
|
|
||||||
local title = player.chapters[ch].title
|
|
||||||
if not title then title = 'unknown' end
|
|
||||||
tipText = string.format('[%d/%d][%s]\\N%s',
|
|
||||||
ch, #player.chapters, title,
|
|
||||||
mp.format_time(seconds))
|
|
||||||
end
|
|
||||||
else
|
|
||||||
tipText = mp.format_time(seconds)
|
|
||||||
end
|
|
||||||
else
|
|
||||||
tipText = '--:--:--'
|
|
||||||
end
|
|
||||||
tooltip:show(tipText, {pos[1], self.geo.y}, self)
|
|
||||||
return true
|
|
||||||
else
|
|
||||||
tooltip:hide(self)
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_left_down'] = function(self, pos)
|
|
||||||
if not self.enabled then return false end
|
|
||||||
if self:isInside(pos) then
|
|
||||||
self.allowDrag = true
|
|
||||||
local seekTo = self:getValueAt(pos)
|
|
||||||
if seekTo then
|
|
||||||
mp.commandv('seek', seekTo, 'absolute-percent')
|
|
||||||
env.updateTime()
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_left_up'] = function(self, pos)
|
|
||||||
self.allowDrag = false
|
|
||||||
self.lastSeek = nil
|
|
||||||
end
|
|
||||||
ne.responder['file-loaded'] = function(self)
|
|
||||||
-- update chapter markers
|
|
||||||
env.updateTime()
|
|
||||||
self.markers = {}
|
|
||||||
if player.duration then
|
|
||||||
for i, v in ipairs(player.chapters) do
|
|
||||||
self.markers[i] = (v.time*100 / player.duration)
|
|
||||||
end
|
|
||||||
self:render()
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('seekbar')
|
|
||||||
|
|
||||||
-- time display
|
|
||||||
ne = newElement('time1', 'button')
|
|
||||||
ne.layer = 10
|
|
||||||
ne.style = clone(styles.time)
|
|
||||||
ne.geo.w = 64
|
|
||||||
ne.geo.h = 20
|
|
||||||
ne.geo.an = 7
|
|
||||||
ne.enabled = true
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.x = 25
|
|
||||||
self.geo.y = player.geo.refY - 44
|
|
||||||
self:setPos()
|
|
||||||
end
|
|
||||||
ne.responder['time'] = function(self)
|
|
||||||
if player.timePos then
|
|
||||||
self.pack[4] = mp.format_time(player.timePos)
|
|
||||||
else
|
|
||||||
self.pack[4] = '--:--:--'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('time1')
|
|
||||||
|
|
||||||
-- time duration
|
|
||||||
ne = newElement('time2', 'time1')
|
|
||||||
ne.geo.an = 9
|
|
||||||
ne.isDuration = true
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.x = player.geo.width - 25
|
|
||||||
self.geo.y = player.geo.refY - 44
|
|
||||||
self:setPos()
|
|
||||||
self:setHitBox()
|
|
||||||
end
|
|
||||||
ne.responder['time'] = function(self)
|
|
||||||
if self.isDuration then
|
|
||||||
val = player.duration
|
|
||||||
else
|
|
||||||
val = -player.timeRem
|
|
||||||
end
|
|
||||||
if val then
|
|
||||||
self.pack[4] = mp.format_time(val)
|
|
||||||
else
|
|
||||||
self.pack[4] = '--:--:--'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_left_up'] = function(self, pos)
|
|
||||||
if self:isInside(pos) then
|
|
||||||
self.isDuration = not self.isDuration
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('time2')
|
|
||||||
|
|
||||||
-- title
|
|
||||||
ne = newElement('title')
|
|
||||||
ne.layer = 10
|
|
||||||
ne.style = clone(styles.title)
|
|
||||||
ne.geo.x = 20
|
|
||||||
ne.geo.an = 1
|
|
||||||
ne.visible = false
|
|
||||||
ne.title = ''
|
|
||||||
ne.render = function(self)
|
|
||||||
local maxchars = player.geo.width / 23
|
|
||||||
local text = self.title
|
|
||||||
-- 估计1个中文字符约等于1.5个英文字符
|
|
||||||
local charcount = (text:len() + select(2, text:gsub('[^\128-\193]', ''))*2) / 3
|
|
||||||
if not (maxchars == nil) and (charcount > maxchars) then
|
|
||||||
local limit = math.max(0, maxchars - 3)
|
|
||||||
if (charcount > limit) then
|
|
||||||
while (charcount > limit) do
|
|
||||||
text = text:gsub('.[\128-\191]*$', '')
|
|
||||||
charcount = (text:len() + select(2, text:gsub('[^\128-\193]', ''))*2) / 3
|
|
||||||
end
|
|
||||||
text = text .. '...'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
self.pack[4] = text
|
|
||||||
end
|
|
||||||
ne.tick = function(self)
|
|
||||||
if not self.visible then return '' end
|
|
||||||
if self.trans >= 0.9 then
|
|
||||||
self.visible = false
|
|
||||||
end
|
|
||||||
return table.concat(self.pack)
|
|
||||||
end
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.y = player.geo.refY - 92
|
|
||||||
self:setPos()
|
|
||||||
self:render()
|
|
||||||
self.visible = self.visible and (player.geo.height >= 320)
|
|
||||||
end
|
|
||||||
ne.responder['pause'] = function(self)
|
|
||||||
self.visible = (self.visible or player.paused) and (player.geo.height >= 320)
|
|
||||||
end
|
|
||||||
ne.responder['file-loaded'] = function(self)
|
|
||||||
local title = mp.command_native({'expand-text', '${media-title}'})
|
|
||||||
title = title:gsub('\\n', ' '):gsub('\\$', ''):gsub('{','\\{')
|
|
||||||
self.title = title
|
|
||||||
self:render()
|
|
||||||
self.visible = true
|
|
||||||
end
|
|
||||||
ne.responder['idle'] = function(self)
|
|
||||||
self.visible = not player.idle
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('title')
|
|
||||||
|
|
||||||
-- window controllers
|
|
||||||
ne = newElement('winClose', 'button')
|
|
||||||
ne.layer = 10
|
|
||||||
ne.style = clone(styles.winControl)
|
|
||||||
ne.geo.y = 16
|
|
||||||
ne.geo.w = 40
|
|
||||||
ne.geo.h = 32
|
|
||||||
ne.geo.an = 5
|
|
||||||
ne.text = '\238\132\149'
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.x = player.geo.width - 20
|
|
||||||
self:init()
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_left_up'] = function(self, pos)
|
|
||||||
if self.visible and self:isInside(pos) then
|
|
||||||
mp.commandv('quit')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne.responder['fullscreen'] = function(self)
|
|
||||||
self.visible = player.fullscreen
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('winClose')
|
|
||||||
|
|
||||||
|
|
||||||
ne = newElement('winMax', 'winClose')
|
|
||||||
ne.text = ''
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.x = player.geo.width - 60
|
|
||||||
if player.maximized or player.fullscreen then
|
|
||||||
self.text = '\238\132\148'
|
|
||||||
else
|
|
||||||
self.text = '\238\132\147'
|
|
||||||
end
|
|
||||||
self:init()
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_left_up'] = function(self, pos)
|
|
||||||
if self.visible and self:isInside(pos) then
|
|
||||||
if player.fullscreen then
|
|
||||||
mp.commandv('cycle', 'fullscreen')
|
|
||||||
else
|
|
||||||
mp.commandv('cycle', 'window-maximized')
|
|
||||||
end
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('winMax')
|
|
||||||
|
|
||||||
ne = newElement('winMin', 'winClose')
|
|
||||||
ne.text = '\238\132\146'
|
|
||||||
ne.responder['resize'] = function(self)
|
|
||||||
self.geo.x = player.geo.width - 100
|
|
||||||
self:init()
|
|
||||||
end
|
|
||||||
ne.responder['mbtn_left_up'] = function(self, pos)
|
|
||||||
if self.visible and self:isInside(pos) then
|
|
||||||
mp.commandv('cycle', 'window-minimized')
|
|
||||||
return true
|
|
||||||
end
|
|
||||||
return false
|
|
||||||
end
|
|
||||||
ne:init()
|
|
||||||
addToPlayLayout('winMin')
|
|
|
@ -1,14 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
target="$HOME/.local/bin"
|
|
||||||
|
|
||||||
echo "installing $2 to $1"
|
|
||||||
|
|
||||||
if test -f $2; then
|
|
||||||
cp $2 "$target/$1"
|
|
||||||
chmod +x "$target/$1"
|
|
||||||
else
|
|
||||||
echo "file $2 doesnt exist"
|
|
||||||
fi
|
|
||||||
|
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
nordvpn login --callback "$1"
|
|
|
@ -1,367 +0,0 @@
|
||||||
#!/usr/bin/env bash
|
|
||||||
#
|
|
||||||
# OpenAI CLI v2.2.2
|
|
||||||
# Created by @janlay
|
|
||||||
#
|
|
||||||
|
|
||||||
set -eo pipefail
|
|
||||||
|
|
||||||
# openai-cli accepts various exported environment variables:
|
|
||||||
# OPENAI_API_KEY : OpenAI's API key
|
|
||||||
# OPENAI_API_ENDPOINT : Custom API endpoint
|
|
||||||
# OPENAI_MAX_TOKENS : Maximum number of tokens to use
|
|
||||||
# OPENAI_CHAT_MODEL : ChatGPT model
|
|
||||||
# OPENAI_DATA_DIR : Directory to store data
|
|
||||||
OPENAI_API_ENDPOINT="${OPENAI_API_ENDPOINT:-https://api.openai.com}"
|
|
||||||
OPENAI_API_KEY="${OPENAI_API_KEY:-sk-proj-Y9WpJ6RxNUgF0qqOrU8uT3BlbkFJgeRmt7zldRdiV1WrxC8G}"
|
|
||||||
OPENAI_MAX_TOKENS="${OPENAI_MAX_TOKENS:-2000}"
|
|
||||||
OPENAI_CHAT_MODEL="${OPENAI_CHAT_MODEL:-gpt-4-turbo}"
|
|
||||||
declare _config_dir="${OPENAI_DATA_DIR:-$XDG_CONFIG_HOME}"
|
|
||||||
OPENAI_DATA_DIR="${_config_dir:-$HOME/.openai}"
|
|
||||||
|
|
||||||
# defaults
|
|
||||||
readonly _app_name=openai _app_version=2.2.2
|
|
||||||
readonly default_api_version=1 default_api_name=chat/completions default_model="$OPENAI_CHAT_MODEL" default_topic=General
|
|
||||||
|
|
||||||
declare -i chat_mode=0 dry_run=0
|
|
||||||
declare tokens_file="$OPENAI_DATA_DIR/total_tokens" api_version=$default_api_version api_name=$default_api_name topic=$default_topic
|
|
||||||
declare dump_file dumped_file data_file temp_dir rest_args prompt_file prompt
|
|
||||||
|
|
||||||
trap cleanup EXIT
|
|
||||||
cleanup() {
|
|
||||||
if [ -d "$temp_dir" ]; then
|
|
||||||
rm -rf -- "$temp_dir"
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
raise_error() {
|
|
||||||
[ "$2" = 0 ] || echo -n "$_app_name: " >&2
|
|
||||||
echo -e "$1" >&2
|
|
||||||
exit "${2:-1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
load_conversation() {
|
|
||||||
[ -f "$data_file" ] && cat "$data_file" || echo '{}'
|
|
||||||
}
|
|
||||||
|
|
||||||
update_conversation() {
|
|
||||||
local entry="$2" data
|
|
||||||
[[ $entry == \{* ]] || entry=$(jq -n --arg content "$entry" '{$content}')
|
|
||||||
entry=$(jq --arg role "$1" '. += {$role}' <<<"$entry")
|
|
||||||
data=$(load_conversation)
|
|
||||||
jq --argjson item "$entry" '.messages += [$item]' <<<"$data" >"$data_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
save_tokens() {
|
|
||||||
local data num="$1"
|
|
||||||
[ -f "$data_file" ] && {
|
|
||||||
data=$(load_conversation)
|
|
||||||
jq --argjson tokens "$num" '.total_tokens += $tokens' <<<"$data" >"$data_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
data=0
|
|
||||||
[ -f "$tokens_file" ] && data=$(cat "$tokens_file")
|
|
||||||
echo "$((data + num))" >"$tokens_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
read_prompt() {
|
|
||||||
# read prompt from args first
|
|
||||||
local word accepts_props=1 props='{}' real_prompt
|
|
||||||
if [ ${#rest_args[@]} -gt 0 ]; then
|
|
||||||
# read file $prompt_file word by word, and extract words starting with '+'
|
|
||||||
for word in "${rest_args[@]}"; do
|
|
||||||
if [ $accepts_props -eq 1 ] && [ "${word:0:1}" = '+' ]; then
|
|
||||||
word="${word:1}"
|
|
||||||
# determine value's type for jq
|
|
||||||
local options=(--arg key "${word%%=*}") value="${word#*=}" arg=--arg
|
|
||||||
[[ $value =~ ^[+-]?\ ?[0-9.]+$ || $value = true || $value = false || $value == [\[\{]* ]] && arg=--argjson
|
|
||||||
options+=("$arg" value "$value")
|
|
||||||
props=$(jq "${options[@]}" '.[$key] = $value' <<<"$props")
|
|
||||||
else
|
|
||||||
real_prompt="$real_prompt $word"
|
|
||||||
accepts_props=0
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
[ -n "$props" ] && echo "$props" >"$temp_dir/props"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "$real_prompt" ]; then
|
|
||||||
[ -n "$prompt_file" ] && echo "* Prompt file \`$prompt_file' will be ignored as the prompt parameters are provided." >&2
|
|
||||||
echo -n "${real_prompt:1}" >"$temp_dir/prompt"
|
|
||||||
elif [ -n "$prompt_file" ]; then
|
|
||||||
[ -f "$prompt_file" ] || raise_error "File not found: $prompt_file." 3
|
|
||||||
[[ -s $prompt_file ]] || raise_error "Empty file: $prompt_file." 4
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
openai_models() {
|
|
||||||
call_api | jq
|
|
||||||
}
|
|
||||||
|
|
||||||
openai_moderations() {
|
|
||||||
local prop_file="$temp_dir/props" payload="{\"model\": \"text-moderation-latest\"}"
|
|
||||||
|
|
||||||
# overwrite default properties with user's
|
|
||||||
read_prompt
|
|
||||||
[ -f "$prop_file" ] && payload=$(jq -n --argjson payload "$payload" '$payload | . += input' <"$prop_file")
|
|
||||||
|
|
||||||
# append user's prompt to messages
|
|
||||||
local payload_file="$temp_dir/payload" input_file="$temp_dir/prompt"
|
|
||||||
[ -f "$input_file" ] || input_file="${prompt_file:-/dev/stdin}"
|
|
||||||
jq -Rs -cn --argjson payload "$payload" '$payload | .input = input' "$input_file" >"$payload_file"
|
|
||||||
|
|
||||||
call_api | jq -c '.results[]'
|
|
||||||
}
|
|
||||||
|
|
||||||
openai_images_generations() {
|
|
||||||
local prop_file="$temp_dir/props" payload="{\"n\": 1, \"size\": \"1024x1024\"}"
|
|
||||||
|
|
||||||
# overwrite default properties with user's
|
|
||||||
read_prompt
|
|
||||||
[ -f "$prop_file" ] && payload=$(jq -n --argjson payload "$payload" '$payload | . += input | . += {response_format: "url"}' <"$prop_file")
|
|
||||||
|
|
||||||
# append user's prompt to messages
|
|
||||||
local payload_file="$temp_dir/payload" input_file="$temp_dir/prompt"
|
|
||||||
[ -f "$input_file" ] || input_file="${prompt_file:-/dev/stdin}"
|
|
||||||
jq -Rs -cn --argjson payload "$payload" '$payload | .prompt = input' "$input_file" >"$payload_file"
|
|
||||||
|
|
||||||
call_api | jq -r '.data[].url'
|
|
||||||
}
|
|
||||||
|
|
||||||
openai_embeddings() {
|
|
||||||
local prop_file="$temp_dir/props" payload="{\"model\": \"text-embedding-ada-002\"}"
|
|
||||||
|
|
||||||
# overwrite default properties with user's
|
|
||||||
read_prompt
|
|
||||||
[ -f "$prop_file" ] && payload=$(jq -n --argjson payload "$payload" '$payload | . += input' <"$prop_file")
|
|
||||||
|
|
||||||
# append user's prompt to messages
|
|
||||||
local payload_file="$temp_dir/payload" input_file="$temp_dir/prompt"
|
|
||||||
[ -f "$input_file" ] || input_file="${prompt_file:-/dev/stdin}"
|
|
||||||
jq -Rs -cn --argjson payload "$payload" '$payload | .input = input' "$input_file" >"$payload_file"
|
|
||||||
|
|
||||||
call_api | jq -c
|
|
||||||
}
|
|
||||||
|
|
||||||
openai_chat_completions() {
|
|
||||||
[ -n "$dumped_file" ] || {
|
|
||||||
local prop_file="$temp_dir/props" payload="{\"model\": \"$default_model\", \"stream\": true, \"temperature\": 0.5, \"max_tokens\": $OPENAI_MAX_TOKENS}"
|
|
||||||
|
|
||||||
# overwrite default properties with user's
|
|
||||||
read_prompt
|
|
||||||
[ -f "$prop_file" ] && {
|
|
||||||
payload=$(jq -n --argjson payload "$payload" '$payload | . += input | . += {messages: []}' <"$prop_file")
|
|
||||||
}
|
|
||||||
|
|
||||||
local data
|
|
||||||
data=$(load_conversation | jq .messages)
|
|
||||||
[ "$topic" != "$default_topic" ] && {
|
|
||||||
if [ $chat_mode -eq 1 ]; then
|
|
||||||
# load all messages for chat mode
|
|
||||||
payload=$(jq --argjson messages "$data" 'setpath(["messages"]; $messages)' <<<"$payload")
|
|
||||||
else
|
|
||||||
# load only first message for non-chat mode
|
|
||||||
payload=$(jq --argjson messages "$data" 'setpath(["messages"]; [$messages[0]])' <<<"$payload")
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
# append user's prompt to messages
|
|
||||||
local payload_file="$temp_dir/payload" input_file="$temp_dir/prompt"
|
|
||||||
[ -f "$input_file" ] || input_file="${prompt_file:-/dev/stdin}"
|
|
||||||
jq -Rs -cn --argjson payload "$payload" '$payload | .messages += [{role: "user", content: input}]' "$input_file" >"$payload_file"
|
|
||||||
}
|
|
||||||
|
|
||||||
local chunk reason text role fn_name
|
|
||||||
call_api | while read -r chunk; do
|
|
||||||
[ -z "$chunk" ] && continue
|
|
||||||
chunk=$(cut -d: -f2- <<<"$chunk" | jq '.choices[0]')
|
|
||||||
reason=$(jq -r '.finish_reason // empty' <<<"$chunk")
|
|
||||||
[[ $reason = stop || $reason = function_call ]] && break
|
|
||||||
[ -n "$reason" ] && raise_error "API error: $reason" 10
|
|
||||||
|
|
||||||
# get role and function info from the first chunk
|
|
||||||
[ -z "$role" ] && {
|
|
||||||
role=$(jq -r '.delta.role // empty' <<<"$chunk")
|
|
||||||
fn_name=$(jq -r '.delta.function_call.name // empty' <<<"$chunk")
|
|
||||||
}
|
|
||||||
|
|
||||||
# workaround: https://stackoverflow.com/a/15184414
|
|
||||||
chunk=$(
|
|
||||||
jq -r '.delta | .function_call.arguments // .content // empty' <<<"$chunk"
|
|
||||||
printf x
|
|
||||||
)
|
|
||||||
# ensure chunk is not empty
|
|
||||||
[ ${#chunk} -ge 2 ] || continue
|
|
||||||
|
|
||||||
chunk="${chunk:0:${#chunk}-2}"
|
|
||||||
text="$text$chunk"
|
|
||||||
echo -n "$chunk"
|
|
||||||
done
|
|
||||||
|
|
||||||
# append response to topic file for chat mode
|
|
||||||
[ "$chat_mode" -eq 1 ] && {
|
|
||||||
[ -n "$fn_name" ] && text=$(jq -n --arg name "$fn_name" --argjson arguments "${text:-\{\}}" '{function_call: {$name, $arguments}}')
|
|
||||||
|
|
||||||
update_conversation user "$prompt"
|
|
||||||
update_conversation "$role" "$text"
|
|
||||||
}
|
|
||||||
echo
|
|
||||||
}
|
|
||||||
|
|
||||||
# shellcheck disable=SC2120
|
|
||||||
call_api() {
|
|
||||||
# return dumped file if specified
|
|
||||||
[ -n "$dumped_file" ] && {
|
|
||||||
cat "$dumped_file"
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
local url="$OPENAI_API_ENDPOINT/v$api_version/$api_name" auth="Bearer $OPENAI_API_KEY"
|
|
||||||
|
|
||||||
# dry-run mode
|
|
||||||
[ "$dry_run" -eq 1 ] && {
|
|
||||||
echo "Dry-run mode, no API calls made."
|
|
||||||
echo -e "\nRequest URL:\n--------------\n$url"
|
|
||||||
echo -en "\nAuthorization:\n--------------\n"
|
|
||||||
sed -E 's/(sk-.{3}).{41}/\1****/' <<<"$auth"
|
|
||||||
[ -n "$payload_file" ] && {
|
|
||||||
echo -e "\nPayload:\n--------------"
|
|
||||||
jq <"$payload_file"
|
|
||||||
}
|
|
||||||
exit 0
|
|
||||||
} >&2
|
|
||||||
|
|
||||||
local args=("$url" --no-buffer -fsSL -H 'Content-Type: application/json' -H "Authorization: $auth")
|
|
||||||
[ -n "$payload_file" ] && args+=(-d @"$payload_file")
|
|
||||||
[ $# -gt 0 ] && args+=("$@")
|
|
||||||
|
|
||||||
[ -n "$dump_file" ] && args+=(-o "$dump_file")
|
|
||||||
curl "${args[@]}"
|
|
||||||
[ -z "$dump_file" ] || exit 0
|
|
||||||
}
|
|
||||||
|
|
||||||
create_topic() {
|
|
||||||
update_conversation system "${rest_args[*]}"
|
|
||||||
raise_error "Topic '$topic' created with initial prompt '${rest_args[*]}'" 0
|
|
||||||
}
|
|
||||||
|
|
||||||
usage() {
|
|
||||||
raise_error "OpenAI Client v$_app_version
|
|
||||||
|
|
||||||
SYNOPSIS
|
|
||||||
ABSTRACT
|
|
||||||
$_app_name [-n] [-a api_name] [-v api_version] [-o dump_file] [INPUT...]
|
|
||||||
$_app_name -i dumped_file
|
|
||||||
|
|
||||||
DEFAULT_API (v$default_api_version/$default_api_name)
|
|
||||||
$_app_name [-c] [+property=value...] [@TOPIC] [-f file | prompt ...]
|
|
||||||
prompt
|
|
||||||
Prompt string for the request to OpenAI API. This can consist of multiple
|
|
||||||
arguments, which are considered to be separated by spaces.
|
|
||||||
-f file
|
|
||||||
A file to be read as prompt. If file is - or neither this parameter nor a prompt
|
|
||||||
is specified, read from standard input.
|
|
||||||
-c
|
|
||||||
Continues the topic, the default topic is '$default_topic'.
|
|
||||||
property=value
|
|
||||||
Overwrites default properties in payload. Prepend a plus sign '+' before property=value.
|
|
||||||
eg: +model=gpt-3.5-turbo-0301, +stream=false
|
|
||||||
|
|
||||||
TOPICS
|
|
||||||
Topic starts with an at sign '@'.
|
|
||||||
To create new topic, use \`$_app_name @new_topic initial prompt'
|
|
||||||
|
|
||||||
OTHER APIS
|
|
||||||
$_app_name -a models
|
|
||||||
|
|
||||||
GLOBAL OPTIONS
|
|
||||||
Global options apply to all APIs.
|
|
||||||
-v version
|
|
||||||
API version, default is '$default_api_version'.
|
|
||||||
-a name
|
|
||||||
API name, default is '$default_api_name'.
|
|
||||||
-n
|
|
||||||
Dry-run mode, don't call API.
|
|
||||||
-o filename
|
|
||||||
Dumps API response to a file and exits.
|
|
||||||
-i filename
|
|
||||||
Uses specified dumped file instead of requesting API.
|
|
||||||
Any request-related arguments and user input are ignored.
|
|
||||||
|
|
||||||
--
|
|
||||||
Ignores rest of arguments, useful when unquoted prompt consists of '-'.
|
|
||||||
|
|
||||||
-h
|
|
||||||
Shows this help" 0
|
|
||||||
}
|
|
||||||
|
|
||||||
parse() {
|
|
||||||
local opt
|
|
||||||
while getopts 'v:a:f:i:o:cnh' opt; do
|
|
||||||
case "$opt" in
|
|
||||||
c)
|
|
||||||
chat_mode=1
|
|
||||||
;;
|
|
||||||
v)
|
|
||||||
api_version="$OPTARG"
|
|
||||||
;;
|
|
||||||
a)
|
|
||||||
api_name="$OPTARG"
|
|
||||||
;;
|
|
||||||
f)
|
|
||||||
prompt_file="$OPTARG"
|
|
||||||
[ "$prompt_file" = - ] && prompt_file=
|
|
||||||
;;
|
|
||||||
n)
|
|
||||||
dry_run=1
|
|
||||||
;;
|
|
||||||
i)
|
|
||||||
dumped_file="$OPTARG"
|
|
||||||
;;
|
|
||||||
o)
|
|
||||||
dump_file="$OPTARG"
|
|
||||||
;;
|
|
||||||
h | ?)
|
|
||||||
usage
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
done
|
|
||||||
shift "$((OPTIND - 1))"
|
|
||||||
|
|
||||||
# extract the leading topic
|
|
||||||
[[ "$1" =~ ^@ ]] && {
|
|
||||||
topic="${1#@}"
|
|
||||||
shift
|
|
||||||
}
|
|
||||||
|
|
||||||
[ $chat_mode -eq 0 ] || {
|
|
||||||
[[ -n $topic && $topic != "$default_topic" ]] || raise_error 'Topic is required for chatting.' 2
|
|
||||||
}
|
|
||||||
|
|
||||||
rest_args=("$@")
|
|
||||||
}
|
|
||||||
|
|
||||||
check_bin() {
|
|
||||||
command -v "$1" >/dev/null || raise_error "$1 not found. Use package manager (Homebrew, apt-get etc.) to install it." "${2:-1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
|
||||||
parse "$@"
|
|
||||||
check_bin jq 10
|
|
||||||
|
|
||||||
mkdir -p "$OPENAI_DATA_DIR"
|
|
||||||
data_file="$OPENAI_DATA_DIR/$topic.json"
|
|
||||||
temp_dir=$(mktemp -d)
|
|
||||||
|
|
||||||
if [[ $topic == "$default_topic" || -f "$data_file" ]]; then
|
|
||||||
[ -z "$OPENAI_API_KEY" ] && raise_error 'OpenAI API key is required.' 11
|
|
||||||
|
|
||||||
local fn="openai_${api_name//\//_}"
|
|
||||||
[ "$(type -t "$fn")" = function ] || raise_error "API '$api_name' is not available." 12
|
|
||||||
"$fn"
|
|
||||||
else
|
|
||||||
[ ${#rest_args[@]} -gt 0 ] || raise_error "Prompt for new topic is required" 13
|
|
||||||
create_topic
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
|
@ -1,56 +0,0 @@
|
||||||
#!/usr/bin/zsh
|
|
||||||
|
|
||||||
ROOT_PREFIX="${REPO_ROOT_DIR:-$HOME/repo}"
|
|
||||||
GIT_USER="${REPO_GIT_USER:-git}"
|
|
||||||
|
|
||||||
clean_path()
|
|
||||||
{
|
|
||||||
stripped=$1
|
|
||||||
prefix="http://"
|
|
||||||
stripped="${stripped#"$prefix"}"
|
|
||||||
prefix="https://"
|
|
||||||
stripped="${stripped#"$prefix"}"
|
|
||||||
prefix="git@"
|
|
||||||
stripped="${stripped#"$prefix"}"
|
|
||||||
suffix=".git"
|
|
||||||
stripped="${stripped%"$suffix"}"
|
|
||||||
stripped=$(echo "$stripped" | sed -e "s/:/\//1")
|
|
||||||
echo $stripped
|
|
||||||
}
|
|
||||||
|
|
||||||
show_help()
|
|
||||||
{
|
|
||||||
printf "Usage: repo <get|goto> <repo>\n"
|
|
||||||
}
|
|
||||||
|
|
||||||
do_get()
|
|
||||||
{
|
|
||||||
cleaned=$(clean_path $1)
|
|
||||||
output_path="$ROOT_PREFIX/$cleaned"
|
|
||||||
mkdir -p $output_path
|
|
||||||
if [ ! -d $output_path/.git ]; then
|
|
||||||
repourl=$(echo "$GIT_USER@$cleaned" | sed -e "s/\//:/1")
|
|
||||||
git clone $repourl $output_path
|
|
||||||
fi
|
|
||||||
cd $output_path
|
|
||||||
}
|
|
||||||
|
|
||||||
do_goto()
|
|
||||||
{
|
|
||||||
cleaned=$(clean_path $1)
|
|
||||||
output_path="$ROOT_PREFIX/$cleaned"
|
|
||||||
cd $output_path
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
case "$1" in
|
|
||||||
'get' )
|
|
||||||
do_get $2
|
|
||||||
do_goto $2
|
|
||||||
;;
|
|
||||||
'go' | "goto")
|
|
||||||
do_goto $2
|
|
||||||
;;
|
|
||||||
'help' | "-h"| "-help" | "--help")
|
|
||||||
show_help
|
|
||||||
esac
|
|
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
bspad hide
|
|
|
@ -1,5 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
OUTPUT_FILENAME="/home/a/shots/$(date '+%Y-%m-%d_%H:%M:%S').png"
|
|
||||||
scrot --freeze -F $OUTPUT_FILENAME -s
|
|
||||||
curl https://put.gay -H "authorization: $PASTE_TOKEN" --silent -F "file=@$OUTPUT_FILENAME;headers=\"bucket: shot\"" | xclip -selection clipboard
|
|
Loading…
Reference in New Issue