Added support for simple named mentions.

Added a GetByName method to the UserStore.
Added eight mention tests.

Added the tryStepBackward function, might get removed.
Removed some superfluous commented debug statements.
This commit is contained in:
Azareal 2018-09-20 16:31:09 +10:00
parent 47d1010a53
commit e9645c07bd
3 changed files with 70 additions and 27 deletions

View File

@ -180,10 +180,16 @@ func tryStepForward(i int, step int, runes []rune) (int, bool) {
return i - step, false return i - step, false
} }
// TODO: Write a test for this
func tryStepBackward(i int, step int, runes []rune) (int, bool) {
if i == 0 {
return i, false
}
return i - 1, true
}
// TODO: Preparse Markdown and normalize it into HTML? // TODO: Preparse Markdown and normalize it into HTML?
func PreparseMessage(msg string) string { func PreparseMessage(msg string) string {
//fmt.Println("initial msg: ", msg)
//fmt.Println("initial []byte(msg): ", []byte(msg))
// TODO: Kick this check down a level into SanitiseBody? // TODO: Kick this check down a level into SanitiseBody?
if !utf8.ValidString(msg) { if !utf8.ValidString(msg) {
return "" return ""
@ -201,8 +207,6 @@ func PreparseMessage(msg string) string {
// There are a few useful cases for having spaces, but I'd like to stop the WYSIWYG from inserting random lines here and there // There are a few useful cases for having spaces, but I'd like to stop the WYSIWYG from inserting random lines here and there
msg = SanitiseBody(msg) msg = SanitiseBody(msg)
//fmt.Println("before msg: ", msg)
//fmt.Println("before []byte(msg): ", []byte(msg))
var runes = []rune(msg) var runes = []rune(msg)
msg = "" msg = ""
@ -263,7 +267,6 @@ func PreparseMessage(msg string) string {
for i := 0; i < len(runes); i++ { for i := 0; i < len(runes); i++ {
char := runes[i] char := runes[i]
if char == '&' && peekMatch(i, "lt;", runes) { if char == '&' && peekMatch(i, "lt;", runes) {
//fmt.Println("found less than")
var ok bool var ok bool
i, ok = tryStepForward(i, 4, runes) i, ok = tryStepForward(i, 4, runes)
if !ok { if !ok {
@ -271,8 +274,6 @@ func PreparseMessage(msg string) string {
break break
} }
char := runes[i] char := runes[i]
//fmt.Println("char: ", char)
//fmt.Println("string(char): ", string(char))
if int(char) >= len(allowedTags) { if int(char) >= len(allowedTags) {
//fmt.Println("sentinel char out of bounds") //fmt.Println("sentinel char out of bounds")
msg += "&" msg += "&"
@ -311,29 +312,19 @@ func PreparseMessage(msg string) string {
var newI = -1 var newI = -1
var out string var out string
toActionList := tagToAction[char] toActionList := tagToAction[char]
//fmt.Println("toActionList: ", toActionList)
for _, toAction := range toActionList { for _, toAction := range toActionList {
//fmt.Printf("toAction: %+v\n", toAction)
// TODO: Optimise this, maybe with goto or a function call to avoid scanning the text twice? // TODO: Optimise this, maybe with goto or a function call to avoid scanning the text twice?
if (toAction.PartialMode && !closeTag && peekMatch(i, toAction.Suffix, runes)) || peekMatch(i, toAction.Suffix+"&gt;", runes) { if (toAction.PartialMode && !closeTag && peekMatch(i, toAction.Suffix, runes)) || peekMatch(i, toAction.Suffix+"&gt;", runes) {
//fmt.Println("peekMatched")
newI, out = toAction.Do(toAction, !closeTag, i, runes) newI, out = toAction.Do(toAction, !closeTag, i, runes)
//fmt.Println("newI: ", newI)
//fmt.Println("i: ", i)
//fmt.Println("string(runes[i]): ", string(runes[i]))
if newI != -1 { if newI != -1 {
i = newI i = newI
} else if out != "" { } else if out != "" {
i += len(toAction.Suffix + "&gt;") i += len(toAction.Suffix + "&gt;")
} }
//fmt.Println("i: ", i)
//fmt.Println("string(runes[i]): ", string(runes[i]))
//fmt.Println("out: ", out)
break break
} }
} }
if out == "" { if out == "" {
//fmt.Println("no out")
msg += "&" msg += "&"
if closeTag { if closeTag {
i -= 5 i -= 5
@ -343,24 +334,54 @@ func PreparseMessage(msg string) string {
} else if out != " " { } else if out != " " {
msg += out msg += out
} }
} else if char == '@' && (i == 0 || runes[i-1] < 33) {
// TODO: Handle usernames containing spaces, maybe in the front-end with AJAX
// Do not mention-ify ridiculously long things
var ok bool
i, ok = tryStepForward(i, 1, runes)
if !ok {
msg += "@"
continue
}
start := i
for j := 0; i < len(runes) && j < Config.MaxUsernameLength; j++ {
cchar := runes[i]
if cchar < 33 {
break
}
i++
}
username := string(runes[start:i])
if username == "" {
msg += "@"
i = start - 1
continue
}
//fmt.Printf("username: %+v\n", username)
user, err := Users.GetByName(username)
if err != nil {
if err != ErrNoRows {
LogError(err)
}
msg += "@"
i = start - 1
continue
}
msg += "@" + strconv.Itoa(user.ID)
i--
} else { } else {
msg += string(char) msg += string(char)
} }
} }
//fmt.Println("running autoclosers")
//fmt.Println("msg: ", msg)
for _, actionList := range tagToAction { for _, actionList := range tagToAction {
//if len(actionList) > 0 {
// fmt.Println("actionList: ", actionList)
//}
for _, toAction := range actionList { for _, toAction := range actionList {
//fmt.Printf("toAction: %+v\n", toAction)
if toAction.Depth > 0 { if toAction.Depth > 0 {
//fmt.Println("autoclosing")
for ; toAction.Depth > 0; toAction.Depth-- { for ; toAction.Depth > 0; toAction.Depth-- {
_, out := toAction.Do(toAction, false, len(runes), runes) _, out := toAction.Do(toAction, false, len(runes), runes)
//fmt.Println("out: ", out)
if out != "" { if out != "" {
msg += out msg += out
} }
@ -368,8 +389,6 @@ func PreparseMessage(msg string) string {
} }
} }
} }
//fmt.Println("msg: ", msg)
return strings.TrimSpace(shortcodeToUnicode(msg)) return strings.TrimSpace(shortcodeToUnicode(msg))
} }

View File

@ -19,6 +19,7 @@ var ErrLongUsername = errors.New("this username is too long")
type UserStore interface { type UserStore interface {
DirtyGet(id int) *User DirtyGet(id int) *User
Get(id int) (*User, error) Get(id int) (*User, error)
GetByName(name string) (*User, error)
Exists(id int) bool Exists(id int) bool
GetOffset(offset int, perPage int) (users []*User, err error) GetOffset(offset int, perPage int) (users []*User, err error)
//BulkGet(ids []int) ([]*User, error) //BulkGet(ids []int) ([]*User, error)
@ -36,6 +37,7 @@ type DefaultUserStore struct {
cache UserCache cache UserCache
get *sql.Stmt get *sql.Stmt
getByName *sql.Stmt
getOffset *sql.Stmt getOffset *sql.Stmt
exists *sql.Stmt exists *sql.Stmt
register *sql.Stmt register *sql.Stmt
@ -53,6 +55,7 @@ func NewDefaultUserStore(cache UserCache) (*DefaultUserStore, error) {
return &DefaultUserStore{ return &DefaultUserStore{
cache: cache, cache: cache,
get: acc.SimpleSelect("users", "name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group", "uid = ?", "", ""), get: acc.SimpleSelect("users", "name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group", "uid = ?", "", ""),
getByName: acc.Select("users").Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group").Where("name = ?").Prepare(),
getOffset: acc.Select("users").Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group").Orderby("uid ASC").Limit("?,?").Prepare(), getOffset: acc.Select("users").Columns("uid, name, group, active, is_super_admin, session, email, avatar, message, url_prefix, url_name, level, score, liked, last_ip, temp_group").Orderby("uid ASC").Limit("?,?").Prepare(),
exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""), exists: acc.SimpleSelect("users", "uid", "uid = ?", "", ""),
register: acc.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()"), // TODO: Implement user_count on users_groups here register: acc.SimpleInsert("users", "name, email, password, salt, group, is_super_admin, session, active, message, createdAt, lastActiveAt", "?,?,?,?,?,0,'',?,'',UTC_TIMESTAMP(),UTC_TIMESTAMP()"), // TODO: Implement user_count on users_groups here
@ -98,6 +101,19 @@ func (mus *DefaultUserStore) Get(id int) (*User, error) {
return user, err return user, err
} }
// TODO: Log weird cache errors? Not just here but in every *Cache?
// ! This bypasses the cache, use frugally
func (mus *DefaultUserStore) GetByName(name string) (*User, error) {
user := &User{Loggedin: true}
err := mus.getByName.QueryRow(name).Scan(&user.ID, &user.Name, &user.Group, &user.Active, &user.IsSuperAdmin, &user.Session, &user.Email, &user.RawAvatar, &user.Message, &user.URLPrefix, &user.URLName, &user.Level, &user.Score, &user.Liked, &user.LastIP, &user.TempGroup)
user.Init()
if err == nil {
mus.cache.Set(user)
}
return user, err
}
// TODO: Optimise this, so we don't wind up hitting the database every-time for small gaps // TODO: Optimise this, so we don't wind up hitting the database every-time for small gaps
// TODO: Make this a little more consistent with DefaultGroupStore's GetRange method // TODO: Make this a little more consistent with DefaultGroupStore's GetRange method
func (store *DefaultUserStore) GetOffset(offset int, perPage int) (users []*User, err error) { func (store *DefaultUserStore) GetOffset(offset int, perPage int) (users []*User, err error) {

View File

@ -1223,6 +1223,14 @@ func TestPreparser(t *testing.T) {
msgList = addMETri(msgList, "</><>", "&lt;/&gt;&lt;&gt;") msgList = addMETri(msgList, "</><>", "&lt;/&gt;&lt;&gt;")
msgList = addMETri(msgList, "<>", "&lt;&gt;") msgList = addMETri(msgList, "<>", "&lt;&gt;")
msgList = addMETri(msgList, "</>", "&lt;/&gt;") msgList = addMETri(msgList, "</>", "&lt;/&gt;")
msgList = addMETri(msgList, "@", "@")
msgList = addMETri(msgList, "@Admin", "@1")
msgList = addMETri(msgList, "@Bah", "@Bah")
msgList = addMETri(msgList, " @Admin", "@1")
msgList = addMETri(msgList, "\n@Admin", "@1")
msgList = addMETri(msgList, "@Admin\n", "@1")
msgList = addMETri(msgList, "@Admin\ndd", "@1\ndd")
msgList = addMETri(msgList, "d@Admin", "d@Admin")
//msgList = addMETri(msgList, "byte 0", string([]byte{0}), "") //msgList = addMETri(msgList, "byte 0", string([]byte{0}), "")
msgList = addMETri(msgList, "byte 'a'", string([]byte{'a'}), "a") msgList = addMETri(msgList, "byte 'a'", string([]byte{'a'}), "a")
//msgList = addMETri(msgList, "byte 255", string([]byte{255}), "") //msgList = addMETri(msgList, "byte 255", string([]byte{255}), "")