add scripts

This commit is contained in:
sudacode 2025-05-03 23:07:18 -07:00
parent 5cff20c839
commit 773f468b37
Signed by: sudacode
SSH Key Fingerprint: SHA256:lT5C2bB398DcX6daCF/gYFNSTK3y+Du3oTGUnYzfTEw
20 changed files with 1081 additions and 0 deletions

View File

@ -0,0 +1,171 @@
package main
import (
"encoding/json"
"fmt"
"io"
"math/rand"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"time"
)
const (
wallhavenAPI = "https://wallhaven.cc/api/v1"
wallpaperDir = "Pictures/wallpapers/wallhaven"
)
var topics = []string{
"132262 - Mobuseka",
"konosuba",
"bunny girl senpai",
"oshi no ko",
"kill la kill",
"lofi",
"eminence in shadow",
}
type WallhavenResponse struct {
Data []struct {
Path string `json:"path"`
} `json:"data"`
}
func main() {
// Initialize random source
r := rand.New(rand.NewSource(time.Now().UnixNano()))
// Check if a file path was provided as argument
if len(os.Args) > 1 {
imgPath := os.Args[1]
if _, err := os.Stat(imgPath); err == nil {
changeWallpaper(imgPath, "")
return
}
}
// Create wallpaper directory if it doesn't exist
homeDir, err := os.UserHomeDir()
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting home directory: %v\n", err)
os.Exit(1)
}
wallpaperPath := filepath.Join(homeDir, wallpaperDir)
if err := os.MkdirAll(wallpaperPath, 0755); err != nil {
fmt.Fprintf(os.Stderr, "Error creating wallpaper directory: %v\n", err)
os.Exit(1)
}
// Download and set new wallpaper
newWallpaper, topic := downloadRandomWallpaper(wallpaperPath, r)
if newWallpaper != "" {
changeWallpaper(newWallpaper, topic)
} else {
notify("Failed to download new wallpaper", "critical")
os.Exit(1)
}
}
func downloadRandomWallpaper(wallpaperPath string, r *rand.Rand) (string, string) {
// Select random topic
topic := topics[r.Intn(len(topics))]
var query string
var displayName string
// Check if the topic is a tag ID with name
if tagRegex := regexp.MustCompile(`^(\d+)\s*-\s*(.+)$`); tagRegex.MatchString(topic) {
matches := tagRegex.FindStringSubmatch(topic)
query = fmt.Sprintf("id:%s", matches[1])
displayName = strings.TrimSpace(matches[2])
} else {
query = url.QueryEscape(topic)
displayName = topic
}
fmt.Fprintf(os.Stderr, "Searching for wallpapers related to: %s\n", displayName)
// Get wallpapers from Wallhaven API
resp, err := http.Get(fmt.Sprintf("%s/search?q=%s&purity=100&categories=110&sorting=random", wallhavenAPI, query))
if err != nil {
fmt.Fprintf(os.Stderr, "Error fetching from Wallhaven: %v\n", err)
return "", ""
}
defer resp.Body.Close()
// Parse response
var wallhavenResp WallhavenResponse
if err := json.NewDecoder(resp.Body).Decode(&wallhavenResp); err != nil {
fmt.Fprintf(os.Stderr, "Error parsing response: %v\n", err)
return "", ""
}
if len(wallhavenResp.Data) == 0 {
fmt.Fprintf(os.Stderr, "No wallpapers found for topic: %s\n", displayName)
return "", ""
}
// Select a random image from the results
randomIndex := r.Intn(len(wallhavenResp.Data))
wallpaperURL := wallhavenResp.Data[randomIndex].Path
filename := filepath.Base(wallpaperURL)
filepath := filepath.Join(wallpaperPath, filename)
fmt.Fprintf(os.Stderr, "Downloading: %s\n", filename)
resp, err = http.Get(wallpaperURL)
if err != nil {
fmt.Fprintf(os.Stderr, "Error downloading wallpaper: %v\n", err)
return "", ""
}
defer resp.Body.Close()
file, err := os.Create(filepath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error creating file: %v\n", err)
return "", ""
}
defer file.Close()
if _, err := io.Copy(file, resp.Body); err != nil {
fmt.Fprintf(os.Stderr, "Error saving wallpaper: %v\n", err)
return "", ""
}
return filepath, displayName
}
func changeWallpaper(wallpaperPath, topic string) {
// Save current wallpaper path
homeDir, _ := os.UserHomeDir()
wallpaperFile := filepath.Join(homeDir, ".wallpaper")
if err := os.WriteFile(wallpaperFile, []byte(wallpaperPath), 0644); err != nil {
fmt.Fprintf(os.Stderr, "Error saving wallpaper path: %v\n", err)
}
// Change wallpaper using hyprctl
cmd := exec.Command("hyprctl", "hyprpaper", "reload", ","+wallpaperPath)
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Error changing wallpaper: %v\n", err)
}
// Send notification
filename := filepath.Base(wallpaperPath)
message := fmt.Sprintf("Wallpaper changed to %s", filename)
if topic != "" {
message += fmt.Sprintf(" (%s)", topic)
}
notify(message, "normal")
}
func notify(message, urgency string) {
cmd := exec.Command("notify-send", "-i", "hyprpaper", "-u", urgency, "change-wallpaper.go", message)
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Error sending notification: %v\n", err)
}
}

View File

@ -0,0 +1,90 @@
#!/usr/bin/env bash
# Wallhaven API configuration
WALLHAVEN_API="https://wallhaven.cc/api/v1"
WALLPAPER_DIR="$HOME/Pictures/wallpapers"
TOPICS=(
"konosuba"
"bunny girl senpai"
"oshi no ko"
"kill la kill"
"lofi"
"eminence in shadow"
"132262 - Mobuseka"
)
# Create wallpaper directory if it doesn't exist
mkdir -p "$WALLPAPER_DIR"
# Function to download a random wallpaper from Wallhaven
download_random_wallpaper() {
# Select random topic
local random_topic="${TOPICS[$RANDOM % ${#TOPICS[@]}]}"
local query
local display_name
# Check if the topic is a tag ID with name
if [[ "$random_topic" =~ ^([0-9]+)[[:space:]]*-[[:space:]]*(.+)$ ]]; then
query="id:${BASH_REMATCH[1]}"
display_name="${BASH_REMATCH[2]}"
else
query=$(echo "$random_topic" | sed 's/ /+/g')
display_name="$random_topic"
fi
echo "Searching for wallpapers related to: $display_name" >&2
# Get wallpapers from Wallhaven API
local response=$(curl -s "$WALLHAVEN_API/search?q=$query&purity=100&categories=110&sorting=random")
# Get all image URLs and select a random one
local urls=($(echo "$response" | jq -r '.data[].path'))
if [ ${#urls[@]} -eq 0 ]; then
echo "No wallpapers found for topic: $display_name" >&2
return 1
fi
local random_index=$((RANDOM % ${#urls[@]}))
local url="${urls[$random_index]}"
if [ -n "$url" ] && [ "$url" != "null" ]; then
local filename=$(basename "$url")
echo "Downloading: $filename" >&2
curl -s "$url" -o "$WALLPAPER_DIR/$filename"
if [ $? -eq 0 ]; then
echo "$WALLPAPER_DIR/$filename"
echo "$display_name" > "$WALLPAPER_DIR/.last_topic"
return 0
fi
fi
echo "No wallpapers found for topic: $display_name" >&2
return 1
}
# Handle direct image file input
if [[ -f "$1" ]]; then
echo "Changing wallpaper to $1"
echo "$1" > "$HOME/.wallpaper"
hyprctl hyprpaper reload ,"$1"
notify-send -i hyprpaper -u normal "change-wallpaper.sh" "Wallpaper changed to ${1##*/}"
exit 0
fi
# Download a new random wallpaper
new_wallpaper=$(download_random_wallpaper)
if [ -n "$new_wallpaper" ] && [ -f "$new_wallpaper" ]; then
echo "Changing wallpaper to $new_wallpaper"
echo "$new_wallpaper" > "$HOME/.wallpaper"
# Get the topic used for this wallpaper
topic=$(cat "$WALLPAPER_DIR/.last_topic")
# Apply the selected wallpaper
hyprctl hyprpaper reload ,"$new_wallpaper"
notify-send -i hyprpaper -u normal "change-wallpaper.sh" "Wallpaper changed to ${new_wallpaper##*/wallpapers/} ($topic)"
else
notify-send -i hyprpaper -u critical "change-wallpaper.sh" "Failed to download new wallpaper"
exit 1
fi

14
projects/scripts/dragon.sh Executable file
View File

@ -0,0 +1,14 @@
#!/bin/bash
set -Eeuo pipefail
HOME="/home/$(whoami)"
clip="$(wl-paste)"
if [[ -z "$clip" ]]; then
notify-send "Dragon" "Clipboard is empty"
exit 1
fi
DIR="$(hyprctl activeworkspace | grep -i lastwindowtitle | sed 's/\slastwindowtitle: //')"
DIR="${DIR//\~/$HOME}"
PTH="$DIR/$(basename "$clip")"
if [[ -e "$PTH" ]]; then
dragon-drop "$PTH"
fi

29
projects/scripts/mpv-add.sh Executable file
View File

@ -0,0 +1,29 @@
#!/usr/bin/env bash
set -Eeuo pipefail
URL="${1:-$(wl-paste -p)}"
MPV_SOCKET=/tmp/mpvsocket
ICON_PATH="$HOME/.local/share/icons/Magna-Glassy-Dark-Icons/apps/48/mpv.svg"
TITLE="mpv-add.sh"
if [[ -z "$URL" ]]; then
notify-send -i "$ICON_PATH" "$TITLE" "No URL provided"
exit 1
fi
if ! [[ -f "$URL" ]] && ! yt-dlp --simulate "$URL"; then
notify-send -i "$ICON_PATH" "$TITLE" "Invalid URL"
exit 1
fi
if ! pgrep -x mpv &> /dev/null; then
mpv "$URL" &> /dev/null &
notify-send -i "$ICON_PATH" "$TITLE" "Playing $URL"
else
if echo "{ \"command\": [\"script-message\", \"add_to_queue\", \"$URL\" ] }" | socat - "$MPV_SOCKET" &> /dev/null; then
notify-send -i "$ICON_PATH" "$TITLE" "Added $URL to queue"
else
notify-send -i "$ICON_PATH" "$TITLE" "Failed to add $URL to queue"
fi
fi

15
projects/scripts/ocr.sh Executable file
View File

@ -0,0 +1,15 @@
#!/bin/bash
set -Eeuo pipefail
# RES="$(slurp | grim -g - - | gazou | sed '1d;$d')"
# # Truncate RES for display if it's longer than 100 characters
# DISPLAY_RES="${RES:0:100}"
# if [ ${#RES} -gt 100 ]; then
# DISPLAY_RES="${DISPLAY_RES}..."
# fi
# notify-send "GAZOU" "Text: $DISPLAY_RES"
# echo "$RES" | wl-copy
slurp | grim -g - /tmp/ocr.png || exit 1
transformers_ocr recognize --image-path /tmp/ocr.png || exit 1
notify-send "tramsformers_ocr" "Text: $DISPLAY_RES"

View File

@ -0,0 +1,93 @@
#!/bin/sh
# Version 1.2
# shoutout to https://gist.github.com/Cephian/f849e326e3522be9a4386b60b85f2f23 for the original script,
# https://github.com/xythh/ added the ankiConnect functionality
# toggle record computer audio (run once to start, run again to stop)
# dependencies: ffmpeg, pulseaudio, curl
# where recording gets saved, gets deleted after being imported to anki
DIRECTORY="$HOME/.cache/"
FORMAT="mp3" # ogg or mp3
# cut file since it glitches a bit at the end sometimes
CUT_DURATION="0.1"
#port used by ankiconnect
ankiConnectPort="8765"
# gets the newest created card, so make sure to create the card first with yomichan
newestNoteId=$(curl -s localhost:$ankiConnectPort -X POST -d '{"action": "findNotes", "version": 6, "params": { "query": "is:new"}}' | jq '.result[-1]')
#Audio field name
audioFieldName="SentenceAudio"
#if there is no newest note, you either have a complete empty anki or ankiconnect isn't running
if [ "$newestNoteId" = "" ]; then
notify-send "anki connect not found"
exit 1
fi
if pgrep -f "parec"; then
pkill -f "parec"
else
time=$(date +%s)
name="$DIRECTORY/$time"
wav_file="$name.wav"
out_file="$name.$FORMAT"
if ! [ -d "$DIRECTORY" ]; then
mkdir "$DIRECTORY"
fi
notify-send -t 1000 "Audio recording started"
#timeout 1m arecord -t wav -f cd "$wav_file"
# just grabs last running source... may not always work if your pulseaudio setup is complicated
if ! timeout 1m parec -d"$(pactl list sinks | grep -B1 'State: RUNNING' | sed -nE 's/Sink #(.*)/\1/p' | tail -n 1)" --file-format=wav "$wav_file"; then
notify-send "Error recording " "most likely no audio playing"
rm "$wav_file"
exit 1
fi
input_duration=$(ffprobe -v error -select_streams a:0 -show_entries stream=duration -of default=noprint_wrappers=1:nokey=1 "$wav_file")
output_duration=$(echo "$input_duration"-"$CUT_DURATION" | bc)
# encode file and delete OG
if [ $FORMAT = "ogg" ]; then
ffmpeg -i "$wav_file" -vn -codec:a libvorbis -b:a 64k -t "$output_duration" "$out_file"
elif [ $FORMAT = "mp3" ]; then
ffmpeg -i "$wav_file" -vn -codec:a libmp3lame -qscale:a 1 -t "$output_duration" "$out_file"
else
notify-send "Record Error" "Unknown format $FORMAT"
fi
rm "$wav_file"
# Update newest note with recorded audio
curl -s localhost:$ankiConnectPort -X POST -d '{
"action": "updateNoteFields",
"version": 6,
"params": {
"note": {
"id": '"$newestNoteId"',
"fields": {
"'$audioFieldName'": ""
},
"audio": [{
"path": "'"$out_file"'",
"filename": "'"$time"'.'$FORMAT'",
"fields": [
"'$audioFieldName'"
]
}]
}
}
}'
# opens changed note, comment if you don't want it.
curl -s localhost:$ankiConnectPort -X POST -d '{
"action": "guiBrowse",
"version": 6,
"params": {
"query": "nid:'"$newestNoteId"'"
}
}'
notify-send -t 1000 "Audio recording copied"
rm "$out_file"
fi

View File

@ -0,0 +1,75 @@
#!/bin/sh
# Version 1.2
# click and drag to screenshot dragged portion
# click on specific window to screenshot window area
# dependencies: imagemagick, xclip,curl maybe xdotool (see comment below)
# shoutout to https://gist.github.com/Cephian/f849e326e3522be9a4386b60b85f2f23 for the original script,
# https://github.com/xythh/ added the ankiConnect functionality
# if anki is running the image is added to your latest note as a jpg, if anki is not running it's added to your clipboard as a png
time=$(date +%s)
tmp_file="$HOME/.cache/$time"
ankiConnectPort="8765"
pictureField="Picture"
quality="90"
# This gets your notes marked as new and returns the newest one.
newestNoteId=$(curl -s localhost:$ankiConnectPort -X POST -d '{"action": "findNotes", "version": 6, "params": { "query": "is:new"}}' | jq '.result[-1]')
# you can remove these two lines if you don't have software which
# makes your mouse disappear when you use the keyboard (e.g. xbanish, unclutter)
# https://github.com/ImageMagick/ImageMagick/issues/1745#issuecomment-777747494
xdotool mousemove_relative 1 1
xdotool mousemove_relative -- -1 -1
# if anki connect is running it will return your latest note id, and the following code will run, if anki connect is not running nothing is return.
if [ "$newestNoteId" != "" ]; then
if ! import -quality $quality "$tmp_file.jpg"; then
# most likley reason this returns a error, is for fullscreen applications that take full control which does not allowing imagemagick to select the area, use windowed fullscreen or if running wine use a virtual desktop to avoid this.
notify-send "Error screenshoting " "most likely unable to find selection"
exit 1
fi
curl -s localhost:$ankiConnectPort -X POST -d '{
"action": "updateNoteFields",
"version": 6,
"params": {
"note": {
"id": '"$newestNoteId"',
"fields": {
"'$pictureField'": ""
},
"picture": [{
"path": "'"$tmp_file"'.jpg",
"filename": "paste-'"$time"'.jpg",
"fields": [
"'$pictureField'"
]
}]
}
}
}'
#remove if you don't want anki to show you the card you just edited
curl -s localhost:$ankiConnectPort -X POST -d '{
"action": "guiBrowse",
"version": 6,
"params": {
"query": "nid:'"$newestNoteId"'"
}
}'
#you can comment this if you do not use notifcations.
notify-send "Screenshot Taken" "Added to note"
rm "$tmp_file.jpg"
else
if ! import -quality $quality "$tmp_file.png"; then
notify-send "Error screenshoting " "most likely unable to find selection"
exit 1
fi
# we use pngs when copying to clipboard because they have greater support when pasting.
xclip -selection clipboard -target image/png -i "$tmp_file.png"
rm "$tmp_file.png"
#you can comment this if you do not use notifcations.
notify-send "Screenshot Taken" "Copied to clipboard"
fi

109
projects/scripts/screenshot.sh Executable file
View File

@ -0,0 +1,109 @@
#!/usr/bin/env bash
# GUI Screenshot Tool for Wayland Using Zenity, Grim, Slurp, and Rofi
SCRIPT_NAME=$(basename "$0")
TMP_DIR=/tmp
DEFAULT_FILENAME=screenshot.png
TMP_SCREENSHOT="$TMP_DIR/$DEFAULT_FILENAME"
HYPRLAND_REGEX='.at[0],(.at[1]) .size[0]x(.size[1])'
REQUIREMENTS=(grim slurp rofi zenity wl-copy)
USE_NOTIFICATIONS=1
CHOICES=(
"1. Select a region and save - slurp | grim -g - \"$TMP_SCREENSHOT\""
"2. Select a region and copy to clipboard - slurp | grim -g - - | wl-copy"
"3. Whole screen - grim \"$TMP_SCREENSHOT\""
"4. Current window - hyprctl -j activewindow | jq -r \"${HYPRLAND_REGEX}\" | grim -g - \"$TMP_SCREENSHOT\""
"5. Edit - slurp | grim -g - - | swappy -f -"
"6. Quit - exit 0"
)
notify() {
local body="$1"
local title="$2"
if [[ -z "$body" ]]; then
echo "notify: No message provided"
return 1
fi
if [[ -z "$title" ]]; then
title="$SCRIPT_NAME"
fi
if ((USE_NOTIFICATIONS)); then
notify-send "$title" "$body"
else
printf "%s\n%s\n" "$title" "$body"
fi
return 0
}
check_deps() {
for cmd in "${REQUIREMENTS[@]}"; do
if ! command -v "$cmd" &> /dev/null; then
echo "Error: $cmd is not installed. Please install it first."
exit 1
fi
done
}
main() {
CHOICE="$(rofi -dmenu -i -p "Enter option or select from the list" \
-mesg "Select a Screenshot Option" \
-a 0 -no-custom -location 0 \
-yoffset 30 -xoffset 30 \
-theme-str 'listview {columns: 2; lines: 3;} window {width: 45%;}' \
-window-title "$SCRIPT_NAME" \
-format 'i' \
<<< "$(printf "%s\n" "${CHOICES[@]%% - *}")")"
if [[ -z "$CHOICE" ]]; then
notify "No option selected." ""
exit 0
fi
sleep 0.2 # give time for the rofi window to close
CMD="${CHOICES[$CHOICE]#* -}"
if [[ -z "$CMD" ]]; then
notify "No option selected." ""
exit 0
fi
# For option 2 (copy to clipboard), handle differently
if [[ "$CHOICE" == "1" ]]; then
if eval "$CMD"; then
notify "Screenshot copied to clipboard"
exit 0
else
notify "An error occurred while taking the screenshot."
exit 1
fi
fi
if ! eval "$CMD"; then
notify "An error occurred while taking the screenshot."
exit 1
fi
notify "screenshot.sh" "Screenshot saved temporarily.\nChoose where to save it permanently"
FILE=$(zenity --file-selection --title="Save Screenshot" --filename="$DEFAULT_FILENAME" --save 2> /dev/null)
case "$?" in
0)
if mv "$TMP_SCREENSHOT" "$FILE"; then
notify "Screenshot saved to $FILE"
else
notify "Failed to save screenshot to $FILE"
fi
;;
1)
rm -f "$TMP_SCREENSHOT"
notify "Screenshot discarded"
;;
-1)
notify "An unexpected error has occurred."
;;
esac
}
check_deps
main

2
projects/scripts/teppei/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
env
.git

10
projects/scripts/teppei/open.sh Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
EP="$1"
DIR="/truenas/sudacode/japanese/nihongo-con-teppei/Nihongo-Con-Teppei-E$EP.mp3"
export FONTCONFIG_FILE="$HOME/.config/mpv/mpv-fonts.conf"
if mpv --profile=builtin-pseudo-gui --vid=1 --external-file=pod/cover.jpg "$DIR"; then
echo "Finished playing Nihongo Con Teppei E$EP"
else
echo "Failed to play Nihongo Con Teppei E$EP"
fi

View File

@ -0,0 +1,92 @@
#!/usr/bin/env python
import logging
from argparse import ArgumentParser
from requests import get
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
AUDIO_BASE_URL = (
"https://www.heypera.com/listen/nihongo-con-teppei-for-beginners/{}/next"
)
SUB_BASE_URL = "https://storage.googleapis.com/pera-transcripts/nihongo-con-teppei-for-beginners/transcripts/{}.vtt"
def get_audio_url(episode_num: int):
chrome_options = Options()
chrome_options.add_argument("--headless")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--no-sandbox")
driver = webdriver.Chrome(options=chrome_options)
try:
driver.get(AUDIO_BASE_URL.format(episode_num))
# Wait for the audio element to be present
audio_element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.TAG_NAME, "audio"))
)
audio_url = audio_element.get_attribute("src")
if audio_url:
logger.info(f"Audio URL: {audio_url}")
else:
logger.error("No audio URL found")
return audio_url
except Exception as e:
logger.error(f"Error: {e}")
raise e
finally:
driver.quit()
def get_sub_url(episode_num: int):
return SUB_BASE_URL.format(episode_num)
def download_file(url: str, filename: str):
response = get(url, timeout=10)
if response.status_code != 200:
logger.error(f"Failed to download {filename}")
return
with open(filename, "wb") as file:
file.write(response.content)
logger.info(f"Downloaded {filename}")
def parse_args():
parser = ArgumentParser(description="Get the audio URL for a given episode number")
parser.add_argument(
"episode_num", type=int, help="The episode number to get the audio URL for"
)
parser.add_argument(
"-d", "--download", action="store_true", help="Download the audio file"
)
parser.add_argument("-o", "--output", help="Output directory name")
return parser.parse_args()
if __name__ == "__main__":
args = parse_args()
if args.episode_num < 1:
logger.error("Episode number must be greater than 0")
episode = args.episode_num
audio = get_audio_url(episode)
sub = get_sub_url(episode)
if args.download:
if args.output:
download_file(audio, f"{args.output}/Nihongo-Con-Teppei-E{episode:0>2}.mp3")
download_file(sub, f"{args.output}/Nihongo-Con-Teppei-E{episode:0>2}.vtt")
else:
download_file(audio, f"Nihongo-Con-Teppei-E{episode:0>2}.mp3")
download_file(sub, f"Nihongo-Con-Teppei-E{episode:0>2}.vtt")
else:
print(f"Audio URL: {audio}")
print(f"Subtitle URL: {sub}")

View File

@ -0,0 +1,9 @@
#!/bin/bash
CMD="$1"
BEFORE_SPACE="${CMD%% *}"
if command -v "$BEFORE_SPACE" &> /dev/null; then
eval "$CMD" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/'\''/\&#39;/g'
else
echo "$CMD" | sed 's/&/\&amp;/g; s/</\&lt;/g; s/>/\&gt;/g; s/'\''/\&#39;/g'
fi

View File

@ -0,0 +1,238 @@
#!/usr/bin/env python3
import gi
gi.require_version("Playerctl", "2.0")
import argparse
import json
import logging
import os
import signal
import sys
from typing import List
import gi
from gi.repository import GLib, Playerctl
from gi.repository.Playerctl import Player
logger = logging.getLogger(__name__)
is_plain = False
def signal_handler(sig, frame):
logger.info("Received signal to stop, exiting")
sys.stdout.write("\n")
sys.stdout.flush()
# loop.quit()
sys.exit(0)
class PlayerManager:
def __init__(self, selected_player=None, excluded_player=[]):
self.manager = Playerctl.PlayerManager()
self.loop = GLib.MainLoop()
self.manager.connect(
"name-appeared", lambda *args: self.on_player_appeared(*args)
)
self.manager.connect(
"player-vanished", lambda *args: self.on_player_vanished(*args)
)
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
self.selected_player = selected_player
self.excluded_player = excluded_player.split(",") if excluded_player else []
self.init_players()
def init_players(self):
for player in self.manager.props.player_names:
if player.name in self.excluded_player:
continue
if self.selected_player is not None and self.selected_player != player.name:
logger.debug(f"{player.name} is not the filtered player, skipping it")
continue
self.init_player(player)
def run(self):
logger.info("Starting main loop")
self.loop.run()
def init_player(self, player):
logger.info(f"Initialize new player: {player.name}")
player = Playerctl.Player.new_from_name(player)
player.connect("playback-status", self.on_playback_status_changed, None)
player.connect("metadata", self.on_metadata_changed, None)
self.manager.manage_player(player)
self.on_metadata_changed(player, player.props.metadata)
def get_players(self) -> List[Player]:
return self.manager.props.players
def write_output(self, text, player):
logger.debug(f"Writing output: {text}")
if not is_plain:
output = {
"text": text,
"class": "custom-" + player.props.player_name,
"alt": player.props.player_name,
}
sys.stdout.write(json.dumps(output))
sys.stdout.flush()
else:
sys.stdout.write(text + "\n")
# sys.stdout.write(text)
sys.stdout.flush()
def clear_output(self):
sys.stdout.write("\n")
sys.stdout.flush()
def on_playback_status_changed(self, player, status, _=None):
logger.debug(
f"Playback status changed for player {player.props.player_name}: {status}"
)
self.on_metadata_changed(player, player.props.metadata)
def get_first_playing_player(self):
players = self.get_players()
logger.debug(f"Getting first playing player from {len(players)} players")
if len(players) > 0:
# if any are playing, show the first one that is playing
# reverse order, so that the most recently added ones are preferred
for player in players[::-1]:
if player.props.status == "Playing":
return player
# if none are playing, show the first one
return players[0]
else:
logger.debug("No players found")
return None
def show_most_important_player(self):
logger.debug("Showing most important player")
# show the currently playing player
# or else show the first paused player
# or else show nothing
current_player = self.get_first_playing_player()
if current_player is not None:
self.on_metadata_changed(current_player, current_player.props.metadata)
else:
self.clear_output()
def on_metadata_changed(self, player, metadata, _=None):
logger.debug(f"Metadata changed for player {player.props.player_name}")
player_name = player.props.player_name
artist = player.get_artist()
title = player.get_title()
if title is not None:
title = title.replace("&", "&amp;")
else:
title = "No title"
track_info = ""
if (
player_name == "spotify"
and "mpris:trackid" in metadata.keys()
and ":ad:" in player.props.metadata["mpris:trackid"]
):
track_info = "Advertisement"
elif artist is not None and title is not None:
track_info = f"{artist} - {title}"
else:
track_info = title
if track_info:
if player.props.status == "Playing":
track_info = "playing " + track_info
else:
track_info = "paused " + track_info
# only print output if no other player is playing
current_playing = self.get_first_playing_player()
if (
current_playing is None
or current_playing.props.player_name == player.props.player_name
):
self.write_output(track_info, player)
else:
logger.debug(
f"Other player {current_playing.props.player_name} is playing, skipping"
)
def on_player_appeared(self, _, player):
logger.info(f"Player has appeared: {player.name}")
if player.name in self.excluded_player:
logger.debug(
"New player appeared, but it's in exclude player list, skipping"
)
return
if player is not None and (
self.selected_player is None or player.name == self.selected_player
):
self.init_player(player)
else:
logger.debug(
"New player appeared, but it's not the selected player, skipping"
)
def on_player_vanished(self, _, player):
logger.info(f"Player {player.props.player_name} has vanished")
self.show_most_important_player()
def parse_arguments():
parser = argparse.ArgumentParser()
# Increase verbosity with every occurrence of -v
parser.add_argument("-v", "--verbose", action="count", default=0)
parser.add_argument("-x", "--exclude", "- Comma-separated list of excluded player")
parser.add_argument(
"-p", "--plain", action="store_true", help="Plain text output", default=False
)
# Define for which player we"re listening
parser.add_argument("--player")
parser.add_argument("--enable-logging", action="store_true")
return parser.parse_args()
def main():
global is_plain
arguments = parse_arguments()
# Initialize logging
if arguments.enable_logging:
logfile = os.path.join(
os.path.dirname(os.path.realpath(__file__)), "media-player.log"
)
logging.basicConfig(
filename=logfile,
level=logging.DEBUG,
format="%(asctime)s %(name)s %(levelname)s:%(lineno)d %(message)s",
)
# Logging is set by default to WARN and higher.
# With every occurrence of -v it's lowered by one
logger.setLevel(max((3 - arguments.verbose) * 10, 0))
logger.info("Creating player manager")
if arguments.player:
logger.info(f"Filtering for player: {arguments.player}")
if arguments.exclude:
logger.info(f"Exclude player {arguments.exclude}")
if arguments.plain:
logger.info("Plain text output")
is_plain = arguments.plain
player = PlayerManager(arguments.player, arguments.exclude)
player.run()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,41 @@
#!/bin/bash
set -e
# https://blog.dhampir.no/content/sleeping-without-a-subprocess-in-bash-and-how-to-sleep-forever
snore() {
local IFS
[[ -n "${_snore_fd:-}" ]] || exec {_snore_fd}<> <(:)
read -r ${1:+-t "$1"} -u $_snore_fd || :
}
DELAY=0.2
while snore $DELAY; do
WP_OUTPUT=$(wpctl get-volume @DEFAULT_AUDIO_SINK@)
if [[ $WP_OUTPUT =~ ^Volume:[[:blank:]]([0-9]+)\.([0-9]{2})([[:blank:]].MUTED.)?$ ]]; then
if [[ -n ${BASH_REMATCH[3]} ]]; then
printf "MUTE\n"
else
VOLUME=$((10#${BASH_REMATCH[1]}${BASH_REMATCH[2]}))
ICON=(
""
""
""
)
if [[ $VOLUME -gt 50 ]]; then
printf "%s" "${ICON[0]} "
elif [[ $VOLUME -gt 25 ]]; then
printf "%s" "${ICON[1]} "
elif [[ $VOLUME -ge 0 ]]; then
printf "%s" "${ICON[2]} "
fi
printf "$VOLUME%%\n"
fi
fi
done
exit 0

View File

@ -0,0 +1,25 @@
#!/bin/sh
PLAYER="$1"
if [ -z "$PLAYER" ]; then
echo "Usage: $0 <player>"
exit 1
fi
STATUS="$(playerctl -p "$PLAYER" status)"
if [ -z "$STATUS" ] || [ "$STATUS" = "Stopped" ]; then
exit 0
elif [ "$STATUS" = "Paused" ]; then
STATUS=" "
elif [ "$STATUS" = "Playing" ]; then
STATUS=" "
else
exit 0
fi
TITLE="$(playerctl -p "$PLAYER" metadata title)"
ARTIST="$(playerctl -p "$PLAYER" metadata artist)"
printf "%s\n" "$STATUS$TITLE - $ARTIST"

View File

@ -0,0 +1,19 @@
#!/bin/sh
if pgrep -af "waybar -c /home/sudacode/.config/waybar/catppuccin-macchiato/config-battery.jsonc -s /home/sudacode/.config/waybar/catppuccin-macchiato/style.css" ||
pgrep -af "waybar -c /home/sudacode/.config/waybar/catppuccin-macchiato/config.jsonc -s /home/sudacode/.config/waybar/catppuccin-macchiato/style.css"; then
killall waybar
fi
BASE_DIR="$HOME/.config/waybar/catppuccin-macchiato"
NODE_NAME="$(hyprctl systeminfo | grep -i "node name" | sed 's/Node name: //')"
if [[ "$NODE_NAME" = "sc-arch" ]]; then
CONFIG="$BASE_DIR/config.jsonc"
else
CONFIG="$BASE_DIR/config-laptop.jsonc"
fi
waybar -c "$CONFIG" -s "$BASE_DIR/style.css" &>/dev/null &

View File

@ -0,0 +1,11 @@
#!/bin/sh
zscroll -p ' | ' --delay 0.2 \
--length 30 \
--match-command "$HOME/.config/waybar/scripts/playerctl.sh firefox" \
--match-text ' ' "" \
--match-text ' ' "--scroll 0" \
--match-text "^volume:" "--before-text '' --scroll 0 --after-text ''" \
--update-interval 1 \
--update-check true "$HOME/.config/waybar/scripts/playerctl.sh firefox" &
wait

View File

@ -0,0 +1,11 @@
#!/bin/bash
zscroll -p ' | ' --delay 0.2 \
--length 30 \
--match-command "$HOME/.config/waybar/scripts/escape-pango.sh 'mpc status'" \
--match-text "playing" "--before-text ' '" \
--match-text "paused" "--before-text ' ' --scroll 0" \
--match-text "^volume:" "--before-text '' --scroll 0 --after-text ''" \
--update-interval 1 \
--update-check true "$HOME/.config/waybar/scripts/escape-pango.sh 'mpc current'" &
wait

View File

@ -0,0 +1,11 @@
#!/bin/sh
zscroll -p ' | ' --delay 0.2 \
--length 30 \
--match-command "$HOME/.config/waybar/scripts/playerctl.sh mpv" \
--match-text ' ' "" \
--match-text ' ' "--scroll 0" \
--match-text "^volume:" "--before-text '' --scroll 0 --after-text ''" \
--update-interval 1 \
--update-check true "$HOME/.config/waybar/scripts/playerctl.sh mpv" &
wait

16
projects/scripts/waybar/wttr.sh Executable file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env bash
for _ in {1..5}; do
text="$(curl -s "https://wttr.in/$1?format=1" | sed -E "s/\s+/ /g")"
if [[ "$?" = 0 ]]; then
# text="$(echo "$text" | awk '{print $2}')"
tooltip=$(curl -s "https://wttr.in/$1?format=4")
if [ "$?" = 0 ]; then
tooltip=$(echo "$tooltip" | sed -E "s/\s+/ /g")
echo "{\"text\":\"$text\", \"tooltip\":\"$tooltip\"}"
exit
fi
fi
sleep 2
done
echo "{\"text\":\"error\", \"tooltip\":\"error\"}"