Compare commits

...

No commits in common. "29e1e11c63283c80aa142ac38de2d2d68830c721" and "b345b0e467446872b7d6ed203bdb11903d9d6bbb" have entirely different histories.

24 changed files with 107 additions and 255 deletions

View File

@ -1,11 +0,0 @@
# Installation
```shell
$ git clone git://git.chaotic.ninja/yakumo_izuru/mai
$ cd mai
$ make
# make PREFIX=/usr/local install
```
* Read the [mai.ini(5)](mai.ini.5) manual page
* Use any web server than is able to reverse proxy, like [Apache](https://httpd.apache.org), [h2o](https://h2o.examp1e.net), or [NGINX](https://www.nginx.com).
* Examples are provided on the repository

View File

@ -1,6 +0,0 @@
# List of known instances
| Name | Cloudflare? | Country | URL | Engines supported |
|---------------------------------------------|-------------|---------|--------------------------|-------------------|
| Chaotic Ninja Communication Network Limited | No | Germany | https://tr.chaotic.ninja | Google, Reverso |
| | | | | |

View File

@ -1,9 +0,0 @@
Mai does not host any content. All content shown on any Mai instances is from [Google Translate](https://translate.google.com), [Reverso](https://www.reverso.net/), and [LibreTranslate](https://libretranslate.com)
Mai is not affiliated with none of the above, which this program relays.
Trademarks belong to their respective owners.
Google Translate is a trademark of [Google LLC](https://www.google.com). Reverso is a trademark of Reverso, et cetera.
The creators and maintainers of this repository assume no liability for the accuracy and timeliness of any information provided above. Trademark owner information was researched to the best of the author's knowledge at the time of curation and may be outdated or incorrect.

View File

@ -1,10 +1,10 @@
GO ?= go
PREFIX ?= /usr/local PREFIX ?= /usr/local
GOFLAGS ?= -v -ldflags "-w -X `go list`.Version=${VERSION} -X `go list`.Commit=${COMMIT}" GOFLAGS ?= -v -ldflags "-w -X `${GO} list`.Version=${VERSION}"
VERSION = `git describe --abbrev=0 --tags 2>/dev/null || echo "VERSION"` VERSION ?= 2025.04.17
COMMIT = `git rev-parse --short HEAD || echo "COMMIT"`
build: build:
go build ${GOFLAGS} ./cmd/mai ${GO} build ${GOFLAGS} ./cmd/mai
clean: clean:
rm -f mai rm -f mai
install: install:

View File

@ -12,8 +12,8 @@ import (
"strings" "strings"
"syscall" "syscall"
"marisa.chaotic.ninja/mai" "mahou-no-mori.yakumo.dev/mai"
"marisa.chaotic.ninja/mai/engines" "mahou-no-mori.yakumo.dev/mai/engines"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/favicon" "github.com/gofiber/fiber/v2/middleware/favicon"
@ -24,36 +24,38 @@ import (
) )
var ( var (
configfile string configfile string
groupname string verbose bool
username string
) )
var conf struct { var conf struct {
danmaku int group string
listen string listen string
staticpath string requests int
tmplpath string static string
templates string
user string
} }
func MaiSkipLimiter(c *fiber.Ctx) bool { func MaiSkipLimiter(c *fiber.Ctx) bool {
// Paths listed here are not considered for rate limiting // Paths listed here are not considered for rate limiting
path := c.Path() path := c.Path()
return strings.HasPrefix(path, "/static") || return strings.HasPrefix(path, "/static")
strings.HasPrefix(path, "/docs")
} }
func main() { func main() {
parseFlags() parseFlags()
conf.listen = "127.0.0.1:5000"
conf.requests = 5
conf.static = "./static"
conf.templates = "./views"
if configfile != "" { if configfile != "" {
if verbose {
log.Printf("Reading configuration from %s", configfile)
}
readConf(configfile) readConf(configfile)
} }
// Default settings if conf.user != "" {
conf.danmaku = 10 uid, gid, err := usergroupids(conf.user, conf.group)
conf.listen = "127.0.0.1:5000"
conf.staticpath = "./static"
conf.tmplpath = "./views"
if username != "" {
uid, gid, err := usergroupids(username, groupname)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -61,7 +63,7 @@ func main() {
syscall.Setgid(gid) syscall.Setgid(gid)
} }
engine := html.New(conf.tmplpath, ".html") engine := html.New(conf.templates, ".html")
engine.AddFunc("inc", func(i int) int { return i + 1 }) engine.AddFunc("inc", func(i int) int { return i + 1 })
server := fiber.New( server := fiber.New(
@ -85,7 +87,7 @@ func main() {
server.Use(favicon.New( server.Use(favicon.New(
favicon.Config{ favicon.Config{
File: conf.staticpath + "/favicon.ico", File: conf.static + "/favicon.ico",
}, },
)) ))
@ -100,7 +102,7 @@ func main() {
server.Use(limiter.New(limiter.Config{ server.Use(limiter.New(limiter.Config{
Next: MaiSkipLimiter, Next: MaiSkipLimiter,
Max: conf.danmaku, Max: conf.requests,
Expiration: 30 * time.Second, Expiration: 30 * time.Second,
LimiterMiddleware: limiter.SlidingWindow{}, LimiterMiddleware: limiter.SlidingWindow{},
LimitReached: func(c *fiber.Ctx) error { LimitReached: func(c *fiber.Ctx) error {
@ -296,7 +298,7 @@ func main() {
}) })
server.Get("/toomanyrequests", func(c *fiber.Ctx) error { server.Get("/toomanyrequests", func(c *fiber.Ctx) error {
return c.SendFile(conf.tmplpath + "/429.html") return c.SendFile(conf.templates + "/429.html")
return c.SendStatus(429) return c.SendStatus(429)
}) })
@ -325,13 +327,10 @@ func main() {
} }
return c.Redirect("/") return c.Redirect("/")
}) })
server.Static("/static", conf.staticpath, fiber.Static{ server.Static("/static", conf.static, fiber.Static{
Compress: true,
ByteRange: true, ByteRange: true,
Browse: true, Browse: true,
}) })
server.Static("/docs", "./docs", fiber.Static{})
server.Listen(conf.listen) server.Listen(conf.listen)
} }

View File

@ -7,7 +7,6 @@ import (
func parseFlags() { func parseFlags() {
flag.StringVar(&configfile, "f", "", "Configuration file") flag.StringVar(&configfile, "f", "", "Configuration file")
flag.StringVar(&username, "u", "", "Sets the user to which privilege dropping is done") flag.BoolVar(&verbose, "v", false, "Be verbose")
flag.StringVar(&groupname, "g", "", "Sets the group to which privilege dropping is done")
flag.Parse() flag.Parse()
} }

View File

@ -10,11 +10,13 @@ func readConf(file string) error {
if err != nil { if err != nil {
return err return err
} }
conf.danmaku, _ = cfg.Section("mai").Key("danmaku").Int()
conf.listen = cfg.Section("mai").Key("listen").String()
conf.staticpath = cfg.Section("mai").Key("static").String()
conf.tmplpath = cfg.Section("mai").Key("templates").String()
conf.group = cfg.Section("mai").Key("group").String()
conf.listen = cfg.Section("http").Key("listen").String()
conf.requests, _ = cfg.Section("http").Key("requests").Int()
conf.static = cfg.Section("paths").Key("static").String()
conf.templates = cfg.Section("paths").Key("templates").String()
conf.user = cfg.Section("mai").Key("user").String()
return nil return nil
} }

View File

@ -1,74 +0,0 @@
<!DOCTYPE HTML PUBLIC "//W3C DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<link rel="stylesheet" href="/docs/style.css">
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<meta name="author" content="Izuru Yakumo">
<title>API Documentation | Mai</title>
</head>
<body id="body">
<h1>API documentation</h1>
<table border="1" align="center">
<tr>
<td>
<h2 class="get">[GET] /api/translate</h2>
<h2 class="post">[POST] /api/translate</h2>
<h3>Description</h3>
<p>Translation endpoint, input must be URL-encoded (e.g. multi-byte characters, words separated by space)</p>
<h3>Arguments</h3>
<ul>
<li>engine</li>
<li>from</li>
<li>text</li>
<li>to</li>
</ul>
</td>
</tr>
<tr>
<td>
<h2 class="get">[GET] /api/source_languages</h2>
<h2 class="get">[GET] /api/target_languages</h2>
<h3>Description</h3>
<p>Get a JSON array of supported source and target languages for a particular engine</p>
<h3>Arguments</h3>
<ul>
<li>engine</li>
</ul>
</td>
</tr>
<tr>
<td>
<h2 class="get">[GET] /api/tts</h2>
<h3>Description</h3>
<p>Obtain text-to-speech audio files from an engine, provided said engine supports them</p>
<h3>Arguments</h3>
<ul>
<li>engine</li>
<li>lang</li>
<li>text</li>
</ul>
</td>
</tr>
<tr>
<td>
<h2 class="get">[GET] /robots.txt</h2>
</td>
</tr>
<tr>
<td>
<h2 class="post">[POST] /switchlanguages</h2>
<h3>Description</h3>
<p>Switch between source and target languages, as long as the source language isn't "auto"</p>
<p>Must only be called inside the form interface</p>
</td>
</tr>
<tr>
<td>
<h2 class="get">[GET] /version</h2>
<h3>Description</h3>
<p>Return the software version as a JSON array</p>
</td>
</tr>
</table>
</body>
</html>

View File

@ -1,32 +0,0 @@
<!DOCTYPE HTML PUBLIC "//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<link rel="stylesheet" href="/docs/style.css">
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<meta name="author" content="Izuru Yakumo">
<title>Mai | Documentation</title>
</head>
<body>
<table>
<tr><td>
<table border="1" align="left">
<tr>
<td><a href="/docs/api">API</a></td>
</tr>
</table>
</tr></td>
<tr><td>
<table border="1" align="center">
<tr>
<td>
<p>Welcome to Mai's documentation!<p>
<p>See the menu for the available entries</p>
<p>Anything else is covered by the man pages</p>
</td>
</tr>
</table>
</td></tr>
</table>
</body>
</html>

View File

@ -1,22 +0,0 @@
body {
background-color: #f8cfd2;
color: rgb(206, 147, 191);
}
a, a:visited {
color: rgb(206, 147, 191);
}
@media screen and (prefers-color-scheme: dark) {
body {
background-color: #700000;
color: #ffffff;
}
a, a:visited {
color: #18c018;
}
}
table {
border-color: white;
}

19
example/mai.apache.conf Normal file
View File

@ -0,0 +1,19 @@
<VirtualHost *:80>
ServerName mai.example.com
ServerAlias mai.example.com
RewriteEngine on
RewriteCond %{SERVER_NAME} =mai.example.com
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,NE,R=permanent]
</VirtualHost>
<VirtualHost *:443>
ServerName mai.example.com
ServerAlias mai.example.com
SSLEngine on
SSLCertificateFile /usr/pkg/etc/letsencrypt/live/example.com/fullchain.pem
SSLCertificateKeyFile /usr/pkg/etc/letsencrypt/live/example.com/privkey.pem
ProxyRequests off
ProxyPass / http://127.0.0.1:5000/
ProxyPassReverse / http://127.0.0.1:5000/
ProxyPreserveHost On
</VirtualHost>

View File

@ -1,4 +1,19 @@
[http]
# TCP socket to listen on.
# Must not be already used by something else.
listen = 127.0.0.1:5000
# How many requests per minute are allowed
# before a rate-limit happens.
requests = 10
[mai] [mai]
listen = "127.0.0.1:5000" # Drop privilege to the user and group specified.
rootdir = "./static" # When only the user is specified, the default group of the user will
tmplpath = "./views" # be used.
#
# user = www
# group = www
[paths]
# Where to locate resources such as CSS, etc
static = ./static
# Where to locate the pages to be served
templates = ./views

View File

@ -1,7 +1,7 @@
server { server {
listen 80; listen 80;
listen [::]:80; listen [::]:80;
server_name mai.example.com; server_name mai.example.org;
location / { location / {
return 301 https://$host$request_uri; return 301 https://$host$request_uri;
@ -11,7 +11,7 @@ server {
server { server {
listen 443 ssl; listen 443 ssl;
listen [::]:443 ssl; listen [::]:443 ssl;
server_name mai.example.com; server_name mai.example.org;
ssl_certificate /path/to/fullchain.pem; ssl_certificate /path/to/fullchain.pem;
ssl_certificate_key /path/to/privkey.pem; ssl_certificate_key /path/to/privkey.pem;

2
go.mod
View File

@ -1,4 +1,4 @@
module marisa.chaotic.ninja/mai module mahou-no-mori.yakumo.dev/mai
go 1.20 go 1.20

View File

@ -1,40 +0,0 @@
.Dd $Mdocdate$
.Dt MAI.INI 5
.Os
.Sh NAME
.Nm mai.ini
.Nd Configuration file for Mai
.Sh OPTIONS
.Ss [mai] section
.Bl -tag -width 11n
.It danmaku (int)
Control the maximum amount of requests
before a ratelimit happens
.It listen (string)
HTTP port for the server to listen.
Default is "localhost:5000"
.It static (string)
Directory where the static resources are located.
Default is "./static"
.It templates (string)
Directory where the templates are located.
Default is "./views"
.El
.Sh ENVIRONMENT
.Bl -tag -width 11n
.It Ev MAI_LIBRETRANSLATE_INSTANCE
LibreTranslate instance to use, it's required for the engine to work.
.It Ev MAI_LIBRETRANSLATE_API
LibreTranslate API key for the above setting, it may or
may not be required, depending on the instance.
.El
.Sh FILES
.Pa /usr/local/etc/mai/mai.ini
.Sh SEE ALSO
.Xr mai 1
.Sh AUTHORS
.An fattalion
.An metalune
.An ManeraKai Lk https://manerakai.com
.Sh MAINTAINERS
.An Izuru Yakumo Aq Mt yakumo.izuru@chaotic.ninja

View File

@ -1,5 +1,5 @@
#!/bin/sh #!/bin/sh
# $TheSupernovaDuo$ # $YakumoLabs$
# #
# PROVIDE: mai # PROVIDE: mai
# REQUIRE: DAEMON NETWORKING syslog # REQUIRE: DAEMON NETWORKING syslog

View File

@ -1,21 +1,19 @@
#!/bin/sh #!/bin/sh
# $TheSupernovaDuo$ # $YakumoLabs$
# #
# PROVIDE: mai # PROVIDE: mai
# REQUIRE: NETWORKING DAEMON # REQUIRE: NETWORKING DAEMON
# BEFORE: LOGIN
# KEYWORD: shutdown
$_rc_subr_loaded . /etc/rc.subr $_rc_subr_loaded . /etc/rc.subr
name="mai" name="mai"
rcvar=$name rcvar=$name
command="/usr/pkg/bin/mai" command="/usr/local/bin/mai"
command_args="-f /usr/pkg/etc/mai/mai.ini -u www -g www" command_args="-f /usr/local/etc/mai/mai.ini"
pidfile="/var/run/${name}.pid" pidfile="/var/run/${name}.pid"
start_cmd="mai_start" start_cmd="mai_start"
required_files="/usr/pkg/etc/mai/mai.ini" required_files="/usr/local/etc/mai/mai.ini"
mai_start() { mai_start() {
$command $command_args $command $command_args

View File

@ -1,8 +1,8 @@
#!/bin/ksh #!/bin/ksh
# $TheSupernovaDuo$ # $YakumoLabs$
daemon="/usr/local/bin/mai" daemon="/usr/local/bin/mai"
daemon_flags="-f /usr/local/etc/mai/mai.ini -u www -g www" daemon_flags="-f /usr/local/etc/mai/mai.ini"
. /etc/rc.d/rc.subr . /etc/rc.d/rc.subr

View File

@ -1,2 +1,3 @@
cmd: /usr/local/bin/mai -f /usr/local/etc/mai/mai.ini -g www -u www # $YakumoLabs$
cmd: /usr/local/bin/mai -f /usr/local/etc/mai/mai.ini
cwd: /usr/local/share/mai cwd: /usr/local/share/mai

13
rc.d/mai.service Normal file
View File

@ -0,0 +1,13 @@
# $YakumoLabs$
[Unit]
Description=Mai
Documentation=https://suzunaan.yakumo.dev/mai
[Service]
Type=simple
Restart=always
RestartSec=5
ExecStart=/usr/local/bin/mai -f /usr/local/etc/mai/mai.ini
[Install]
WantedBy=multi-user.target

4
rc.d/mokou.conf Normal file
View File

@ -0,0 +1,4 @@
# $YakumoLabs$
description=Mai
exec=/usr/bin/env daemonize -p /var/run/mai.pid /usr/local/bin/mai -f /usr/local/etc/mai.ini
pidfile=/var/run/mai.pid

4
rc.d/s6 Normal file
View File

@ -0,0 +1,4 @@
#!/command/execlineb -P
# $YakumoLabs$
/usr/local/bin/mai
-f /usr/local/etc/mai/mai.ini

View File

@ -7,12 +7,9 @@ import (
var ( var (
// Version release version // Version release version
Version = "0.0.1" Version = "0.0.1"
// Commit will be overwritten automatically by the build system
Commit = "HEAD"
) )
// FullVersion display the full version and build // FullVersion display the full version and build
func FullVersion() string { func FullVersion() string {
return fmt.Sprintf("%s@%s", Version, Commit) return fmt.Sprintf("%s", Version)
} }

View File

@ -126,12 +126,7 @@
</form> </form>
<br><br><br><br><br> <br><br><br><br><br>
<footer class="center"> <footer class="center">
<p> <a href="https://suzunaan.yakumo.dev/mai">Hosted on Suzunaan Software Library</a>
<a href="https://git.chaotic.ninja/yakumo.izuru/mai">Source code</a>
</p>
<p>
A <a href="https://mirage.h0stname.net">Mirage AIB</a> project
</p>
</footer> </footer>
<script src="/static/script.js"></script> <script src="/static/script.js"></script>
</body> </body>