-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* ui chat * working 2-way chat with and without beerchat mod * chat page * partial command support * ux * update readme --------- Co-authored-by: BuckarooBanzay <[email protected]>
- Loading branch information
1 parent
ab42634
commit 820faa2
Showing
25 changed files
with
432 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package db | ||
|
||
import ( | ||
"mtui/types" | ||
"time" | ||
|
||
"github.com/google/uuid" | ||
"github.com/minetest-go/dbutil" | ||
) | ||
|
||
type ChatLogRepository struct { | ||
dbu *dbutil.DBUtil[*types.ChatLog] | ||
} | ||
|
||
func (r *ChatLogRepository) Insert(l *types.ChatLog) error { | ||
if l.ID == "" { | ||
l.ID = uuid.NewString() | ||
} | ||
|
||
if l.Timestamp == 0 { | ||
l.Timestamp = time.Now().UnixMilli() | ||
} | ||
|
||
return r.dbu.Insert(l) | ||
} | ||
|
||
func (r *ChatLogRepository) Search(channel string, from, to int64) ([]*types.ChatLog, error) { | ||
return r.dbu.SelectMulti("where channel = %s and timestamp > %s and timestamp < %s order by timestamp asc limit 1000", channel, from, to) | ||
} | ||
|
||
func (r *ChatLogRepository) GetLatest(channel string, limit int) ([]*types.ChatLog, error) { | ||
return r.dbu.SelectMulti("where channel = %s order by timestamp asc limit %s", channel, limit) | ||
} | ||
|
||
func (r *ChatLogRepository) DeleteBefore(timestamp int64) error { | ||
return r.dbu.Delete("where timestamp < %s", timestamp) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package db_test | ||
|
||
import ( | ||
"mtui/db" | ||
"mtui/types" | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestChatLogRepo(t *testing.T) { | ||
_db := setupDB(t) | ||
repo := db.NewRepositories(_db).ChatLogRepo | ||
|
||
assert.NoError(t, repo.Insert(&types.ChatLog{Timestamp: 100, Channel: "main", Name: "player1", Message: "msg1"})) | ||
assert.NoError(t, repo.Insert(&types.ChatLog{Timestamp: 200, Channel: "main", Name: "player2", Message: "msg2"})) | ||
assert.NoError(t, repo.Insert(&types.ChatLog{Timestamp: 150, Channel: "other_chan", Name: "player1", Message: "msg1"})) | ||
|
||
list, err := repo.GetLatest("main", 100) | ||
assert.NoError(t, err) | ||
assert.Equal(t, 2, len(list)) | ||
assert.Equal(t, "msg1", list[0].Message) | ||
assert.Equal(t, "player1", list[0].Name) | ||
assert.Equal(t, "msg2", list[1].Message) | ||
assert.Equal(t, "player2", list[1].Name) | ||
|
||
list, err = repo.Search("main", 99, 199) | ||
assert.NoError(t, err) | ||
assert.Equal(t, 1, len(list)) | ||
assert.Equal(t, "msg1", list[0].Message) | ||
assert.Equal(t, "player1", list[0].Name) | ||
|
||
assert.NoError(t, repo.DeleteBefore(199)) | ||
|
||
list, err = repo.GetLatest("main", 100) | ||
assert.NoError(t, err) | ||
assert.Equal(t, 1, len(list)) | ||
assert.Equal(t, "msg2", list[0].Message) | ||
assert.Equal(t, "player2", list[0].Name) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
|
||
create table chat_log( | ||
id text(36) primary key not null, -- uuid | ||
timestamp integer not null, -- unix milliseconds | ||
channel text(32) not null, -- main | ||
name text(64) not null, -- from-playername | ||
message text(512) not null -- message | ||
); | ||
|
||
create index chat_log_index on chat_log(timestamp, channel); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,8 +1,32 @@ | ||
package events | ||
|
||
import "mtui/eventbus" | ||
|
||
const ( | ||
// DM | ||
DirectChatMessageEvent eventbus.EventType = "direct_chat" | ||
import ( | ||
"encoding/json" | ||
"fmt" | ||
"mtui/app" | ||
"mtui/bridge" | ||
"mtui/types" | ||
"mtui/types/command" | ||
) | ||
|
||
func chatLoop(a *app.App, ch chan *bridge.CommandResponse) { | ||
for cmd := range ch { | ||
msg := &command.ChatMessage{} | ||
err := json.Unmarshal(cmd.Data, msg) | ||
if err != nil { | ||
fmt.Printf("Chat notification payload error: %s\n", err.Error()) | ||
continue | ||
} | ||
|
||
err = a.Repos.ChatLogRepo.Insert(&types.ChatLog{ | ||
Channel: msg.Channel, | ||
Name: msg.Name, | ||
Message: msg.Message, | ||
}) | ||
if err != nil { | ||
fmt.Printf("Chat insert error: %s\n", err.Error()) | ||
continue | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package jobs | ||
|
||
import ( | ||
"fmt" | ||
"mtui/app" | ||
"time" | ||
) | ||
|
||
func chatlogCleanup(a *app.App) { | ||
for { | ||
if !a.MaintenanceMode.Load() { | ||
ts := time.Now().AddDate(0, 0, -30) | ||
err := a.Repos.ChatLogRepo.DeleteBefore(ts.UnixMilli()) | ||
if err != nil { | ||
fmt.Printf("ChatLog cleanup error: %s\n", err.Error()) | ||
} | ||
} | ||
|
||
// re-schedule | ||
time.Sleep(time.Second * 10) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
name = mtui_dev | ||
max_users = 13 | ||
server_name = mtui-xxx | ||
mtui.url = http://ui:8080 | ||
mtui.key = mykey | ||
server_announce = true | ||
server_announce = false | ||
secure.http_mods = mtui | ||
name = mtui_dev | ||
max_users = 13 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
export const get_latest_chat_messages = channel => fetch(`api/chat/${channel}/latest`).then(r => r.json()); | ||
|
||
export const search_messages = (channel, from, to) => fetch(`api/chat/${channel}/${from}/${to}`).then(r => r.json()); | ||
|
||
export const send_message = msg => fetch("api/chat", { | ||
method: "POST", | ||
body: JSON.stringify(msg) | ||
}) | ||
.then(r => r.json()); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import DefaultLayout from "../layouts/DefaultLayout.js"; | ||
|
||
import { START } from "../Breadcrumb.js"; | ||
import { search_messages, send_message } from "../../api/chat.js"; | ||
import { execute_chatcommand } from "../../api/chatcommand.js"; | ||
import { get_claims } from "../../service/login.js"; | ||
import format_time from "../../util/format_time.js"; | ||
|
||
export default { | ||
components: { | ||
"default-layout": DefaultLayout | ||
}, | ||
data: function() { | ||
return { | ||
history: [], | ||
msg: "", | ||
handle: null, | ||
channel: "main", | ||
from_timestamp: Date.now() - (3600*1000*24*7), //7 days back or 1000 messages | ||
breadcrumb: [START, { | ||
name: "Chat", | ||
icon: "comment", | ||
link: "" | ||
}] | ||
}; | ||
}, | ||
mounted: function() { | ||
this.update(); | ||
this.handle = setInterval(() => this.update(), 1000); | ||
}, | ||
unmounted: function() { | ||
clearInterval(this.handle); | ||
}, | ||
methods: { | ||
format_time, | ||
send: function() { | ||
if (this.is_command) { | ||
// send command | ||
execute_chatcommand(get_claims().username, this.msg.substring(1)) | ||
.then(result => { | ||
this.scroll(); | ||
this.history.push({ | ||
timestamp: +Date.now(), | ||
name: "", | ||
message: result.message, | ||
success: result.success | ||
}); | ||
this.msg = ""; | ||
}); | ||
|
||
} else { | ||
// send message | ||
send_message({ | ||
channel: this.channel, | ||
message: this.msg | ||
}) | ||
.then(() => { | ||
this.update(); | ||
this.msg = ""; | ||
}); | ||
} | ||
}, | ||
update: function() { | ||
const later = Date.now() + (3600*1000); | ||
search_messages(this.channel, this.from_timestamp, later) | ||
.then(msgs => { | ||
msgs.forEach(msg => { | ||
if (msg.timestamp > this.from_timestamp) { | ||
this.from_timestamp = msg.timestamp; | ||
} | ||
this.scroll(); | ||
this.history.push(msg); | ||
}); | ||
}); | ||
}, | ||
scroll: function() { | ||
const el = this.$refs.container; | ||
if (el.scrollTop == el.scrollTopMax) { | ||
// at the bottom, scroll further | ||
setTimeout(() => el.scrollTop = el.scrollTopMax, 10); | ||
} | ||
} | ||
}, | ||
computed: { | ||
is_command: function() { | ||
return this.msg.length > 0 && this.msg[0] == '/'; | ||
} | ||
}, | ||
template: /*html*/` | ||
<default-layout title="Chat" icon="comment" :breadcrumb="breadcrumb"> | ||
<div ref="container" style="height: 600px; overflow: scroll;"> | ||
<div v-for="msg in history" :key="msg.id" | ||
v-bind:class="{'bg-success':msg.success==true, 'bg-warning':msg.success==false}" | ||
style="display: flex;"> | ||
<div class="text-muted" style="width: 200px; flex: 0 0 auto;"> | ||
{{format_time(msg.timestamp/1000)}} | ||
</div> | ||
<div style="width: 200px; flex: 0 0 auto;"> | ||
<router-link :to="'/profile/' + msg.name" v-if="msg.name != ''"> | ||
{{msg.name}} | ||
</router-link> | ||
<span v-else> | ||
{{msg.name}} | ||
</span> | ||
</div> | ||
<div style="flex: 1 1 auto;"> | ||
{{msg.message}} | ||
</div> | ||
</div> | ||
</div> | ||
<form @submit.prevent="send" class="row"> | ||
<div class="input-group"> | ||
<input type="text" placeholder="Message" v-model="msg" class="form-control"/> | ||
<button class="btn btn-success" type="submit" v-if="!is_command" :disabled="msg == ''"> | ||
<i class="fa-solid fa-paper-plane"></i> | ||
Send | ||
</button> | ||
<button class="btn btn-warning" type="submit" v-if="is_command"> | ||
<i class="fa-solid fa-terminal"></i> | ||
Execute command | ||
</button> | ||
</div> | ||
</form> | ||
</default-layout> | ||
` | ||
}; |
Oops, something went wrong.