Log direct messages and keep track of open direct message tabs

This commit is contained in:
Ken-Håvard Lieng 2020-05-06 04:19:55 +02:00
parent e97bb519ed
commit 8305dd561d
17 changed files with 655 additions and 304 deletions

File diff suppressed because one or more lines are too long

View File

@ -29,6 +29,13 @@ function loadState({ store }, env) {
}); });
} }
if (env.openDMs) {
store.dispatch({
type: 'PRIVATE_CHATS',
privateChats: env.openDMs
});
}
if (env.users) { if (env.users) {
store.dispatch({ store.dispatch({
type: socketActions.USERS, type: socketActions.USERS,

View File

@ -1,7 +1,7 @@
import Cookie from 'js-cookie'; import Cookie from 'js-cookie';
import debounce from 'lodash/debounce'; import debounce from 'lodash/debounce';
import { getSelectedTab } from 'state/tab'; import { getSelectedTab } from 'state/tab';
import { isChannel, stringifyTab } from 'utils'; import { stringifyTab } from 'utils';
import { observe } from 'utils/observe'; import { observe } from 'utils/observe';
const saveTab = debounce( const saveTab = debounce(
@ -11,7 +11,7 @@ const saveTab = debounce(
export default function storage({ store }) { export default function storage({ store }) {
observe(store, getSelectedTab, tab => { observe(store, getSelectedTab, tab => {
if (isChannel(tab) || (tab.server && !tab.name)) { if (tab.server) {
saveTab(tab); saveTab(tab);
} }
}); });

View File

@ -26,6 +26,7 @@ export const CLOSE_MODAL = 'CLOSE_MODAL';
export const CLOSE_PRIVATE_CHAT = 'CLOSE_PRIVATE_CHAT'; export const CLOSE_PRIVATE_CHAT = 'CLOSE_PRIVATE_CHAT';
export const OPEN_PRIVATE_CHAT = 'OPEN_PRIVATE_CHAT'; export const OPEN_PRIVATE_CHAT = 'OPEN_PRIVATE_CHAT';
export const PRIVATE_CHATS = 'PRIVATE_CHATS';
export const SEARCH_MESSAGES = 'SEARCH_MESSAGES'; export const SEARCH_MESSAGES = 'SEARCH_MESSAGES';
export const TOGGLE_SEARCH = 'TOGGLE_SEARCH'; export const TOGGLE_SEARCH = 'TOGGLE_SEARCH';

View File

@ -30,6 +30,16 @@ export default createReducer(
} }
}, },
[actions.PRIVATE_CHATS](state, { privateChats }) {
privateChats.forEach(({ server, name }) => {
if (!state[server]) {
state[server] = [];
}
state[server].push(name);
});
},
[actions.socket.PM](state, action) { [actions.socket.PM](state, action) {
if (action.from.indexOf('.') === -1) { if (action.from.indexOf('.') === -1) {
open(state, action.server, action.from); open(state, action.server, action.from);
@ -46,7 +56,11 @@ export function openPrivateChat(server, nick) {
return { return {
type: actions.OPEN_PRIVATE_CHAT, type: actions.OPEN_PRIVATE_CHAT,
server, server,
nick nick,
socket: {
type: 'open_dm',
data: { server, name: nick }
}
}; };
} }
@ -55,7 +69,11 @@ export function closePrivateChat(server, nick) {
dispatch({ dispatch({
type: actions.CLOSE_PRIVATE_CHAT, type: actions.CLOSE_PRIVATE_CHAT,
server, server,
nick nick,
socket: {
type: 'close_dm',
data: { server, name: nick }
}
}); });
dispatch(updateSelection()); dispatch(updateSelection());
}; };

View File

@ -31,6 +31,7 @@ type indexData struct {
Defaults *config.Defaults Defaults *config.Defaults
Servers []Server Servers []Server
Channels []*storage.Channel Channels []*storage.Channel
OpenDMs []storage.Tab
HexIP bool HexIP bool
Version dispatchVersion Version dispatchVersion
@ -43,7 +44,7 @@ type indexData struct {
Messages *Messages Messages *Messages
} }
func (d *Dispatch) getIndexData(r *http.Request, path string, state *State) *indexData { func (d *Dispatch) getIndexData(r *http.Request, state *State) *indexData {
cfg := d.Config() cfg := d.Config()
data := indexData{ data := indexData{
@ -98,35 +99,37 @@ func (d *Dispatch) getIndexData(r *http.Request, path string, state *State) *ind
} }
data.Channels = channels data.Channels = channels
server, channel := getTabFromPath(path) openDMs, err := state.user.GetOpenDMs()
if isInChannel(channels, server, channel) { if err != nil {
data.addUsersAndMessages(server, channel, state) return nil
return &data
} }
data.OpenDMs = openDMs
server, channel = parseTabCookie(r, path) tab, err := tabFromRequest(r)
if isInChannel(channels, server, channel) { if err == nil && hasTab(channels, openDMs, tab.Server, tab.Name) {
data.addUsersAndMessages(server, channel, state) data.addUsersAndMessages(tab.Server, tab.Name, state)
} }
return &data return &data
} }
func (d *indexData) addUsersAndMessages(server, channel string, state *State) { func (d *indexData) addUsersAndMessages(server, name string, state *State) {
users := channelStore.GetUsers(server, channel) if isChannel(name) {
users := channelStore.GetUsers(server, name)
if len(users) > 0 { if len(users) > 0 {
d.Users = &Userlist{ d.Users = &Userlist{
Server: server, Server: server,
Channel: channel, Channel: name,
Users: users, Users: users,
} }
} }
}
messages, hasMore, err := state.user.GetLastMessages(server, channel, 50) messages, hasMore, err := state.user.GetLastMessages(server, name, 50)
if err == nil && len(messages) > 0 { if err == nil && len(messages) > 0 {
m := Messages{ m := Messages{
Server: server, Server: server,
To: channel, To: name,
Messages: messages, Messages: messages,
} }
@ -138,10 +141,16 @@ func (d *indexData) addUsersAndMessages(server, channel string, state *State) {
} }
} }
func isInChannel(channels []*storage.Channel, server, channel string) bool { func hasTab(channels []*storage.Channel, openDMs []storage.Tab, server, name string) bool {
if channel != "" { if name != "" {
for _, ch := range channels { for _, ch := range channels {
if server == ch.Server && channel == ch.Name { if server == ch.Server && name == ch.Name {
return true
}
}
for _, tab := range openDMs {
if server == tab.Server && name == tab.Name {
return true return true
} }
} }
@ -149,30 +158,52 @@ func isInChannel(channels []*storage.Channel, server, channel string) bool {
return false return false
} }
func getTabFromPath(rawPath string) (string, string) { func tabFromRequest(r *http.Request) (Tab, error) {
path := strings.Split(strings.Trim(rawPath, "/"), "/") tab := Tab{}
if len(path) >= 2 {
name, err := url.PathUnescape(path[len(path)-1]) var path string
if err == nil && isChannel(name) { if strings.HasPrefix(r.URL.Path, "/ws") {
return path[len(path)-2], name path = r.URL.EscapedPath()[3:]
} } else {
} referer, err := url.Parse(r.Referer())
return "", "" if err != nil {
} return tab, err
}
path = referer.EscapedPath()
}
func parseTabCookie(r *http.Request, path string) (string, string) {
if path == "/" { if path == "/" {
cookie, err := r.Cookie("tab") cookie, err := r.Cookie("tab")
if err == nil { if err != nil {
v, err := url.PathUnescape(cookie.Value) return tab, err
if err == nil { }
tab := strings.SplitN(v, ";", 2)
if len(tab) == 2 && isChannel(tab[1]) { v, err := url.PathUnescape(cookie.Value)
return tab[0], tab[1] if err != nil {
return tab, err
}
parts := strings.SplitN(v, ";", 2)
if len(parts) == 2 {
tab.Server = parts[0]
tab.Name = parts[1]
}
} else {
parts := strings.Split(strings.Trim(path, "/"), "/")
if len(parts) > 0 && len(parts) < 3 {
if len(parts) == 2 {
name, err := url.PathUnescape(parts[1])
if err != nil {
return tab, err
}
tab.Name = name
}
tab.Server = parts[0]
} }
} }
}
} return tab, nil
return "", ""
} }

View File

@ -104,6 +104,29 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer(in *jlexer.Lexer, out
} }
in.Delim(']') in.Delim(']')
} }
case "openDMs":
if in.IsNull() {
in.Skip()
out.OpenDMs = nil
} else {
in.Delim('[')
if out.OpenDMs == nil {
if !in.IsDelim(']') {
out.OpenDMs = make([]storage.Tab, 0, 2)
} else {
out.OpenDMs = []storage.Tab{}
}
} else {
out.OpenDMs = (out.OpenDMs)[:0]
}
for !in.IsDelim(']') {
var v3 storage.Tab
easyjson7e607aefDecodeGithubComKhliengDispatchStorage1(in, &v3)
out.OpenDMs = append(out.OpenDMs, v3)
in.WantComma()
}
in.Delim(']')
}
case "hexIP": case "hexIP":
out.HexIP = bool(in.Bool()) out.HexIP = bool(in.Bool())
case "version": case "version":
@ -176,11 +199,11 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchServer(out *jwriter.Writer, i
} }
{ {
out.RawByte('[') out.RawByte('[')
for v3, v4 := range in.Servers { for v4, v5 := range in.Servers {
if v3 > 0 { if v4 > 0 {
out.RawByte(',') out.RawByte(',')
} }
out.Raw((v4).MarshalJSON()) out.Raw((v5).MarshalJSON())
} }
out.RawByte(']') out.RawByte(']')
} }
@ -195,19 +218,38 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchServer(out *jwriter.Writer, i
} }
{ {
out.RawByte('[') out.RawByte('[')
for v5, v6 := range in.Channels { for v6, v7 := range in.Channels {
if v5 > 0 { if v6 > 0 {
out.RawByte(',') out.RawByte(',')
} }
if v6 == nil { if v7 == nil {
out.RawString("null") out.RawString("null")
} else { } else {
easyjson7e607aefEncodeGithubComKhliengDispatchStorage(out, *v6) easyjson7e607aefEncodeGithubComKhliengDispatchStorage(out, *v7)
} }
} }
out.RawByte(']') out.RawByte(']')
} }
} }
if len(in.OpenDMs) != 0 {
const prefix string = ",\"openDMs\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
{
out.RawByte('[')
for v8, v9 := range in.OpenDMs {
if v8 > 0 {
out.RawByte(',')
}
easyjson7e607aefEncodeGithubComKhliengDispatchStorage1(out, v9)
}
out.RawByte(']')
}
}
if in.HexIP { if in.HexIP {
const prefix string = ",\"hexIP\":" const prefix string = ",\"hexIP\":"
if first { if first {
@ -284,6 +326,61 @@ func (v *indexData) UnmarshalJSON(data []byte) error {
func (v *indexData) UnmarshalEasyJSON(l *jlexer.Lexer) { func (v *indexData) UnmarshalEasyJSON(l *jlexer.Lexer) {
easyjson7e607aefDecodeGithubComKhliengDispatchServer(l, v) easyjson7e607aefDecodeGithubComKhliengDispatchServer(l, v)
} }
func easyjson7e607aefDecodeGithubComKhliengDispatchStorage1(in *jlexer.Lexer, out *storage.Tab) {
isTopLevel := in.IsStart()
if in.IsNull() {
if isTopLevel {
in.Consumed()
}
in.Skip()
return
}
in.Delim('{')
for !in.IsDelim('}') {
key := in.UnsafeFieldName(false)
in.WantColon()
if in.IsNull() {
in.Skip()
in.WantComma()
continue
}
switch key {
case "server":
out.Server = string(in.String())
case "name":
out.Name = string(in.String())
default:
in.SkipRecursive()
}
in.WantComma()
}
in.Delim('}')
if isTopLevel {
in.Consumed()
}
}
func easyjson7e607aefEncodeGithubComKhliengDispatchStorage1(out *jwriter.Writer, in storage.Tab) {
out.RawByte('{')
first := true
_ = first
if in.Server != "" {
const prefix string = ",\"server\":"
first = false
out.RawString(prefix[1:])
out.String(string(in.Server))
}
if in.Name != "" {
const prefix string = ",\"name\":"
if first {
first = false
out.RawString(prefix[1:])
} else {
out.RawString(prefix)
}
out.String(string(in.Name))
}
out.RawByte('}')
}
func easyjson7e607aefDecodeGithubComKhliengDispatchStorage(in *jlexer.Lexer, out *storage.Channel) { func easyjson7e607aefDecodeGithubComKhliengDispatchStorage(in *jlexer.Lexer, out *storage.Channel) {
isTopLevel := in.IsStart() isTopLevel := in.IsStart()
if in.IsNull() { if in.IsNull() {
@ -392,9 +489,9 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchConfig(in *jlexer.Lexer, out
out.Channels = (out.Channels)[:0] out.Channels = (out.Channels)[:0]
} }
for !in.IsDelim(']') { for !in.IsDelim(']') {
var v7 string var v10 string
v7 = string(in.String()) v10 = string(in.String())
out.Channels = append(out.Channels, v7) out.Channels = append(out.Channels, v10)
in.WantComma() in.WantComma()
} }
in.Delim(']') in.Delim(']')
@ -457,11 +554,11 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchConfig(out *jwriter.Writer, i
} }
{ {
out.RawByte('[') out.RawByte('[')
for v8, v9 := range in.Channels { for v11, v12 := range in.Channels {
if v8 > 0 { if v11 > 0 {
out.RawByte(',') out.RawByte(',')
} }
out.String(string(v9)) out.String(string(v12))
} }
out.RawByte(']') out.RawByte(']')
} }
@ -640,9 +737,9 @@ func easyjson7e607aefDecodeGithubComKhliengDispatchServer2(in *jlexer.Lexer, out
out.Channels = (out.Channels)[:0] out.Channels = (out.Channels)[:0]
} }
for !in.IsDelim(']') { for !in.IsDelim(']') {
var v10 string var v13 string
v10 = string(in.String()) v13 = string(in.String())
out.Channels = append(out.Channels, v10) out.Channels = append(out.Channels, v13)
in.WantComma() in.WantComma()
} }
in.Delim(']') in.Delim(']')
@ -705,11 +802,11 @@ func easyjson7e607aefEncodeGithubComKhliengDispatchServer2(out *jwriter.Writer,
} }
{ {
out.RawByte('[') out.RawByte('[')
for v11, v12 := range in.Channels { for v14, v15 := range in.Channels {
if v11 > 0 { if v14 > 0 {
out.RawByte(',') out.RawByte(',')
} }
out.String(string(v12)) out.String(string(v15))
} }
out.RawByte(']') out.RawByte(']')
} }

View File

@ -1,47 +1,60 @@
package server package server
import ( import (
"net/http"
"net/url"
"testing" "testing"
"github.com/khlieng/dispatch/storage"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestGetTabFromPath(t *testing.T) { func TestGetTabFromPath(t *testing.T) {
cases := []struct { cases := []struct {
input string input *http.Request
expectedServer string expectedTab Tab
expectedChannel string
}{ }{
{ {
"/chat.freenode.net/%23r%2Fstuff%2F/", &http.Request{
"chat.freenode.net", URL: &url.URL{Path: "/init"},
"#r/stuff/", Header: http.Header{"Referer": []string{"/chat.freenode.net/%23r%2Fstuff%2F"}},
},
Tab{storage.Tab{Server: "chat.freenode.net", Name: "#r/stuff/"}},
}, { }, {
"/chat.freenode.net/%23r%2Fstuff%2F", &http.Request{
"chat.freenode.net", URL: &url.URL{Path: "/init"},
"#r/stuff/", Header: http.Header{"Referer": []string{"/chat.freenode.net/%23r%2Fstuff"}},
},
Tab{storage.Tab{Server: "chat.freenode.net", Name: "#r/stuff"}},
}, { }, {
"/chat.freenode.net/%23r%2Fstuff", &http.Request{
"chat.freenode.net", URL: &url.URL{Path: "/init"},
"#r/stuff", Header: http.Header{"Referer": []string{"/chat.freenode.net/%23stuff"}},
},
Tab{storage.Tab{Server: "chat.freenode.net", Name: "#stuff"}},
}, { }, {
"/chat.freenode.net/%23stuff", &http.Request{
"chat.freenode.net", URL: &url.URL{Path: "/init"},
"#stuff", Header: http.Header{"Referer": []string{"/chat.freenode.net/stuff"}},
},
Tab{storage.Tab{Server: "chat.freenode.net", Name: "stuff"}},
}, { }, {
"/chat.freenode.net/%23stuff/cake", &http.Request{
"", URL: &url.URL{Path: "/init"},
"", Header: http.Header{"Referer": []string{"/data/chat.freenode.net/%23apples"}},
},
Tab{},
}, { }, {
"/data/chat.freenode.net/%23apples", &http.Request{
"chat.freenode.net", URL: &url.URL{Path: "/ws/chat.freenode.net"},
"#apples", },
Tab{storage.Tab{Server: "chat.freenode.net"}},
}, },
} }
for _, tc := range cases { for _, tc := range cases {
server, channel := getTabFromPath(tc.input) tab, err := tabFromRequest(tc.input)
assert.Equal(t, tc.expectedServer, server) assert.Nil(t, err)
assert.Equal(t, tc.expectedChannel, channel) assert.Equal(t, tc.expectedTab, tab)
} }
} }

View File

@ -169,17 +169,21 @@ func (i *ircHandler) message(msg *irc.Message) {
From: msg.Nick, From: msg.Nick,
Content: msg.LastParam(), Content: msg.LastParam(),
} }
target := msg.Params[0]
if msg.Params[0] == i.client.GetNick() { if target == i.client.GetNick() {
i.state.sendJSON("pm", message) i.state.sendJSON("pm", message)
i.state.user.AddOpenDM(i.client.Host, message.From)
target = message.From
} else { } else {
message.To = msg.Params[0] message.To = target
i.state.sendJSON("message", message) i.state.sendJSON("message", message)
} }
if msg.Params[0] != "*" { if target != "*" {
go i.state.user.LogMessage(message.ID, go i.state.user.LogMessage(message.ID,
i.client.Host, msg.Nick, msg.Params[0], msg.LastParam()) i.client.Host, msg.Nick, target, msg.LastParam())
} }
} }

View File

@ -221,3 +221,7 @@ type ChannelForward struct {
Old string Old string
New string New string
} }
type Tab struct {
storage.Tab
}

File diff suppressed because it is too large Load Diff

View File

@ -3,7 +3,6 @@ package server
import ( import (
"log" "log"
"net/http" "net/http"
"net/url"
"strings" "strings"
"sync" "sync"
@ -163,14 +162,8 @@ func (d *Dispatch) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
if r.URL.Path == "/init" { if r.URL.Path == "/init" {
referer, err := url.Parse(r.Header.Get("Referer"))
if err != nil {
fail(w, http.StatusInternalServerError)
return
}
state := d.handleAuth(w, r, true, true) state := d.handleAuth(w, r, true, true)
data := d.getIndexData(r, referer.EscapedPath(), state) data := d.getIndexData(r, state)
writeJSON(w, r, data) writeJSON(w, r, data)
} else if strings.HasPrefix(r.URL.Path, "/ws") { } else if strings.HasPrefix(r.URL.Path, "/ws") {

View File

@ -68,17 +68,15 @@ func (h *wsHandler) init(r *http.Request) {
h.state.numIRC(), "IRC connections |", h.state.numIRC(), "IRC connections |",
h.state.numWS(), "WebSocket connections") h.state.numWS(), "WebSocket connections")
tab, err := tabFromRequest(r)
channels, err := h.state.user.GetChannels() channels, err := h.state.user.GetChannels()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
path := r.URL.EscapedPath()
pathServer, pathChannel := getTabFromPath(path)
cookieServer, cookieChannel := parseTabCookie(r, path[3:])
for _, channel := range channels { for _, channel := range channels {
if (channel.Server == pathServer && channel.Name == pathChannel) || if channel.Server == tab.Server && channel.Name == tab.Name {
(channel.Server == cookieServer && channel.Name == cookieChannel) {
// Userlist and messages for this channel gets embedded in the index page // Userlist and messages for this channel gets embedded in the index page
continue continue
} }
@ -91,6 +89,19 @@ func (h *wsHandler) init(r *http.Request) {
h.state.sendLastMessages(channel.Server, channel.Name, 50) h.state.sendLastMessages(channel.Server, channel.Name, 50)
} }
openDMs, err := h.state.user.GetOpenDMs()
if err != nil {
log.Println(err)
}
for _, openDM := range openDMs {
if openDM.Server == tab.Server && openDM.Name == tab.Name {
continue
}
h.state.sendLastMessages(openDM.Server, openDM.Name, 50)
}
} }
func (h *wsHandler) connect(b []byte) { func (h *wsHandler) connect(b []byte) {
@ -306,6 +317,21 @@ func (h *wsHandler) channelSearch(b []byte) {
} }
} }
func (h *wsHandler) openDM(b []byte) {
var data Tab
data.UnmarshalJSON(b)
h.state.sendLastMessages(data.Server, data.Name, 50)
h.state.user.AddOpenDM(data.Server, data.Name)
}
func (h *wsHandler) closeDM(b []byte) {
var data Tab
data.UnmarshalJSON(b)
h.state.user.RemoveOpenDM(data.Server, data.Name)
}
func (h *wsHandler) initHandlers() { func (h *wsHandler) initHandlers() {
h.handlers = map[string]func([]byte){ h.handlers = map[string]func([]byte){
"connect": h.connect, "connect": h.connect,
@ -327,6 +353,8 @@ func (h *wsHandler) initHandlers() {
"set_server_name": h.setServerName, "set_server_name": h.setServerName,
"settings_set": h.setSettings, "settings_set": h.setSettings,
"channel_search": h.channelSearch, "channel_search": h.channelSearch,
"open_dm": h.openDM,
"close_dm": h.closeDM,
} }
} }

View File

@ -15,6 +15,7 @@ var (
bucketUsers = []byte("Users") bucketUsers = []byte("Users")
bucketServers = []byte("Servers") bucketServers = []byte("Servers")
bucketChannels = []byte("Channels") bucketChannels = []byte("Channels")
bucketOpenDMs = []byte("OpenDMs")
bucketMessages = []byte("Messages") bucketMessages = []byte("Messages")
bucketSessions = []byte("Sessions") bucketSessions = []byte("Sessions")
) )
@ -34,6 +35,7 @@ func New(path string) (*BoltStore, error) {
tx.CreateBucketIfNotExists(bucketUsers) tx.CreateBucketIfNotExists(bucketUsers)
tx.CreateBucketIfNotExists(bucketServers) tx.CreateBucketIfNotExists(bucketServers)
tx.CreateBucketIfNotExists(bucketChannels) tx.CreateBucketIfNotExists(bucketChannels)
tx.CreateBucketIfNotExists(bucketOpenDMs)
tx.CreateBucketIfNotExists(bucketMessages) tx.CreateBucketIfNotExists(bucketMessages)
tx.CreateBucketIfNotExists(bucketSessions) tx.CreateBucketIfNotExists(bucketSessions)
return nil return nil
@ -168,6 +170,13 @@ func (s *BoltStore) RemoveServer(user *storage.User, address string) error {
b.Delete(k) b.Delete(k)
} }
b = tx.Bucket(bucketOpenDMs)
c = b.Cursor()
for k, _ := c.Seek(serverID); bytes.HasPrefix(k, serverID); k, _ = c.Next() {
b.Delete(k)
}
return nil return nil
}) })
} }
@ -246,6 +255,42 @@ func (s *BoltStore) RemoveChannel(user *storage.User, server, channel string) er
}) })
} }
func (s *BoltStore) GetOpenDMs(user *storage.User) ([]storage.Tab, error) {
var openDMs []storage.Tab
s.db.View(func(tx *bolt.Tx) error {
c := tx.Bucket(bucketOpenDMs).Cursor()
for k, _ := c.Seek(user.IDBytes); bytes.HasPrefix(k, user.IDBytes); k, _ = c.Next() {
tab := bytes.Split(k[8:], []byte{0})
openDMs = append(openDMs, storage.Tab{
Server: string(tab[0]),
Name: string(tab[1]),
})
}
return nil
})
return openDMs, nil
}
func (s *BoltStore) AddOpenDM(user *storage.User, server, nick string) error {
return s.db.Batch(func(tx *bolt.Tx) error {
b := tx.Bucket(bucketOpenDMs)
return b.Put(channelID(user, server, nick), nil)
})
}
func (s *BoltStore) RemoveOpenDM(user *storage.User, server, nick string) error {
return s.db.Batch(func(tx *bolt.Tx) error {
b := tx.Bucket(bucketOpenDMs)
return b.Delete(channelID(user, server, nick))
})
}
func (s *BoltStore) LogMessage(message *storage.Message) error { func (s *BoltStore) LogMessage(message *storage.Message) error {
return s.db.Batch(func(tx *bolt.Tx) error { return s.db.Batch(func(tx *bolt.Tx) error {
b, err := tx.Bucket(bucketMessages).CreateBucketIfNotExists([]byte(message.Server + ":" + message.To)) b, err := tx.Bucket(bucketMessages).CreateBucketIfNotExists([]byte(message.Server + ":" + message.To))

View File

@ -38,6 +38,10 @@ type Store interface {
GetChannels(user *User) ([]*Channel, error) GetChannels(user *User) ([]*Channel, error)
AddChannel(user *User, channel *Channel) error AddChannel(user *User, channel *Channel) error
RemoveChannel(user *User, server, channel string) error RemoveChannel(user *User, server, channel string) error
GetOpenDMs(user *User) ([]Tab, error)
AddOpenDM(user *User, server, nick string) error
RemoveOpenDM(user *User, server, nick string) error
} }
type SessionStore interface { type SessionStore interface {

View File

@ -190,6 +190,23 @@ func (u *User) RemoveChannel(server, channel string) error {
return u.store.RemoveChannel(u, server, channel) return u.store.RemoveChannel(u, server, channel)
} }
type Tab struct {
Server string
Name string
}
func (u *User) GetOpenDMs() ([]Tab, error) {
return u.store.GetOpenDMs(u)
}
func (u *User) AddOpenDM(server, nick string) error {
return u.store.AddOpenDM(u, server, nick)
}
func (u *User) RemoveOpenDM(server, nick string) error {
return u.store.RemoveOpenDM(u, server, nick)
}
type Message struct { type Message struct {
ID string `json:"-" bleve:"-"` ID string `json:"-" bleve:"-"`
Server string `json:"-" bleve:"server"` Server string `json:"-" bleve:"server"`

View File

@ -80,6 +80,16 @@ func TestUser(t *testing.T) {
channels, err = user.GetChannels() channels, err = user.GetChannels()
assert.Len(t, channels, 0) assert.Len(t, channels, 0)
user.AddOpenDM(srv.Host, "cake")
openDMs, err := user.GetOpenDMs()
assert.Nil(t, err)
assert.Len(t, openDMs, 1)
err = user.RemoveOpenDM(srv.Host, "cake")
assert.Nil(t, err)
openDMs, err = user.GetOpenDMs()
assert.Nil(t, err)
assert.Len(t, openDMs, 0)
settings := user.GetClientSettings() settings := user.GetClientSettings()
assert.NotNil(t, settings) assert.NotNil(t, settings)
assert.Equal(t, storage.DefaultClientSettings(), settings) assert.Equal(t, storage.DefaultClientSettings(), settings)