Initial Commit

This commit is contained in:
Salastil
2025-11-18 02:07:08 -05:00
parent b3ebf32a75
commit 1c5418edf6
12 changed files with 2379 additions and 0 deletions

112
utils/bbcode.go Normal file
View File

@@ -0,0 +1,112 @@
package utils
import (
"regexp"
"strings"
)
// BBCodeToMarkdown converts simplified Siropu-style BBCode into Matrix-safe Markdown.
//
// RULES:
// - [img]URL[/img] remains an image but without video tagging
// - [url] and [url=...] become markdown links
// - All [video]...[/video] wrappers are REMOVED entirely (your requirement)
// - Bold/italic/underline basic BBCode converted to markdown
// - Unknown tags stripped and inner text preserved
//
func BBCodeToMarkdown(in string) string {
if in == "" {
return ""
}
s := in
// ----------------------------------------------------
// STRIP VIDEO TAGS COMPLETELY
// ----------------------------------------------------
s = stripTagCompletely(s, "video")
// ----------------------------------------------------
// IMAGE TAGS → leave as-is, but sanitize formatting
// ----------------------------------------------------
s = regexp.MustCompile(`(?i)\[img\](.*?)\[/img\]`).ReplaceAllString(s, "![]($1)")
// ----------------------------------------------------
// URL TAGS → Markdown links
// ----------------------------------------------------
// [url]http://x[/url]
s = regexp.MustCompile(`(?i)\[url\](.*?)\[/url\]`).ReplaceAllString(s, "[$1]($1)")
// [url=http://x]label[/url]
s = regexp.MustCompile(`(?i)\[url=(.*?)\](.*?)\[/url\]`).ReplaceAllString(s, "[$2]($1)")
// ----------------------------------------------------
// BASIC FORMATTING → Markdown
// ----------------------------------------------------
replacements := map[*regexp.Regexp]string{
regexp.MustCompile(`(?i)\[b\](.*?)\[/b\]`): "**$1**",
regexp.MustCompile(`(?i)\[i\](.*?)\[/i\]`): "*$1*",
regexp.MustCompile(`(?i)\[u\](.*?)\[/u\]`): "__$1__",
regexp.MustCompile(`(?i)\[s\](.*?)\[/s\]`): "~~$1~~",
regexp.MustCompile(`(?i)\[quote\](.*?)\[/quote\]`): "> $1",
}
for re, repl := range replacements {
s = re.ReplaceAllString(s, repl)
}
// ----------------------------------------------------
// REMOVE ANY OTHER BBCODE TAGS, KEEP CONTENT
// ----------------------------------------------------
s = regexp.MustCompile(`(?i)\[(\/?)[a-zA-Z0-9\=\#]+?\]`).ReplaceAllString(s, "")
// Cleanup whitespace
return strings.TrimSpace(s)
}
// stripTagCompletely removes [tag]...[/tag] entirely, preserving inner text only if desired.
// Here we drop everything inside video tags.
func stripTagCompletely(s, tag string) string {
re := regexp.MustCompile(`(?is)\[` + tag + `(?:=[^\]]*)?\].*?\[\/` + tag + `\]`)
return re.ReplaceAllString(s, "")
}
// IsImageURL determines whether a string looks like an image link.
// Used by both Matrix → Sneed and Sneed → Matrix paths.
func IsImageURL(u string) bool {
u = strings.ToLower(strings.TrimSpace(u))
if !(strings.HasPrefix(u, "http://") || strings.HasPrefix(u, "https://")) {
return false
}
// strip query
if i := strings.Index(u, "?"); i > 0 {
u = u[:i]
}
return strings.HasSuffix(u, ".png") ||
strings.HasSuffix(u, ".jpg") ||
strings.HasSuffix(u, ".jpeg") ||
strings.HasSuffix(u, ".gif") ||
strings.HasSuffix(u, ".webp")
}
// WrapImageForSneed produces the BBCode wrapper used for outbound
// Matrix → Sneed image messages.
//
// Example:
// input: "https://example.com/img.jpg"
// output: "[url=https://example.com/img.jpg][img]https://example.com/img.jpg[/img][/url]"
//
func WrapImageForSneed(url string) string {
if url == "" {
return ""
}
return "[url=" + url + "][img]" + url + "[/img][/url]"
}
// ExtractFirstURL finds the first URL-like token in a message.
// Useful for deciding if a message is an image-only post.
func ExtractFirstURL(s string) string {
re := regexp.MustCompile(`https?://[^\s]+`)
found := re.FindString(s)
return found
}

150
utils/boundedmap.go Normal file
View File

@@ -0,0 +1,150 @@
package utils
import (
"sync"
"time"
)
// BoundedMap is a size-limited and time-limited map.
// Entries automatically expire after a TTL, and older
// entries are removed when exceeding MaxSize.
type BoundedMap struct {
mu sync.Mutex
entries map[interface{}]entry
MaxSize int
TTL time.Duration
}
type entry struct {
Value interface{}
Created time.Time
}
// NewBoundedMap creates a new bounded map with max size and TTL.
func NewBoundedMap(maxSize int, ttl time.Duration) *BoundedMap {
return &BoundedMap{
entries: make(map[interface{}]entry),
MaxSize: maxSize,
TTL: ttl,
}
}
// Set stores a key/value pair, replacing old entry if needed.
func (b *BoundedMap) Set(key, value interface{}) {
b.mu.Lock()
defer b.mu.Unlock()
b.entries[key] = entry{
Value: value,
Created: time.Now(),
}
// If map is growing too large, evict oldest items.
if len(b.entries) > b.MaxSize {
b.evictOldest()
}
}
// Get retrieves a value if it exists and is not expired.
func (b *BoundedMap) Get(key interface{}) (interface{}, bool) {
b.mu.Lock()
defer b.mu.Unlock()
e, ok := b.entries[key]
if !ok {
return nil, false
}
if time.Since(e.Created) > b.TTL {
delete(b.entries, key)
return nil, false
}
return e.Value, true
}
// Delete removes an entry.
func (b *BoundedMap) Delete(key interface{}) {
b.mu.Lock()
defer b.mu.Unlock()
delete(b.entries, key)
}
// CleanupOldEntries removes expired entries and returns number removed.
func (b *BoundedMap) CleanupOldEntries() int {
b.mu.Lock()
defer b.mu.Unlock()
count := 0
now := time.Now()
for k, e := range b.entries {
if now.Sub(e.Created) > b.TTL {
delete(b.entries, k)
count++
}
}
return count
}
// evictOldest removes the single oldest entry from the map.
// Called automatically when the map exceeds MaxSize.
func (b *BoundedMap) evictOldest() {
if len(b.entries) == 0 {
return
}
var (
oldestKey interface{}
oldestTS time.Time
first = true
)
for k, e := range b.entries {
if first {
oldestKey = k
oldestTS = e.Created
first = false
continue
}
if e.Created.Before(oldestTS) {
oldestKey = k
oldestTS = e.Created
}
}
delete(b.entries, oldestKey)
}
// Size returns the current number of live entries.
// Note: expired entries are not removed until Get() or CleanupOldEntries().
func (b *BoundedMap) Size() int {
b.mu.Lock()
defer b.mu.Unlock()
return len(b.entries)
}
// Keys returns a slice of all keys (including expired ones).
// Expired keys will be filtered during normal Get/Cleanup operations.
func (b *BoundedMap) Keys() []interface{} {
b.mu.Lock()
defer b.mu.Unlock()
out := make([]interface{}, 0, len(b.entries))
for k := range b.entries {
out = append(out, k)
}
return out
}
// Values returns all values in the map (including expired ones).
func (b *BoundedMap) Values() []interface{} {
b.mu.Lock()
defer b.mu.Unlock()
out := make([]interface{}, 0, len(b.entries))
for _, e := range b.entries {
out = append(out, e.Value)
}
return out
}

143
utils/helpers.go Normal file
View File

@@ -0,0 +1,143 @@
package utils
import (
"encoding/json"
"regexp"
"strconv"
"strings"
"time"
)
// ---------------------------------------------------------
// STRING HELPERS
// ---------------------------------------------------------
// Truncate returns the first N runes of s, safely.
func Truncate(s string, n int) string {
rs := []rune(s)
if len(rs) <= n {
return s
}
return string(rs[:n])
}
// NormalizeUsername lowers and strips unsafe characters.
// Used by the Matrix ghost-user generator and Sneed mapping.
func NormalizeUsername(s string) string {
s = strings.ToLower(strings.TrimSpace(s))
s = strings.ReplaceAll(s, " ", "_")
s = strings.ReplaceAll(s, "@", "")
s = strings.ReplaceAll(s, ":", "")
s = strings.ReplaceAll(s, "#", "")
s = strings.ReplaceAll(s, "/", "")
s = strings.ReplaceAll(s, "\\", "")
return s
}
// CleanSpaces reduces all whitespace clusters to a single space.
func CleanSpaces(s string) string {
space := regexp.MustCompile(`\s+`)
return space.ReplaceAllString(strings.TrimSpace(s), " ")
}
// StripControlChars removes non-printable or weird control characters.
func StripControlChars(s string) string {
re := regexp.MustCompile(`[\x00-\x1F\x7F]`)
return re.ReplaceAllString(s, "")
}
// ---------------------------------------------------------
// URL HELPERS
// ---------------------------------------------------------
// NormalizeURL trims and strips unused trailing punctuation.
func NormalizeURL(u string) string {
u = strings.TrimSpace(u)
if strings.HasSuffix(u, ")") || strings.HasSuffix(u, "]") {
u = u[:len(u)-1]
}
return u
}
// IsLikelyURL checks for a simple URL pattern.
func IsLikelyURL(s string) bool {
s = strings.TrimSpace(s)
return strings.HasPrefix(s, "http://") || strings.HasPrefix(s, "https://")
}
// ---------------------------------------------------------
// NUMERIC CONVERSIONS
// ---------------------------------------------------------
// ToInt attempts to convert any JSON-type number to int.
func ToInt(v interface{}) (int, bool) {
switch t := v.(type) {
case int:
return t, true
case int64:
return int(t), true
case float64:
return int(t), true
case string:
i, err := strconv.Atoi(t)
if err == nil {
return i, true
}
}
return 0, false
}
// ---------------------------------------------------------
// JSON SERIALIZATION
// ---------------------------------------------------------
// MustJSON marshals v or returns a placeholder string.
func MustJSON(v interface{}) string {
b, err := json.Marshal(v)
if err != nil {
return "<json error>"
}
return string(b)
}
// PrettyJSON pretty prints JSON map / slice items.
func PrettyJSON(v interface{}) string {
b, err := json.MarshalIndent(v, "", " ")
if err != nil {
return "<json error>"
}
return string(b)
}
// ---------------------------------------------------------
// TIMESTAMP HELPERS
// ---------------------------------------------------------
// NowMS returns the current Unix time in milliseconds.
func NowMS() int64 {
return time.Now().UnixNano() / int64(time.Millisecond)
}
// IsFresher compares two timestamps (ms).
// Returns true if tNew is strictly newer than tOld.
func IsFresher(tNew, tOld int64) bool {
return tNew > tOld
}
// Age returns the duration since a timestamp in ms.
func Age(ts int64) time.Duration {
return time.Since(time.UnixMilli(ts))
}
// ---------------------------------------------------------
// MESSAGE ID HELPERS
// ---------------------------------------------------------
// IsValidMessageID checks that a Sneedchat message_id is safe.
func IsValidMessageID(id int) bool {
return id > 0 && id < 1_000_000_000
}
// IsValidSyntheticID verifies that bridge synthetic IDs are nonzero.
func IsValidSyntheticID(id int) bool {
return id > 0
}