[feature] add scripts

This commit is contained in:
Klesh Wong 2020-11-17 16:26:46 +08:00
parent d5cec00a78
commit ebb9d37357
10 changed files with 775 additions and 0 deletions

45
bin/decrypt_dbeaver_passwords Executable file
View File

@ -0,0 +1,45 @@
#!/bin/env python3
# https://stackoverflow.com/questions/39928401/recover-db-password-stored-in-my-dbeaver-connection
# requires pycrypto lib (pip install pycrypto)
import sys
import base64
import os
import json
from Crypto.Cipher import AES
default_paths = [
'~/Library/DBeaverData/workspace6/General/.dbeaver/credentials-config.json',
'~/.local/share/DBeaverData/workspace6/General/.dbeaver/credentials-config.json',
'~/.local/share/.DBeaverData/workspace6/General/.dbeaver/credentials-config.json',
]
if len(sys.argv) < 2:
for path in default_paths:
filepath = os.path.expanduser(path)
try:
f = open(filepath, 'rb')
f.close()
break
except Exception as e:
pass
else:
filepath = sys.argv[1]
print(filepath)
#PASSWORD_DECRYPTION_KEY = bytes([-70, -69, 74, -97, 119, 74, -72, 83, -55, 108, 45, 101, 61, -2, 84, 74])
PASSWORD_DECRYPTION_KEY = bytes([186, 187, 74, 159, 119, 74, 184, 83, 201, 108, 45, 101, 61, 254, 84, 74])
data = open(filepath, 'rb').read()
decryptor = AES.new(PASSWORD_DECRYPTION_KEY, AES.MODE_CBC, data[:16])
padded_output = decryptor.decrypt(data[16:])
output = padded_output.rstrip(padded_output[-1:])
try:
print(json.dumps(json.loads(output), indent=4, sort_keys=True))
except:
print(output)

20
bin/ffmpeghelper Normal file
View File

@ -0,0 +1,20 @@
# addsound
ffmpeg -f lavfi -i aevalsrc=0 -i $infile -vcodec copy -acodec aac -map 0:0 -map 1:0 -shortest -y $outfile
# join
ffmpeg -i head2.mp4 -i input.mp4 ^
-filter_complex "[0:v:0][0:a:0][1:v:0][1:a:0]concat=n=2:v=1:a=1[outv][outa]" ^
-map "[outv]" -map "[outa]" -y output.mp4
# make
ffmpeg -f lavfi -i color=c=white:s=640x368:d=10^
-vf drawtext=fontfile=sarasa-gothic-sc-regular.ttf:fontsize=40:fontcolor=orange:x=(w-text_w)/2:y=60:text='3.18 勿忘国耻',^
drawtext=fontfile=sarasa-gothic-sc-regular.ttf:fontsize=16:fontcolor=black:x=(w-text_w)/2:y=130:text='战疫期间,宅家的日子里。',^
drawtext=fontfile=sarasa-gothic-sc-regular.ttf:fontsize=16:fontcolor=black:x=(w-text_w)/2:y=150:text='拿起两年没时间练习的吉它。弹奏一曲《爱的罗曼史》。',^
drawtext=fontfile=sarasa-gothic-sc-regular.ttf:fontsize=16:fontcolor=black:x=(w-text_w)-40:y=h-text_h-40-40:text='田东中学',^
drawtext=fontfile=sarasa-gothic-sc-regular.ttf:fontsize=16:fontcolor=black:x=(w-text_w)-40:y=h-text_h-20-40:text='初二6班',^
drawtext=fontfile=sarasa-gothic-sc-regular.ttf:fontsize=16:fontcolor=black:x=(w-text_w)-40:y=h-text_h-40:text='黄科宁',^
fps=fps=29.44 ^
-y head.mp4

116
bin/kc Executable file
View File

@ -0,0 +1,116 @@
#!/usr/bin/env python3
import json
import os
import sys
try:
import yaml
import clipboard
except:
print("please install PyYAML/clipboard first", file=sys.stderr)
print("sudo pip install PyYAML clipboard", file=sys.stderr)
sys.exit(-1)
new_config_data = clipboard.paste()
if not new_config_data:
print("clipboard is empty", file=sys.stderr)
sys.exit(-1)
try:
new_config = yaml.load(new_config_data, Loader=yaml.Loader)
except:
print("illegal yaml format", file=sys.stderr)
new_clusters = new_config.get('clusters')
new_contexts = new_config.get('contexts')
new_users = new_config.get('users')
if not new_clusters or not new_contexts or not new_users:
print("configuration yaml must have clusers/contexts/users", file=sys.stderr)
sys.exit(-1)
new_context_name = input("Enter context name:")
if not new_context_name:
print("aborted!", file=sys.stderr)
sys.exit(-1)
new_cluster_name = input("Enter cluster name:")
if not new_cluster_name:
print("aborted!", file=sys.stderr)
sys.exit(-1)
new_user_name = f'{new_cluster_name}-user'
# load config file
cfg_path = os.path.expanduser('~/.kube/config')
with open(cfg_path) as f:
config = yaml.load(f, Loader=yaml.Loader) or {}
config['apiVersion'] = config.get('apiVersion', 'v1')
config['kind'] = config.get('kind', 'Config')
config['clusters'] = config.get('clusters', [])
config['contexts'] = config.get('contexts', [])
config['users'] =config.get('users', [])
# merge cluster into config
def append_or_replace(array, elem, cond):
index = -1
for i, c in enumerate(array):
if cond(c):
index = i
break
if index > -1:
old_elem = array[index]
array[index] = elem
return old_elem
else:
array.append(elem)
def update_context_ref(old, new, ref_key):
if old and old['name'] != new['name']:
for ctx in config['contexts']:
if ctx[ref_key] == old['name']:
ctx[ref_key] = new['name']
new_context = new_contexts[0]
new_cluster = new_clusters[0]
new_user = new_users[0]
new_context['name'] = new_context_name
new_context['context']['cluster'] = new_cluster_name
new_context['context']['user'] = new_user_name
new_cluster['name'] = new_cluster_name
new_user['name'] = new_user_name
old_cluster = append_or_replace(
config['clusters'],
new_cluster,
lambda c: (
c['name'] == new_cluster_name or c['cluster'] == new_cluster['cluster']
)
)
update_context_ref(old_cluster, new_cluster, 'cluster')
old_user = append_or_replace(
config['users'],
new_user,
lambda u: (
u['name'] == new_user['name'] or u['user'] == new_user['user']
)
)
update_context_ref(old_user, new_user, 'user')
append_or_replace(
config['contexts'],
new_context,
lambda c: (
c['name'] == new_context_name
)
)
# save config file
config['current-context'] = new_context_name
with open(cfg_path, 'w') as f:
f.write(yaml.dump(config))

74
bin/mergesrt Executable file
View File

@ -0,0 +1,74 @@
#!/usr/bin/env python
import optparse, sys, os
from collections import namedtuple
optparser = optparse.OptionParser()
optparser.add_option("-i", dest="input", help="input multiple srt files", action='append')
optparser.add_option("-o", dest="output", help="output file")
(opts, _) = optparser.parse_args()
inputfile = opts.input
sub = namedtuple("sub", "begin, end, content")
inputcontent = []
def process(line):
(begin, end) = line[1][:-2].strip().split(" --> ")
content = line[2:]
return sub(begin, end, content)
def time(rawtime):
(hour, minute, seconds) = rawtime.strip().split(":")
(second, milisecond) = seconds.strip().split(",")
return int(milisecond) + 1000 * int(second) + 1000 * 60 * int(minute) + 1000 * 60 * 60 * int(hour)
def findnext(point, inputcontent):
smallest = sys.maxint
smallestid = 0
for i in range(len(point)):
if point[i] == len(inputcontent[i]):
continue
else:
begintime = time(inputcontent[i][point[i]].begin)
if begintime < smallest:
smallest = begintime
smallestid = i
return smallestid
def merge(inputcontent):
outputcontent = []
point = [0 for i in inputcontent]
maxpoint = [0 for i in inputcontent]
for i in range(len(inputcontent)):
maxpoint[i] = len(inputcontent[i])
while point != maxpoint:
nextid = findnext(point, inputcontent)
outputcontent.append(inputcontent[nextid][point[nextid]])
point[nextid] += 1
return outputcontent
def printsub(raw, f):
outputfile = open(f, 'w')
for i in range(len(raw)):
outputfile.write("%d\r\n" % (i+1))
outputfile.write("%s --> %s \r\n" % (raw[i].begin, raw[i].end))
for c in raw[i].content:
outputfile.write("%s"%c)
outputfile.write("\r\n")
for f in inputfile:
line = []
content = []
for l in open(f):
if l == "\r\n":
content.append(process(line))
line = []
else:
line.append(l)
if line:
content.append(process(line))
inputcontent.append(content)
outputraw = merge(inputcontent)
printsub(outputraw, opts.output)

101
bin/mounthd.sh Executable file
View File

@ -0,0 +1,101 @@
#!/bin/bash
set -e
MOUNT_PATH=${1-'/mnt/hgst3t'}
# find all umounted devices/partions and deal with them
rm -rf /tmp/umounted_devs
IFS=' '
NUM=1
lsblk --noheadings --raw | while read -ra INFO; do
DEV="${INFO[0]}"
SIZE="${INFO[3]}"
TYPE="${INFO[5]}"
MOUNT="${INFO[6]}"
# skip mounted entry
#blkid | grep -F "/dev/$DEV" > /dev/null && continue
echo " $NUM) /dev/$DEV $SIZE $TYPE" >> /tmp/umounted_devs
NUM=$(($NUM+1))
done
if [ ! -s /tmp/umounted_devs ]; then
echo "no operatable drive/partition found"
exit -1
fi
echo
echo Pick a drive/partition to process
echo
cat /tmp/umounted_devs
echo
read -p "Please enter the line number: " NUM
LINE=$(sed -n "${NUM}p" /tmp/umounted_devs)
IFS=' ' read LN DEV SIZE TYPE <<< $LINE
echo
echo You selected $TYPE $DEV with size of $SIZE
echo
init_drive() {
echo "g
n
1
y
w" | sudo fdisk $DEV
}
init_partition() {
echo formating partition $1
sudo umount $1 || true
sudo mkfs.exfat $1
}
mount_partition() {
# remove mounting record from fstab
sed "\#$MOUNT_PATH\s#d" /etc/fstab | sudo tee /etc/fstab
UUID=$(sudo blkid -s UUID -o value $1)
echo "UUID=$UUID $MOUNT_PATH exfat auto,user,rw,async 0 0" | sudo tee -a /etc/fstab
mkdir -p $MOUNT_PATH
sudo mount -a
mkdir -p $MOUNT_PATH/movies
sudo systemctl start transmission
sudo systemctl start smb
}
# disk selected
if [ "$TYPE" = "disk" ]; then
NUM=$(ls -l $DEV* | wc -l)
# alert if drive already has partition
if [ $NUM -gt 1 ]; then
read -p "partitions found on $DEV, are u sure to initialize this drive? [y/N]: " CONFIRM
[ "$CONFIRM" != 'y' ] && exit -1
fi
init_drive
# format newly created partition on that dev
PART=$(ls $DEV* | tail -1)
init_partition $PART
mount_partition $PART
# partition selected
elif [ "$TYPE" = "part" ]; then
# partition is unformatted
if [ -z "$(blkid $DEV)" ] ; then
init_partition $DEV
# partition is not exfat format
elif blkid $DEV | grep -Fv exfat > /dev/null; then
read -p "all data on $DEV will be destroyed, are u sure? [y/N]: " CONFIRM
[ "$CONFIRM" != 'y' ] && exit -1
init_partition $DEV
fi
# now, we known partition is exfat format
mount_partition $DEV
fi

98
bin/rds Executable file
View File

@ -0,0 +1,98 @@
#!/bin/bash
# Following regex is based on https://tools.ietf.org/html/rfc3986#appendix-B with
# additional sub-expressions to split authority into userinfo, host and port
#
readonly URI_REGEX='^(([^:/?#]+):)?(//((([^:/?#]+)?(:([^:/?#]+))?@)?([^:/?#]+)(:([0-9]+))?))(/([^?#]*))?(\?([^#]*))?(#(.*))?'
# ↑↑ ↑ ↑↑↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
# |2 scheme | ||6 userinfo passwd 7 host | 9 port | 11 rpath | 13 query | 15 fragment
# 1 scheme: | |5 userinfo@ 8 :… 10 path 12 ?… 14 #…
# | 4 authority
# 3 //…
IDX_SCHEME=2
IDX_USER=6
IDX_PASSWD=8
IDX_HOST=9
IDX_PORT=11
IDX_PATH=12
IDX_RPATH=13
IDX_QUERY=15
IDX_FRAGMENT=17
# processing argument
POSITIONAL=()
while [[ $# -gt 0 ]]; do
case "$1" in
-s|--ssh)
SSH="$2"
shift
shift
;;
--)
shift
POSITIONAL+=("$@")
break
;;
*)
POSITIONAL+=("$1")
shift
;;
esac
done
set -- "${POSITIONAL[@]}"
# check arguments
if [[ $# == 0 ]]; then
echo "Usage: $0 [-s|--ssh SSH_DESTINATION] <REDIS_URI> [...REDIS_CLI_ARGS]"
echo " -s, --ssh for ssh tunnel"
echo " REDIS_URI redis://:passwd@host:port/db"
exit -1
fi
# pop the REDIS_URI and process it
if [[ ! ($1 =~ $URI_REGEX) ]]; then
echo "Invalid redis uri"
exit -1
fi
shift
SCHEME=${BASH_REMATCH[$IDX_SCHEME]}
USER=${BASH_REMATCH[$IDX_USER]}
PASSWD=${BASH_REMATCH[$IDX_PASSWD]}
HOST=${BASH_REMATCH[$IDX_HOST]}
PORT=${BASH_REMATCH[$IDX_PORT]}
UPATH=${BASH_REMATCH[$IDX_PATH]}
RPATH=${BASH_REMATCH[$IDX_RPATH]}
QUERY=${BASH_REMATCH[$IDX_QUERY]}
FRAGMENT=${BASH_REMATCH[$IDX_FRAGMENT]}
# create ssh tunnel if `--ssh` is assigned
is-port-used() {
lsof -i -P -n | awk '{print $9}' | grep -F ":$1" >/dev/null
}
if [[ -n $SSH ]]; then
# find available
read lower_port upper_port < /proc/sys/net/ipv4/ip_local_port_range
while :; do
for (( port = lower_port ; port <= upper_port ; port++ )); do
! is-port-used $port && break 2
done
done
# forward local port to remote destination
ssh -N -L localhost:$port:$HOST:$PORT $SSH &
SSH_PID=$!
# wait until port is ready
while ! is-port-used $port; do
sleep 0.1
done
HOST=localhost
PORT=$port
fi
redis-cli -h "$HOST" -p "${PORT-6379}" -a "$PASSWD" -n "${RPATH-0}" "$@"
# clean up ssh connection
[[ -n $SSH_PID ]] && kill -9 $SSH_PID

38
bin/umounthd.sh Executable file
View File

@ -0,0 +1,38 @@
#!/bin/bash
# need exfat-utils on archlinux
set -e
# ensure target path is mounted
DRIVE_PATH=${1-'/mnt/hgst3t'}
if ! mountpoint -q -- "$DRIVE_PATH"; then
echo "$DRIVE_PATH is not mounted"
exit -1
fi
# get device
DEV=$(grep -F "$DRIVE_PATH" /proc/mounts | awk '{print $1}')
# stop services that might using this target drives
sudo systemctl stop transmission
sudo systemctl stop smb
if sudo lsof $DRIVE_PATH 2>/dev/null; then
$DRIVE_PATH is being used
exit -1
fi
# create archive index file
read -p "Please enter archive number: " NUM
[ "$NUM" -ne "$NUM" ] && echo $NUM is not a number && exit -1
tree -L 2 $DRIVE_PATH/movies > ~/hgst3t-$NUM.txt
# remove mounting record from fstab
sed "\#$DRIVE_PATH\s#d" /etc/fstab | sudo tee /etc/fstab
sudo umount $DRIVE_PATH
echo you can safely remove $DRIVE_PATH now

25
win/get-fontname.ps1 Normal file
View File

@ -0,0 +1,25 @@
[CmdletBinding()]
param (
[Parameter()]
[string]
$FontPath
)
Add-Type -AssemblyName PresentationCore
$face = New-Object System.Windows.Media.GlyphTypeface -ArgumentList "$(Resolve-Path $FontPath)"
$style = "$($face.Style)"
$weight = "$($face.Weight)"
$stretch = "$($face.Stretch)"
$name = $face.FamilyNames["en-US"] -replace '\W',''
if ($style -ne "Normal") {
$name += "-$($style)"
}
if ($weight -ne "Normal") {
$name += "-$($weight)"
}
if ($stretch -ne "Normal") {
$name += "-$(stretch)"
}
$name

202
win/pixabay.ps1 Normal file
View File

@ -0,0 +1,202 @@
[CmdletBinding()]
param (
[Parameter(Mandatory=$false)]
[string]
$ApiKey='18224187-6d4fdb0c31aebbab0f814ab5d',
[Parameter(Mandatory=$false)]
[string]
$Keyword,
[Parameter(Mandatory=$false)]
[string]
$Id,
[Parameter(Mandatory=$false)]
[ValidateSet('all', 'photo', 'illustration', 'vector')]
[string]
$Type,
[Parameter(Mandatory=$false)]
[ValidateSet('all', 'horizontal', 'vertical')]
[string]
$Orientation,
[Parameter(Mandatory=$false)]
[ValidateSet(
'backgrounds',
'fashion',
'nature',
'science',
'education',
'feelings',
'health',
'people',
'religion',
'places',
'animals',
'industry',
'computer',
'food',
'sports',
'transportation',
'travel',
'buildings',
'business',
'music'
)]
[string]
$Category,
[Parameter(Mandatory=$false)]
[int]
$MinWidth=0,
[Parameter(Mandatory=$false)]
[int]
$MinHeight=0,
[Parameter(Mandatory=$false)]
[ValidateSet(
"grayscale",
"transparent",
"red",
"orange",
"yellow",
"green",
"turquoise",
"blue",
"lilac",
"pink",
"white",
"gray",
"black",
"brown"
)]
[string[]]
$Colors,
[Parameter(Mandatory=$false)]
[bool]
$EditorsChoiceOnly=$false,
[Parameter(Mandatory=$false)]
[bool]
$SafeForWorkOnly=$false,
[Parameter(Mandatory=$false)]
[ValidateSet("popular", "latest")]
[string]
$Order='popular',
[Parameter(Mandatory=$false)]
[int]
$Page=1,
[Parameter(Mandatory=$false)]
[ValidateRange(3, 200)]
[int]
$Size=50,
[Parameter(Mandatory=$false)]
[string]
$OutFile,
[Parameter(Mandatory=$false)]
[string]
$OutDir,
[Parameter(Mandatory=$false)]
[bool]
$OutDirWithType=$true
)
if ($OutDir -and -not $OutFile) {
$OutFile
}
function Save-Hit {
param (
[Parameter()]
[PSCustomObject]
$hit
)
$FilePath = $OutFile
if (-not $FilePath) {
$FilePath = $OutDir
if ($OutDirWithType) {
$FilePath = Join-Path $FilePath ($hit.type -replace '\W+','_' )
}
if (-not (Test-Path $FilePath)) {
New-Item -ItemType Directory $FilePath | Out-Null
}
$FileName = ($hit.id.ToString() + '_' +
($hit.tags -split ", " | %{ $_ -replace '\s+','-' } | Join-String -Separator '_') +
'.' + $hit.largeImageURL.Split('.')[-1])
$FilePath = Join-Path $FilePath $FileName
}
$msg = "saving $($hit.id) to $FilePath"
Write-Host $msg.PadRight(100) -NoNewline
if (Test-Path $FilePath) {
Write-Host "[SKIP]"
} else {
$job = Start-Job -ScriptBlock {
try {
Invoke-WebRequest -TimeoutSec 5 -Uri $args[0] -OutFile $args[1]
$true
} catch {
$false
}
} -ArgumentList $hit.largeImageURL,$FilePath
$fg = 'red'
$tx = '[TIMEOUTED]'
if (Wait-Job $job -Timeout 20) {
$ok = Receive-Job $job
$fg = $ok ? 'green' : 'red'
$tx = $ok ? '[OK]' : '[Failed]'
}
Remove-Job -force $job
Write-Host -ForegroundColor $fg $tx
}
}
[System.Net.ServicePointManager]::MaxServicePointIdleTime = 5
$Res = Invoke-WebRequest -TimeoutSec 5 -Uri https://pixabay.com/api/ -Body @{
key = $ApiKey;
q = $Keyword;
id = $Id;
image_type = $Type;
orientation = $Orientation;
category = $Category;
min_width = $MinWidth;
min_height = $MinHeight;
colors = $Colors;
editors_choice = $EditorsChoiceOnly;
safesearch = $SafeForWorkOnly;
order = $Order;
page = $Page;
per_page = $Size;
} | ConvertFrom-Json
Write-Host "Total $($res.total) Accessible $($res.totalHits)"
$Listing = -not $OutFile -and -not $OutDir
if ($Res.hits.Length -eq 1) {
if ($Listing) {
$Res.hits[0]
} else {
Save-Hit $Res.hits[0]
}
} else {
if ($Listing) {
$Res.hits | Select-Object -Property id,type,largeImageURL | Format-Table
} else {
$Res.hits | %{ Save-Hit $_ }
# Save-Hit $Res.hits[0]
}
}

56
win/sentry.ps1 Normal file
View File

@ -0,0 +1,56 @@
[CmdletBinding()]
param (
[ValidateSet("Projects", "MarkRead")]
[string]
$CMD,
[Parameter(Mandatory=$false)]
[string]
$PRO,
[Parameter(Mandatory=$false)]
[string]
$ORG="malong",
[Parameter(Mandatory=$false)]
[string]
$TOKEN="4ef37b6c118e44a499450fb996f2d58ef81faa46e2f14f38aadfa6d4ee0c4062"
)
function Projects {
$PROJECTS_URL="https://sentry.malongtech.cn/api/0/projects/"
while ($PROJECTS_URL) {
$projectsRes = Invoke-WebRequest -Uri $PROJECTS_URL -Headers @{'Authorization'="Bearer $TOKEN"}
$projects = $projectsRes.Content | ConvertFrom-Json
if (-not $projects.Length) {
break
}
foreach ($project in $projects) {
Write-Host $project.id $project.name
}
$PROJECTS_URL=$projectsRes.Headers.Link
if (-not $PROJECTS_URL.Length) {
break
}
$PROJECTS_URL = $PROJECTS_URL[0].ToString()
$PROJECTS_URL=$PROJECTS_URL.SubString(1, $PROJECTS_URL.IndexOf(";")-2)
}
}
function MarkRead {
$ISSUES_URL="https://sentry.malongtech.cn/api/0/projects/$ORG/$PRO/issues/"
while ($true) {
$issuesRes = Invoke-WebRequest -Uri $ISSUES_URL -Headers @{'Authorization'="Bearer $TOKEN"}
$issues = $issuesRes.Content | ConvertFrom-Json
if (-not $issues.Length) {
break
}
$qs = $issues | Select-Object -Property id | %{"id=$($_.id)"} | Join-String -Separator "&"
$deleteRes = Invoke-WebRequest -Uri "$($ISSUES_URL)?$qs" -Method Delete -Body @{'id'=$ids} -Headers @{'Authorization'="Bearer $TOKEN"}
Write-Host "status: $($deleteRes.StatusCode) content: $($deleteRes.Content)"
}
}
switch ($CMD) {
"MarkRead" { MarkRead }
Default { Projects }
}