Skip to content

Commit

Permalink
feat(translator): add morse code and binary translation
Browse files Browse the repository at this point in the history
  • Loading branch information
jaxron committed Nov 8, 2024
1 parent b01eaf7 commit e90795a
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 18 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@
| <p align="center"><img src="assets/images/01.gif" width="450"></p><p align="center">Review flagged accounts quickly with AI assistance. This system provides profile overviews and AI-generated violation reasons, allowing moderators to make informed decisions in seconds.</p> | <p align="center"><img src="assets/images/02.gif" width="450"></p><p align="center">The review menu also allows in-depth user investigation. Moderators can easily explore a user's outfits, friends, and groups, providing a detailed view for thorough and informed decision-making.</p> |
| Streamer Mode | User Log Browser |
| <p align="center"><img src="assets/images/03.gif" width="450"></p><p align="center">Streamer mode provides additional privacy by censoring sensitive user information in the review menu. This feature is particularly useful for content creators and streamers who want to showcase the tool while maintaining privacy.</p> | <p align="center"><img src="assets/images/04.gif" width="450"></p><p align="center">The log browser enables detailed querying of moderation actions. Administrators can search through logs based on specific users, actions, or date ranges, providing detailed audit trails. No more sabotaging!</p> |
| Automatic Translation Support | Session State Preservation |
| <p align="center"><img src="assets/images/05.gif" width="450"></p><p align="center">The review menu features automatic translation capabilities, eliminating language barriers during moderation. This ensures effective review of content across different languages.</p> | <p align="center"><img src="assets/images/06.gif" width="450"></p><p align="center">Review sessions are preserved across channels and servers, allowing moderators to seamlessly resume their work from where they left off.</p> |
| Multi-format Translation | Session State Preservation |
| <p align="center"><img src="assets/images/05.gif" width="450"></p><p align="center">The review menu features comprehensive translation capabilities, supporting natural languages, morse code, and binary. This ensures effective review of content across different languages and encodings.</p> | <p align="center"><img src="assets/images/06.gif" width="450"></p><p align="center">Review sessions are preserved across channels and servers, allowing moderators to seamlessly resume their work from where they left off.</p> |
| Live Statistics Dashboard | Priority Queue System |
| <p align="center"><img src="assets/images/07.gif" width="450"></p><p align="center">The dashboard displays live hourly statistics showing active reviewers and various statistics for real-time performance tracking.</p> | <p align="center"><img src="assets/images/08.gif" width="450"></p><p align="center">Users can be added to priority queues for immediate processing. Workers automatically process these queues to check for potential violations using AI analysis.</p> |
| Real-time Worker Progress Monitoring | |
Expand All @@ -72,7 +72,7 @@ Rotector offers key features for efficient moderation:
- Efficient review and action workflow
- Overview of outfits, friends, groups
- Streamer mode for content creators
- Automatic translation support
- Translation support (natural languages, morse code, binary)
- Session state preservation

- **Worker System**
Expand Down
Binary file modified assets/images/05.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion internal/bot/handlers/review/builders/review_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func (b *ReviewEmbed) getDescription() string {
// Translate the description
translatedDescription, err := b.translator.Translate(context.Background(), description, "auto", "en")
if err == nil && translatedDescription != description {
description = fmt.Sprintf("%s\n%s", description, translatedDescription)
return "(translated)\n" + translatedDescription
}

return description
Expand Down
221 changes: 207 additions & 14 deletions internal/common/translator/translator.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,49 @@ package translator

import (
"context"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"strings"

"github.com/bytedance/sonic"
"github.com/jaxron/axonet/pkg/client"
)

// Translator handles text translation between languages by making requests
// to the Google Translate API through a HTTP client.
// ErrInvalidBinary is returned when the binary input is malformed or incomplete.
var ErrInvalidBinary = errors.New("invalid binary string")

// Translator handles text translation between different formats.
type Translator struct {
client *client.Client
client *client.Client
morseToText map[string]string
}

// New creates a Translator with the provided HTTP client for making
// translation requests.
// New creates a Translator with the provided HTTP client.
func New(client *client.Client) *Translator {
return &Translator{client: client}
return &Translator{
client: client,
// Standard International Morse Code mapping including letters, numbers, and punctuation
morseToText: map[string]string{
".-": "A", "-...": "B", "-.-.": "C", "-..": "D", ".": "E",
"..-.": "F", "--.": "G", "....": "H", "..": "I", ".---": "J",
"-.-": "K", ".-..": "L", "--": "M", "-.": "N", "---": "O",
".--.": "P", "--.-": "Q", ".-.": "R", "...": "S", "-": "T",
"..-": "U", "...-": "V", ".--": "W", "-..-": "X", "-.--": "Y",
"--..": "Z", ".----": "1", "..---": "2", "...--": "3", "....-": "4",
".....": "5", "-....": "6", "--...": "7", "---..": "8", "----.": "9",
"-----": "0", "..--..": "?", "-.-.--": "!", ".-.-.-": ".",
"--..--": ",", "---...": ":", ".----.": "'", ".-..-.": "\"",
},
}
}

// Translate sends text to Google Translate API for translation.
// The response is parsed to extract just the translated text.
func (t *Translator) Translate(ctx context.Context, text, sourceLang, targetLang string) (string, error) {
// TranslateLanguage translates text between natural languages using Google Translate API.
// sourceLang and targetLang should be ISO 639-1 language codes (e.g., "en" for English).
// Returns the translated text and any error encountered during translation.
func (t *Translator) TranslateLanguage(ctx context.Context, text, sourceLang, targetLang string) (string, error) {
// Send request to Google Translate API
resp, err := t.client.NewRequest().
Method(http.MethodGet).
Expand All @@ -40,24 +60,197 @@ func (t *Translator) Translate(ctx context.Context, text, sourceLang, targetLang
}
defer resp.Body.Close()

// Read the response body
// Read and parse the response
body, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}

// Unmarshal the response body
var result []interface{}
err = sonic.Unmarshal(body, &result)
if err != nil {
if err := sonic.Unmarshal(body, &result); err != nil {
return "", err
}

// Build the translated text from response segments
// Extract translated text from the response
var translatedText strings.Builder
for _, slice := range result[0].([]interface{}) {
translatedText.WriteString(slice.([]interface{})[0].(string))
}

return translatedText.String(), nil
}

// TranslateMorse converts Morse code to text.
func (t *Translator) TranslateMorse(morse string) string {
var result strings.Builder

// Split into words by forward slash
words := strings.Split(morse, "/")

for i, word := range words {
if i > 0 {
result.WriteString(" ")
}

// Process each letter in the word
word = strings.TrimSpace(word)
letters := strings.Split(word, " ")

for _, letter := range letters {
letter = strings.TrimSpace(letter)
if letter == "" {
continue
}
if text, ok := t.morseToText[letter]; ok {
result.WriteString(text)
}
}
}

return result.String()
}

// TranslateBinary converts binary strings to text.
// Input should be space-separated 8-bit binary sequences.
// Each sequence is converted to its ASCII character representation.
func (t *Translator) TranslateBinary(binary string) (string, error) {
// Remove all spaces
binary = strings.ReplaceAll(binary, " ", "")

var result strings.Builder
// Process 8 bits at a time
for i := 0; i < len(binary); i += 8 {
if i+8 > len(binary) {
return "", fmt.Errorf("%w: incomplete byte", ErrInvalidBinary)
}

// Convert binary byte to character
num, err := strconv.ParseUint(binary[i:i+8], 2, 8)
if err != nil {
return "", err
}

result.WriteRune(rune(num))
}

return result.String(), nil
}

// Translate automatically detects and translates mixed content in the input string.
// It first attempts to translate any morse code or binary segments, then performs
// a single language translation on the entire resulting text if languages are specified.
func (t *Translator) Translate(ctx context.Context, input, sourceLang, targetLang string) (string, error) {
// Split input into lines
lines := strings.Split(strings.TrimSpace(input), "\n")
var result strings.Builder

// First pass: translate morse and binary segments
for i, line := range lines {
if i > 0 {
result.WriteString("\n")
}

// Split line into segments based on format boundaries
segments := t.splitIntoSegments(line)

// Translate each segment
for j, segment := range segments {
if j > 0 {
result.WriteString(" ")
}

// Translate morse and binary segments
segment = strings.TrimSpace(segment)
if segment == "" {
continue
}

if isMorseFormat(segment) {
result.WriteString(t.TranslateMorse(segment))
continue
}

if isBinaryFormat(segment) {
if translated, err := t.TranslateBinary(segment); err == nil {
result.WriteString(translated)
continue
}
}

result.WriteString(segment)
}
}

// Second pass: translate the entire text if language translation is requested
if sourceLang != "" && targetLang != "" {
translated, err := t.TranslateLanguage(ctx, result.String(), sourceLang, targetLang)
if err != nil {
return "", err
}
return translated, nil
}

return result.String(), nil
}

// splitIntoSegments splits a line into segments based on format boundaries.
// Spaces between segments are preserved in the output.
func (t *Translator) splitIntoSegments(line string) []string {
var segments []string
var currentSegment strings.Builder
words := strings.Fields(line)

for i, word := range words {
// Compare current and previous word formats directly
if i > 0 {
prevIsMorse := isMorseFormat(words[i-1])
prevIsBinary := isBinaryFormat(words[i-1])
currIsMorse := isMorseFormat(word)
currIsBinary := isBinaryFormat(word)

if (prevIsMorse != currIsMorse || prevIsBinary != currIsBinary) && currentSegment.Len() > 0 {
segments = append(segments, strings.TrimSpace(currentSegment.String()))
currentSegment.Reset()
}
}

if currentSegment.Len() > 0 {
currentSegment.WriteString(" ")
}
currentSegment.WriteString(word)
}

if currentSegment.Len() > 0 {
segments = append(segments, strings.TrimSpace(currentSegment.String()))
}

return segments
}

// isMorseFormat checks if text appears to be in Morse code format.
func isMorseFormat(text string) bool {
// Check if text contains only valid morse characters
cleaned := strings.Map(func(r rune) rune {
switch r {
case '.', '-', '/', ' ':
return -1
default:
return r
}
}, text)

return cleaned == "" && (strings.Contains(text, ".") || strings.Contains(text, "-"))
}

// isBinaryFormat checks if text appears to be in binary format.
func isBinaryFormat(text string) bool {
// Remove spaces and check for valid binary string
cleaned := strings.ReplaceAll(text, " ", "")
if len(cleaned)%8 != 0 {
return false
}

return strings.IndexFunc(cleaned, func(r rune) bool {
return r != '0' && r != '1'
}) == -1
}

0 comments on commit e90795a

Please sign in to comment.