Compare commits

...

7 Commits
main ... main

Author SHA1 Message Date
srgooglo dd85e90250 added console log 2025-02-22 06:28:57 +01:00
srgooglo b9b14fa785 fix node_env 2025-02-22 06:24:04 +01:00
srgooglo bc78c82616 use node_env 2025-02-22 06:21:31 +01:00
srgooglo 39165b3ca3 log 2025-02-22 06:11:15 +01:00
srgooglo 0e61f78dbb support tts 2025-02-22 06:08:58 +01:00
srgooglo 0777666877 Actualizar README.md 2025-02-22 04:05:45 +00:00
srgooglo 0d229cd63b Merge pull request 'update monstercanker' (#4) from BlackCherryCat/monstercanker:main into main
Reviewed-on: srgooglo/monstercanker#4
2024-02-26 15:40:26 +00:00
6 changed files with 273 additions and 119 deletions

View File

@ -18,4 +18,6 @@ RUN cd /home/node/app/node_api/api && npm install -D --force
RUN cd /home/node/app/node_api/web && npm install -D --force
RUN cd /home/node/app/node_api/web && npm run build
CMD ["node", "/home/node/app/node_api/api/index.js"]
# set to production
ENV NODE_ENV=production
CMD ["node", "/home/node/app/node_api/api/index.js"]

View File

@ -1,8 +1,8 @@
# [monstercanker](https://monstercanker.mcommunity.fun/)
# [monstercanker](https://msk.ragestudio.net/)
Hechate un gapo la irgen
## API Usage
```https://monstercanker.mcommunity.fun/api?random=<number|bool>```
```https://msk.ragestudio.net/api?random=<number|bool>```

View File

@ -1,48 +1,157 @@
require('dotenv').config()
require("dotenv").config()
const path = require("node:path")
const fs = require("node:fs")
const axios = require("axios")
const { aws4Interceptor } = require("aws4-axios")
const express = require("express")
const path = require("path")
const cors = require("cors")
const mlib = require("../../node_lib")
const getPhrases = require("../../node_lib")
let app = null
const { LISTENING_PORT } = process.env
const PORT = LISTENING_PORT || 3000
async function main() {
app = express()
const cachePath = path.join(process.cwd(), ".cache")
const audiosPath = path.join(cachePath, "audio")
app.use(cors())
app.use(express.json())
const PollyBaseURL = "https://polly.us-east-1.amazonaws.com"
app.get("/api", async (req, res) => {
let { random } = req.query
// try to parse random, can be a number or a boolean
if (random) {
if (random === "true") {
random = true
} else if (Number(random)) {
random = Number(random)
}
}
const phrases = await mlib({ random })
res.json(phrases)
})
app.use(express.static(path.join(__dirname, "..", "web", "dist",)))
// serve static react build
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "..", "web", "dist", "index.html"))
})
app.listen(PORT)
console.log(`Listening on port ${PORT}`)
const PollyDefaultConfig = {
Engine: "generative",
VoiceId: "Lucia",
OutputFormat: "mp3",
LanguageCode: "es-ES",
}
main().catch(console.error)
const interceptor = aws4Interceptor({
options: {
region: "us-east-1",
service: "polly",
},
credentials: {
accessKeyId: process.env.AWS_API_KEY,
secretAccessKey: process.env.AWS_API_SECRET,
},
})
axios.interceptors.request.use(interceptor)
async function synthesizePollyVoice(phrase, phraseId) {
if (!fs.existsSync(audiosPath)) {
fs.mkdirSync(audiosPath, { recursive: true })
}
// call to api and stream result to a file
const voiceResultPath = path.resolve(audiosPath, phraseId + ".mp3")
if (fs.existsSync(voiceResultPath)) {
fs.unlinkSync(voiceResultPath)
}
const voiceResultFile = fs.createWriteStream(voiceResultPath)
console.log(`Catching TTS file for id [${phraseId}]`)
const { data: stream } = await axios({
url: `${PollyBaseURL}/v1/speech`,
method: "POST",
data: {
...PollyDefaultConfig,
Text: phrase,
},
responseType: "stream",
})
stream.pipe(voiceResultFile)
return new Promise((resolve, reject) => {
stream.on("end", () => resolve(voiceResultPath))
stream.on("error", (error) => reject(error))
})
}
async function fetchTTSAudioURL(req, phrase, phraseId) {
const filePath = path.join(audiosPath, `${phraseId}.mp3`)
if (!fs.existsSync(filePath)) {
await synthesizePollyVoice(phrase, phraseId)
}
return `${process.env.NODE_ENV === "production" ? "https" : req.protocol}://${req.get("host")}${req.path}/audio/${phraseId}.mp3`
}
async function handleApiRequest(req, res) {
let { random } = req.query
// try to parse random, can be a number or a boolean
if (random) {
if (random === "true") {
random = true
} else if (Number(random)) {
random = Number(random)
}
}
const result = await getPhrases({ random })
if (random) {
const phraseId = result
.trim()
.toLowerCase()
.replace(/\s+/g, "_")
.replace(/[^\w\s]/gi, "")
return res.json({
id: phraseId,
phrase: result.trim(),
tts_file: await fetchTTSAudioURL(req, result, phraseId),
})
}
return res.json(result)
}
const useLogger = (req, res, next) => {
const startHrTime = process.hrtime()
res.on("finish", () => {
let url = req.url
const elapsedHrTime = process.hrtime(startHrTime)
const elapsedTimeInMs = elapsedHrTime[0] * 1000 + elapsedHrTime[1] / 1e6
res._responseTimeMs = elapsedTimeInMs
// cut req.url if is too long
if (url.length > 100) {
url = url.substring(0, 100) + "..."
}
console.log(
`${req.method} ${res._status_code ?? res.statusCode ?? 200} ${url} ${elapsedTimeInMs}ms`,
)
})
next()
}
async function main() {
app = express()
app.use(cors())
app.use(express.json())
app.use(useLogger)
app.get("/api", handleApiRequest)
app.use("/api/audio", express.static(audiosPath))
app.use(express.static(path.join(__dirname, "..", "web", "dist")))
app.listen(PORT)
console.log(`Listening on port ${PORT}`)
}
main().catch(console.error)

View File

@ -4,6 +4,8 @@
"main": "index.js",
"license": "MIT",
"dependencies": {
"aws4-axios": "^3.3.15",
"axios": "^1.7.9",
"cors": "^2.8.5",
"dotenv": "^16.4.1",
"express": "^4.18.2"

View File

@ -1,86 +1,99 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme : light dark;
color : rgba(255, 255, 255, 0.87);
background-color: #242424;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis : none;
text-rendering : optimizeLegibility;
-webkit-font-smoothing : antialiased;
-moz-osx-font-smoothing: grayscale;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
a {
font-weight : 500;
color : #646cff;
text-decoration: inherit;
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
color: #535bf2;
}
body {
display : flex;
flex-direction: column;
display: flex;
flex-direction: column;
margin : 0;
padding: 0;
margin: 0;
padding: 0;
}
h1 {
font-size : 3.2em;
line-height: 1.1;
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius : 8px;
border : 1px solid transparent;
padding : 0.6em 1.2em;
font-size : 1em;
font-weight : 500;
font-family : inherit;
background-color: #1a1a1a;
cursor : pointer;
transition : border-color 0.25s;
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
outline: 4px auto -webkit-focus-ring-color;
}
.app {
display : flex;
flex-direction: column;
display: flex;
flex-direction: column;
align-items : center;
justify-content: center;
align-items: center;
justify-content: center;
width : 100%;
height: 100vh;
width: 100%;
height: 100vh;
}
.result {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
.playback_audio {
font-size: 24px;
cursor: pointer;
}
}
footer {
position: fixed;
position: fixed;
bottom: 10vh;
left : 0;
bottom: 10vh;
left: 0;
width : 100%;
height: fit-content;
width: 100%;
height: fit-content;
display: flex;
display: flex;
align-items : center;
justify-content: center;
align-items: center;
justify-content: center;
gap: 20px;
}
gap: 20px;
}

View File

@ -5,48 +5,76 @@ import axios from "axios"
import "./index.css"
const API_ENDPOINT = import.meta.env.PROD ? "/api" : `http://${window.location.hostname}:3000/api`
const API_ENDPOINT = import.meta.env.PROD
? "/api"
: `http://${window.location.hostname}:3000/api`
const App = () => {
const [loading, setLoading] = React.useState(true)
const [randomWord, setRandomWord] = React.useState(null)
const [loading, setLoading] = React.useState(true)
const [randomWord, setRandomWord] = React.useState(null)
async function loadRandom({
random = true,
} = {}) {
setLoading(true)
async function loadRandom({ random = true } = {}) {
setLoading(true)
let { data } = await axios({
url: API_ENDPOINT,
method: "GET",
params: {
random,
},
})
let { data } = await axios({
url: API_ENDPOINT,
method: "GET",
params: {
random,
},
})
setLoading(false)
setLoading(false)
setRandomWord(data)
}
setRandomWord(data)
}
React.useEffect(() => {
loadRandom()
}, [])
async function playbackCurrentWord() {
if (!randomWord || !randomWord.tts_file) return
return <div className="app">
{
loading ? <p>Loading...</p> : <h1>{randomWord}</h1>
}
const audio = new Audio()
<footer>
<a href="https://git.ragestudio.net/srgooglo/monstercanker">GitHub</a>
<a href={API_ENDPOINT}>API</a>
</footer>
</div>
audio.src = randomWord.tts_file
audio.volume = 0.5
audio.play()
}
React.useEffect(() => {
loadRandom()
}, [])
return (
<div className="app">
<div className="result">
{loading ? <p>Loading...</p> : <h1>{randomWord.phrase}</h1>}
<div className="playback_audio" onClick={playbackCurrentWord}>
<svg
stroke="currentColor"
fill="none"
strokeWidth="2"
viewBox="0 0 24 24"
strokeLinecap="round"
strokeLinejoin="round"
height="1em"
width="1em"
xmlns="http://www.w3.org/2000/svg"
>
<polygon points="11 5 6 9 2 9 2 15 6 15 11 19 11 5"></polygon>
<path d="M19.07 4.93a10 10 0 0 1 0 14.14M15.54 8.46a5 5 0 0 1 0 7.07"></path>
</svg>
</div>
</div>
<footer>
<a href="https://git.ragestudio.net/srgooglo/monstercanker">
GitHub
</a>
<a href={API_ENDPOINT}>API</a>
</footer>
</div>
)
}
ReactDOM.createRoot(document.getElementById("root")).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)
ReactDOM.createRoot(document.getElementById("root")).render(<App />)