Updated extensions.

This commit is contained in:
Storm Dragon
2025-07-29 11:29:27 -04:00
parent 224b6bfa4a
commit 2b8bf031fa
17 changed files with 875 additions and 1 deletions
+79 -1
View File
@@ -1 +1,79 @@
Placeholder
# TTYverse Extensions
This directory contains bundled extensions for TTYverse, the command-line fediverse client.
## Extension Loading
TTYverse uses a simple XDG-compliant extension system. Extensions are only loaded from:
**`~/.local/share/ttyverse/extensions/`** (XDG_DATA_HOME/ttyverse/extensions)
Extensions are loaded with the `-exts` flag:
```bash
perl ttyverse.pl -exts=soundpack
perl ttyverse.pl -exts=extension1,extension2
```
## Managing Extensions
Use the included management script to enable/disable bundled extensions:
```bash
# Show available and enabled extensions
./extensions/manage-extensions.sh list
# Enable an extension (creates symlink in XDG directory)
./extensions/manage-extensions.sh enable soundpack
# Disable an extension (removes from XDG directory)
./extensions/manage-extensions.sh disable soundpack
# Check extension status
./extensions/manage-extensions.sh status soundpack
```
## Available Extensions
### soundpack.pl
Plays notification sounds for different types of posts (timeline, replies, DMs, mentions, search results).
**Configuration options** (add to `~/.config/ttyverse/ttyverse.rc`):
```
extpref_sound_command=paplay # Sound player (paplay, play, ogg123, etc.)
extpref_soundpack=default # Sound pack name
```
**Sound files location**: `~/.local/share/ttyverse/sounds/default/`
- `default.ogg` - Regular timeline posts
- `mention.ogg` - Mentions of your username
- `dm.ogg` - Direct messages
- `me.ogg` - Your own posts
- `follow.ogg` - New followers
- `boost.ogg` - Your posts boosted
- `favourite.ogg` - Your posts favourited
- `poll.ogg` - Poll notifications
- `announcement.ogg` - Server announcements
## Installing Custom Extensions
1. Copy your extension `.pl` file to `~/.local/share/ttyverse/extensions/`
2. Load it with `-exts=yourextension`
## Extension Development
Extensions are Perl modules that hook into TTYverse's event system. Key variables:
- `$data` - XDG data directory (`~/.local/share/ttyverse`)
- `$config` - XDG config directory (`~/.config/ttyverse`)
- `$store` - Extension workspace (persistent storage)
- `$silent` - Silent mode flag
- `$stdout` - Output filehandle
Extensions can define these functions:
- `$notifier` - Called for new posts
- `$heartbeat` - Called periodically
- `$handle` - Called for user commands
- `$prepost` - Called before posting
- `$postpost` - Called after posting
Set `$store->{'loaded'} = 1;` at the end of your extension.
+70
View File
@@ -0,0 +1,70 @@
# TTYverse Desktop Notifications Extension (libnotify-perl)
# Adapted from original TTYtter extension
# Provides desktop notifications using GTK Gtk2::Notify
# Published under the Floodgap Free Software License: http://www.floodgap.com/software/ffsl/
# What is it?
# This extension enables desktop notifications for TTYverse using the
# libnotify system on Linux/Unix with GUI environments.
# Requirements:
# - Gtk2::Notify Perl module
# - GUI environment with DISPLAY set
# - libnotify system installed
# Installation:
# Install dependency: cpan Gtk2::Notify
# Or on Debian/Ubuntu: sudo apt install libgtk2-notify-perl
# Or on Fedora/RHEL: sudo dnf install perl-Gtk2-Notify
# Or on Arch: sudo pacman -S perl-gtk2-notify
eval { require Gtk2::Notify };
if($@){
print "-- TTYverse notification extension requires Gtk2::Notify\n";
print "-- Install with: cpan Gtk2::Notify\n";
print "-- Or on Debian/Ubuntu: sudo apt install libgtk2-notify-perl\n";
print "-- Or on Fedora/RHEL: sudo dnf install perl-Gtk2-Notify\n";
print "-- Or on Arch: sudo pacman -S perl-gtk2-notify\n";
die();
}
sub notifier_libnotifyperl {
# Skip notifications if no GUI display available
return 1 if(!$ENV{'DISPLAY'});
my $class = shift;
my $text = shift;
my $ref = shift; # Post reference data (unused in this version)
# Handle initialization
if (!defined($class) || !defined($notify_send_path)) {
if (!defined($class)) {
return 1 if ($script); # Skip in script mode
$class = 'TTYverse Desktop Notifications';
$text = 'Desktop notifications are now active for TTYverse.';
Gtk2::Notify->init('ttyverse');
print $streamout "-- $text\n" if (!$silent);
}
}
# Show notification if system is properly initialized
if (Gtk2::Notify->is_initted()) {
my $notification = Gtk2::Notify->new(
"TTYverse: $class",
$text
);
# Set reasonable timeout (5 seconds)
$notification->set_timeout(5_000);
if($notification->show()) {
# Debug output (optional)
# print $streamout "-- Notification shown: $class\n" if ($debug);
}
}
return 1;
}
# Make sure the module loads properly
1;
+287
View File
@@ -0,0 +1,287 @@
#!/usr/bin/env bash
#
# TTYverse Extension Manager
# Simple XDG-compliant extension management for ~/.local/share/ttyverse/extensions/
#
# XDG data directory for TTYverse
DATA_DIR="${XDG_DATA_HOME:-$HOME/.local/share}/ttyverse"
EXT_DIR="$DATA_DIR/extensions"
BUNDLED_DIR="$(dirname "$0")"
# Colors for output (only if stdout is a terminal)
if [[ -t 1 ]]; then
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
NC='\033[0m'
else
GREEN=''
YELLOW=''
RED=''
NC=''
fi
# Ensure extension directory exists
mkdir -p "$EXT_DIR"
show_help() {
cat << EOF
TTYverse Extension Manager
Usage: $0 <command> [extension_name]
Commands:
list Show available and enabled extensions
enable <ext> Enable an extension (symlink to XDG extensions directory)
disable <ext> Disable an extension (remove from XDG extensions directory)
status <ext> Show status of specific extension
help Show this help
Examples:
$0 list # Show all extensions
$0 enable soundpack # Enable soundpack extension
$0 disable soundpack # Disable soundpack extension
$0 status soundpack # Check if soundpack is enabled
XDG Extensions Directory: $EXT_DIR
Bundled Extensions: $BUNDLED_DIR
Note: TTYverse only loads extensions from the XDG directory.
Use this script to enable/disable bundled extensions or install your own.
EOF
}
list_extensions() {
echo -e "${GREEN}Available Extensions:${NC}"
for ext in "$BUNDLED_DIR"/*.pl; do
if [[ -f "$ext" ]]; then
basename="${ext##*/}"
name="${basename%.pl}"
if [[ -L "$EXT_DIR/$basename" || -f "$EXT_DIR/$basename" ]]; then
echo -e " ${GREEN}${NC} $name (enabled)"
else
echo -e " ${YELLOW}${NC} $name (available)"
fi
fi
done
echo -e "\n${GREEN}User Extensions:${NC}"
for ext in "$EXT_DIR"/*.pl; do
if [[ -f "$ext" && ! -L "$ext" ]]; then
basename="${ext##*/}"
name="${basename%.pl}"
echo -e " ${GREEN}${NC} $name (user-installed)"
fi
done
}
enable_extension() {
local name="$1"
local ext_file="$name.pl"
local bundled_path="$BUNDLED_DIR/$ext_file"
local user_path="$EXT_DIR/$ext_file"
if [[ ! -f "$bundled_path" ]]; then
echo -e "${RED}Error:${NC} Extension '$name' not found in bundled extensions"
echo "Available extensions:"
for ext in "$BUNDLED_DIR"/*.pl; do
if [[ -f "$ext" ]]; then
basename="${ext##*/}"
echo " ${basename%.pl}"
fi
done
return 1
fi
if [[ -L "$user_path" ]]; then
echo -e "${YELLOW}Warning:${NC} Extension '$name' is already enabled"
return 0
elif [[ -f "$user_path" ]]; then
echo -e "${YELLOW}Warning:${NC} User-installed extension '$name' already exists"
return 0
fi
ln -s "$(realpath "$bundled_path")" "$user_path"
echo -e "${GREEN}${NC} Enabled extension: $name"
echo " Linked: $bundled_path -> $user_path"
# Special handling for soundpack extension - copy default sounds and configure RC
if [[ "$name" == "soundpack" ]]; then
local sounds_src="$BUNDLED_DIR/sounds"
local sounds_dst="$DATA_DIR/sounds"
local config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/ttyverse"
local rc_file="$config_dir/ttyverserc"
# Copy sound files
if [[ -d "$sounds_src" ]]; then
echo " Copying bundled sound files..."
cp -r "$sounds_src"/* "$sounds_dst/" 2>/dev/null || true
echo -e "${GREEN}${NC} Copied default sound pack to $sounds_dst/"
fi
# Auto-configure RC file
mkdir -p "$config_dir"
local needs_config=false
# Check if soundpack extension is already configured
# Need to check for exts, notifytype, and notifies all being properly set
local has_exts=false
local has_notifytype=false
local has_notifies=false
if [[ -f "$rc_file" ]]; then
grep -q "^exts.*soundpack" "$rc_file" 2>/dev/null && has_exts=true
grep -q "^notifytype=.*soundpack" "$rc_file" 2>/dev/null && has_notifytype=true
grep -q "^notifies=.*mention.*boost.*favourite" "$rc_file" 2>/dev/null && has_notifies=true
fi
if [[ "$has_exts" == "false" ]] || [[ "$has_notifytype" == "false" ]] || [[ "$has_notifies" == "false" ]]; then
needs_config=true
fi
if [[ "$needs_config" == "true" ]]; then
echo " Configuring TTYverse for sound notifications..."
# Create backup if RC file exists
[[ -f "$rc_file" ]] && cp "$rc_file" "$rc_file.backup.$(date +%s)"
# Update or create exts line
if [[ -f "$rc_file" ]] && grep -q "^exts=" "$rc_file"; then
# Check if soundpack is already in exts line
if ! grep -q "^exts=.*soundpack" "$rc_file"; then
sed -i 's/^exts=\(.*\)/exts=\1,soundpack/' "$rc_file"
echo " Added soundpack to existing exts line"
fi
else
# Add new exts line
echo "exts=soundpack" >> "$rc_file"
echo " Added exts=soundpack"
fi
# Add or update notifytype
if [[ -f "$rc_file" ]] && grep -q "^notifytype=" "$rc_file"; then
sed -i 's/^notifytype=.*/notifytype=soundpack/' "$rc_file"
echo " Updated notifytype to soundpack"
else
echo "notifytype=soundpack" >> "$rc_file"
echo " Added notifytype=soundpack"
fi
# Add or update notifies parameter for sound events
if [[ -f "$rc_file" ]] && grep -q "^notifies=" "$rc_file"; then
# Update existing notifies line to include fediverse sound types
sed -i 's/^notifies=.*/notifies=default,mention,dm,me,follow,boost,favourite/' "$rc_file"
echo " Updated existing notifies configuration"
else
# Add new notifies line
echo "notifies=default,mention,dm,me,follow,boost,favourite" >> "$rc_file"
echo " Added notifies configuration"
fi
# Add extension preferences
{
echo ""
echo "# Sound notifications (added by extension manager)"
echo "extpref_sound_command=paplay"
echo "extpref_soundpack=default"
echo ""
echo "# Fediverse sound types: default, mention, dm, me, follow, boost, favourite, poll, announcement"
} >> "$rc_file"
echo -e "${GREEN}${NC} Configured TTYverse RC file for sound notifications"
echo " Restart TTYverse to enable sounds automatically"
else
echo -e "${YELLOW}Note:${NC} Soundpack already configured in RC file"
fi
fi
}
disable_extension() {
local name="$1"
local ext_file="$name.pl"
local user_path="$EXT_DIR/$ext_file"
if [[ ! -e "$user_path" ]]; then
echo -e "${YELLOW}Warning:${NC} Extension '$name' is not enabled"
return 0
fi
if [[ -L "$user_path" ]]; then
rm "$user_path"
echo -e "${GREEN}${NC} Disabled extension: $name"
# Special handling for soundpack - offer to remove from RC file
if [[ "$name" == "soundpack" ]]; then
local config_dir="${XDG_CONFIG_HOME:-$HOME/.config}/ttyverse"
local rc_file="$config_dir/ttyverserc"
if [[ -f "$rc_file" ]] && grep -q "soundpack" "$rc_file"; then
echo -e "${YELLOW}Note:${NC} Soundpack is still configured in $rc_file"
echo " You may want to remove the soundpack lines to fully disable"
echo " Or run TTYverse without -exts=soundpack to override"
fi
fi
elif [[ -f "$user_path" ]]; then
echo -e "${YELLOW}Warning:${NC} '$name' is a user-installed extension (not a symlink)"
echo "Remove manually if needed: $user_path"
fi
}
show_status() {
local name="$1"
local ext_file="$name.pl"
local bundled_path="$BUNDLED_DIR/$ext_file"
local user_path="$EXT_DIR/$ext_file"
echo "Extension: $name"
echo "Bundled: $([[ -f "$bundled_path" ]] && echo "Available" || echo "Not found")"
if [[ -L "$user_path" ]]; then
echo -e "Status: ${GREEN}Enabled${NC} (symlinked)"
echo "Target: $(readlink "$user_path")"
elif [[ -f "$user_path" ]]; then
echo -e "Status: ${GREEN}Enabled${NC} (user-installed)"
else
echo -e "Status: ${YELLOW}Disabled${NC}"
fi
}
# Main command handling
case "${1:-help}" in
list|ls)
list_extensions
;;
enable)
if [[ -z "$2" ]]; then
echo -e "${RED}Error:${NC} Extension name required"
echo "Usage: $0 enable <extension_name>"
exit 1
fi
enable_extension "$2"
;;
disable)
if [[ -z "$2" ]]; then
echo -e "${RED}Error:${NC} Extension name required"
echo "Usage: $0 disable <extension_name>"
exit 1
fi
disable_extension "$2"
;;
status)
if [[ -z "$2" ]]; then
echo -e "${RED}Error:${NC} Extension name required"
echo "Usage: $0 status <extension_name>"
exit 1
fi
show_status "$2"
;;
help|--help|-h)
show_help
;;
*)
echo -e "${RED}Error:${NC} Unknown command: $1"
echo "Run '$0 help' for usage information"
exit 1
;;
esac
+76
View File
@@ -0,0 +1,76 @@
# TTYverse Sound Pack Extension
# Adapted from the original TTYtter soundpack by Storm Dragon: https://stormux.org/
# Support Storm Dragon's work: https://patreon.com/stormux
# Published under the Floodgap Free Software License: http://www.floodgap.com/software/ffsl/
# Plays sounds for different fediverse activities:
# Core: default, mention, dm, me
# Fediverse: follow, boost, favourite, poll, announcement
#
# Configure in RC file with:
# extpref_sound_command=paplay # Sound command (paplay, play, ogg123, etc.)
# extpref_soundpack=default # Sound pack name
# notifytype=soundpack # Enable sound notifications
# notifies=default,mention,dm,me,follow,boost,favourite
sub notifier_soundpack {
my $class = shift;
my $text = shift;
my $ref = shift;
# Debug output
print $stdout "-- DEBUG: soundpack notifier called with class='$class'\n" if (defined($class) && $verbose);
# Determine sound command (default to paplay for modern Linux)
my $sound_command;
if (!$extpref_sound_command) {
$sound_command = "paplay"; # Modern default for PulseAudio
} else {
$sound_command = $extpref_sound_command;
}
# Determine sound pack name
my $sound_pack;
if (!$extpref_soundpack) {
$sound_pack = "default";
} else {
$sound_pack = $extpref_soundpack;
}
# Skip if silent mode enabled
return 1 if ($silent);
if (!defined($class)) {
# Initialize - show loaded message
print $stdout "-- TTYverse sound pack loaded: $sound_pack\n";
print $stdout "-- Supported sounds: default, mention, dm, me, follow, boost, favourite, poll, announcement\n" if (!$silent);
return 1;
}
# Build sound file path using XDG data directory
my $data_dir = "$ENV{'HOME'}/.local/share/ttyverse";
my $sound_file = "$data_dir/sounds/$sound_pack/" . lc($class) . ".ogg";
if (!-f $sound_file) {
# Only warn once per session per class
my $warn_key = "warned_missing_sound_$class";
if (!$store->{$warn_key}) {
print $stdout "-- Warning: Sound file '$class.ogg' not found in pack '$sound_pack'\n";
print $stdout "-- Place sound files in $data_dir/sounds/$sound_pack/\n";
$store->{$warn_key} = 1;
}
return 1;
}
# Play the sound in background (suppress output)
print $stdout "-- DEBUG: Playing sound: $sound_command \"$sound_file\"\n" if ($verbose);
system("$sound_command \"$sound_file\" >/dev/null 2>&1 &");
return 1;
}
# Mark extension as successfully loaded
$store->{'loaded'} = 1;
# Extension loaded successfully
1;
+4
View File
@@ -0,0 +1,4 @@
*.wav filter=lfs diff=lfs merge=lfs -text
*.mp3 filter=lfs diff=lfs merge=lfs -text
*.opus filter=lfs diff=lfs merge=lfs -text
*.ogg filter=lfs diff=lfs merge=lfs -text
+119
View File
@@ -0,0 +1,119 @@
# TTYverse Sound Packs
TTYverse supports audio notifications for different types of fediverse activity. Sound packs are collections of audio files that play when specific events occur.
## Quick Start
1. **Enable sounds**: Use the extension manager to enable the soundpack extension:
```bash
./extensions/manage-extensions.sh enable soundpack
```
2. **Configure TTYverse**: Add to your `~/.config/ttyverse/ttyverserc`:
```
notifytype=soundpack
notifies=default,mention,dm,me,search,follow,boost,favourite
extpref_soundpack=default
extpref_sound_command=paplay
```
3. **Test**: Start TTYverse and you should hear sounds for different activities!
## Sound Pack Structure
Sound packs are stored in `~/.local/share/ttyverse/sounds/` with this structure:
```
sounds/
├── default/ # Default sound pack (included)
│ ├── default.ogg # Regular posts
│ ├── mention.ogg # @mentions of you
│ ├── dm.ogg # Direct messages
│ ├── me.ogg # Your own posts
│ ├── follow.ogg # New followers
│ ├── boost.ogg # Your posts boosted
│ ├── favourite.ogg # Your posts favourited
│ ├── poll.ogg # Poll notifications
│ └── announcement.ogg # Server announcements
├── custom/ # Your custom sound pack
│ └── *.ogg
└── README.md # This file
```
## Required Sound Files
### Core Sounds (Essential)
- **`default.ogg`** - Regular timeline posts
- **`mention.ogg`** - When someone @mentions you
- **`dm.ogg`** - Direct messages received
- **`me.ogg`** - Your own posts (optional feedback)
### Fediverse Interaction Sounds
- **`follow.ogg`** - Someone follows you
- **`boost.ogg`** - Someone boosts (shares) your post
- **`favourite.ogg`** - Someone favourites (likes) your post
- **`poll.ogg`** - Poll results or poll you voted in ends
- **`announcement.ogg`** - Server announcements
## Creating Custom Sound Packs
1. **Create directory**: Make a new directory under `sounds/` with your pack name:
```bash
mkdir -p ~/.local/share/ttyverse/sounds/mystyle
```
2. **Add sound files**: Place `.ogg` files with the required names. All files should be:
- **Format**: OGG Vorbis (most compatible)
- **Length**: 1-3 seconds recommended (short and sweet)
- **Volume**: Normalized to prevent startling users
- **Sample Rate**: 44.1kHz or 48kHz
3. **Configure TTYverse**: Update your config to use the new pack:
```
extpref_soundpack=mystyle
```
4. **Test**: Restart TTYverse to load the new sounds
## Audio Format Support
TTYverse uses your system's audio command for playback. Supported formats depend on your audio player:
- **paplay** (PulseAudio default): OGG, WAV, FLAC
- **play** (SoX): Most formats including MP3, OGG, WAV
- **ogg123**: OGG Vorbis only
- **mpv**: Most formats including MP3, OGG, WAV, FLAC
Configure your preferred player with:
```
extpref_sound_command=paplay # or play, ogg123, mpv, etc.
```
## Troubleshooting
### No Sound
1. Check audio command: `paplay /usr/share/sounds/alsa/Front_Left.wav`
2. Verify sound pack path: `ls ~/.local/share/ttyverse/sounds/`
3. Check TTYverse config: `grep -E "(notifytype|soundpack)" ~/.config/ttyverse/ttyverserc`
4. Test with verbose mode: TTYverse will show warnings for missing sounds
### Wrong Sounds Playing
- Verify file names exactly match the required names (case-sensitive)
- Check that you're using the correct sound pack name in config
- Restart TTYverse after changing sound files
### Performance Issues
- Use compressed formats like OGG instead of WAV
- Keep sound files under 3 seconds
- Use `&` in sound command for background playback (already handled by extension)
## Example Sound Pack Themes
### **Retro Gaming**
Use classic 8-bit style beeps and boops for different actions
### **Natural Sounds**
Bird calls, water drops, wind chimes for a calming experience
### **Minimal**
Simple tones with different pitches for each notification type
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
+60
View File
@@ -0,0 +1,60 @@
# TTYverse Timestamp Extension
# Modified by Storm Dragon: https://stormux.org/
# Support Storm Dragon's work: https://patreon.com/stormux
# Original concept from kosertech.com (see ttyverse.wiki/Extension-Development.md for development guide)
# Published under the Floodgap Free Software License: http://www.floodgap.com/software/ffsl/
# What is it?
# This extension adds timestamps before posts when 5 minutes or more have passed
# between timeline updates, helping organize the flow of your fediverse timeline.
# Usage:
# This extension works automatically. Load it when you start TTYverse or add it
# to your configuration. The timestamp format is: -- HH:MMAM/PM --
$handle = sub {
my $delayInSeconds = 60 * 5; # 5 minutes default
my ($nsec,$nmin,$nhour,$nday,$nmon,$nyear) = localtime(time);
# Initialize timestamp tracking on first run
if(!$Lib_firstrun){
$Lib_firstrun = 1;
print "-- TTYverse timestamp extension loaded\n" if (!$silent);
$Lib_past = time;
}
# Print timestamp if enough time has elapsed
if($Lib_past < (time - $delayInSeconds)){
$Lib_past = time;
my $timeString = "-- ";
my $timeOfDay = "AM";
# Convert to 12-hour format
if ($nhour >= 12) {
if ($nhour > 12) {
$nhour = $nhour - 12;
}
$timeOfDay = "PM";
}
if (($nhour < 10) && ($nhour != 0)) {
$timeString .= "0";
}
if ($nhour == 0) {
$nhour = 12;
$timeOfDay = "AM";
}
$timeString .= "$nhour:";
if ($nmin < 10) {
$timeString .= "0";
}
$timeString .= "$nmin$timeOfDay";
print $streamout "$timeString --\n" if (!$silent);
}
# Allow TTYverse to handle displaying the post normally
my $ref = shift;
&defaulthandle($ref);
return 1;
};
+153
View File
@@ -0,0 +1,153 @@
# TTYverse Text-to-Speech Extension
# By Storm Dragon: https://stormux.org/
# Support Storm Dragon's work: https://patreon.com/stormux
# Published under the Floodgap Free Software License: http://www.floodgap.com/software/ffsl/
# What is it?
# This extension uses text-to-speech to read incoming fediverse posts.
# Multiple synthesizer engines are supported for accessibility.
# Usage:
# Toggle TTS on/off: /tts
# Get help: /tts help
# Load extension when starting TTYverse or add to configuration
$addaction = sub {
my $command = shift;
my $speak = "";
# Get current TTS state from config
my $tts_config = "$ENV{'HOME'}/.config/ttyverse/tts_enabled";
if (-e $tts_config) {
open TTSSETTINGS, $tts_config;
$speak = <TTSSETTINGS>;
close TTSSETTINGS;
chomp($speak) if defined($speak);
} else {
$speak = 0;
}
if ($command eq '/tts') {
if ((!defined($speak)) || ($speak eq 1)) {
$speak = 0;
print $streamout "-- Text-to-speech disabled\n";
} else {
$speak = 1;
print $streamout "-- Text-to-speech enabled\n";
}
# Save new state
open TTSSETTINGS, ">$tts_config";
print TTSSETTINGS "$speak";
close TTSSETTINGS;
return 1;
}
if ($command eq "/tts help") {
my $ttsHelp = "TTYverse Text-to-Speech Extension\n\n" .
"Commands:\n" .
" /tts - Toggle speech on/off\n" .
" /tts help - Show this help\n\n" .
"Configuration (add to TTYverse config):\n\n" .
"extpref_tts_synthesizer=synthName\n" .
" Available: espeak (default), festival, pico, cepstral, mac\n\n" .
"extpref_tts_language=languageCode\n" .
" Default: en-US (supported by espeak and pico)\n\n" .
"extpref_tts_rate=number\n" .
" Speech rate, default: 175 (espeak only)\n\n" .
"extpref_tts_variant=name\n" .
" Voice variant/name (espeak variants, cepstral/mac voices)\n";
print $streamout "$ttsHelp";
return 1;
}
return 0;
};
$handle = sub {
my $ref = shift;
my $class = shift;
# Skip TTS during initial timeline load
if ($last_id eq 0) {
&defaulthandle($ref);
return 1;
}
my $speak = "";
# Get TTS state from config
my $tts_config = "$ENV{'HOME'}/.config/ttyverse/tts_enabled";
if (-e $tts_config) {
open TTSSETTINGS, $tts_config;
$speak = <TTSSETTINGS>;
close TTSSETTINGS;
chomp($speak) if defined($speak);
} else {
$speak = 0;
}
# Initialize TTS configuration
my $soundCommand = "paplay"; # Default for modern Linux/PulseAudio
my $synthesizer = $extpref_tts_synthesizer || "espeak";
my $language = $extpref_tts_language || "en-US";
my $variant = $extpref_tts_variant || "";
my $rate = $extpref_tts_rate || "";
# Override sound command if configured
if ($extpref_sound_command) {
$soundCommand = $extpref_sound_command;
}
# Extract post content for TTS
my $postText = &descape($ref->{'account'}->{'display_name'} || $ref->{'account'}->{'username'}) .
" posted: " . &descape($ref->{'content'});
# Make speech more accessible
$postText =~ s/\"/ /g; # Remove quotes
$postText =~ s/ \#/ hashtag /g; # Make hashtags readable
$postText =~ s/https?:\/\/\S+/ link /g; # Simplify links
$postText =~ s/www\./ /g; # Remove www
$postText =~ s/\// forward slash /g; # Make slashes readable
$postText =~ s/\\/ back slash /g; # Make backslashes readable
$postText =~ s/\blol\b/ laughs out loud /gi; # Expand common abbreviations
$postText =~ s/\brofl\b/ rolling on the floor laughing /gi;
$postText =~ s/<[^>]*>//g; # Remove any remaining HTML
my $speechCommand = "";
# Configure synthesizer command
if ($synthesizer eq "cepstral") {
$speechCommand = "aoss swift ";
if ($variant ne "") {
$speechCommand .= "-n $variant ";
}
$speechCommand .= "\"$postText\"";
} elsif ($synthesizer eq "espeak") {
if ($rate eq "") {
$rate = "175";
}
$speechCommand = "$synthesizer -v " . lc($language);
if ($variant ne "") {
$speechCommand .= "+$variant";
}
$speechCommand .= " -s $rate \"$postText\"";
} elsif ($synthesizer eq "festival") {
$speechCommand = "echo \"$postText\" | festival --tts";
} elsif ($synthesizer eq "mac") {
$speechCommand = "say ";
if ($variant ne "") {
$speechCommand .= "-v $variant ";
}
$speechCommand .= "\"$postText\"";
} elsif ($synthesizer eq "pico") {
$speechCommand = "pico2wave -l $language -w /tmp/ttyverse.wav \"$postText\" && $soundCommand /tmp/ttyverse.wav";
}
# Speak the post if TTS is enabled
if ($speak eq 1) {
system("$speechCommand");
}
# Allow TTYverse to display the post normally
&defaulthandle($ref);
return 1;
};