mirror of
https://github.com/amigan/aim-oscar-server.git
synced 2024-11-21 20:19:47 -05:00
Location Services
This commit is contained in:
parent
9dc97738b3
commit
b660db4462
9 changed files with 242 additions and 86 deletions
|
@ -18,6 +18,7 @@ var versions map[uint16]uint16
|
||||||
func init() {
|
func init() {
|
||||||
versions = make(map[uint16]uint16)
|
versions = make(map[uint16]uint16)
|
||||||
versions[1] = 3
|
versions[1] = 3
|
||||||
|
versions[2] = 1
|
||||||
versions[3] = 1
|
versions[3] = 1
|
||||||
versions[4] = 1
|
versions[4] = 1
|
||||||
versions[17] = 1
|
versions[17] = 1
|
||||||
|
@ -36,7 +37,7 @@ func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, sna
|
||||||
case 0x02:
|
case 0x02:
|
||||||
user := models.UserFromContext(ctx)
|
user := models.UserFromContext(ctx)
|
||||||
if user != nil {
|
if user != nil {
|
||||||
user.Status = models.UserStatusActive
|
user.Status = models.UserStatusOnline
|
||||||
if err := user.Update(ctx, db); err != nil {
|
if err := user.Update(ctx, db); err != nil {
|
||||||
return ctx, errors.Wrap(err, "could not set user as active")
|
return ctx, errors.Wrap(err, "could not set user as active")
|
||||||
}
|
}
|
||||||
|
@ -51,7 +52,7 @@ func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, sna
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, buddy := range buddies {
|
for _, buddy := range buddies {
|
||||||
if buddy.Source.Status != models.UserStatusActive {
|
if buddy.Source.Status != models.UserStatusOnline {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +62,7 @@ func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, sna
|
||||||
|
|
||||||
tlvs := []*oscar.TLV{
|
tlvs := []*oscar.TLV{
|
||||||
oscar.NewTLV(1, util.Word(0)), // TODO: user class
|
oscar.NewTLV(1, util.Word(0)), // TODO: user class
|
||||||
oscar.NewTLV(0x06, util.Dword(0x50)), // TODO: User Status
|
oscar.NewTLV(0x06, util.Dword(uint32(user.Status))), // TODO: User Status
|
||||||
oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(SRV_HOST)))), // External IP
|
oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(SRV_HOST)))), // External IP
|
||||||
oscar.NewTLV(0x0f, util.Dword(uint32(time.Since(user.LastActivityAt).Seconds()))), // Idle Time
|
oscar.NewTLV(0x0f, util.Dword(uint32(time.Since(user.LastActivityAt).Seconds()))), // Idle Time
|
||||||
oscar.NewTLV(0x03, util.Dword(uint32(time.Now().Unix()))), // Client Signon Time
|
oscar.NewTLV(0x03, util.Dword(uint32(time.Now().Unix()))), // Client Signon Time
|
||||||
|
@ -110,7 +111,7 @@ func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, sna
|
||||||
|
|
||||||
// TODO: make actual rate groups instead of this hack. I can't tell which subtypes are supported so
|
// TODO: make actual rate groups instead of this hack. I can't tell which subtypes are supported so
|
||||||
// make it set rate limits for everything family for all subtypes under 0x21.
|
// make it set rate limits for everything family for all subtypes under 0x21.
|
||||||
rg.WriteUint16(3 * 0x21) // Number of rate groups
|
rg.WriteUint16(uint16(len(versions)) * 0x21) // Number of rate groups
|
||||||
for family := range versions {
|
for family := range versions {
|
||||||
for subtype := 0; subtype < 0x21; subtype++ {
|
for subtype := 0; subtype < 0x21; subtype++ {
|
||||||
rg.WriteUint16(family)
|
rg.WriteUint16(family)
|
||||||
|
@ -135,14 +136,14 @@ func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, sna
|
||||||
onlineSnac.Data.WriteString(user.Username)
|
onlineSnac.Data.WriteString(user.Username)
|
||||||
onlineSnac.Data.WriteUint16(0) // warning level
|
onlineSnac.Data.WriteUint16(0) // warning level
|
||||||
|
|
||||||
user.Status = models.UserStatusActive
|
user.Status = models.UserStatusOnline
|
||||||
if err := user.Update(ctx, db); err != nil {
|
if err := user.Update(ctx, db); err != nil {
|
||||||
return ctx, errors.Wrap(err, "could not set user as active")
|
return ctx, errors.Wrap(err, "could not set user as active")
|
||||||
}
|
}
|
||||||
|
|
||||||
tlvs := []*oscar.TLV{
|
tlvs := []*oscar.TLV{
|
||||||
oscar.NewTLV(0x01, util.Dword(0x80)), // User Class
|
oscar.NewTLV(0x01, util.Dword(0)), // User Class
|
||||||
oscar.NewTLV(0x06, util.Dword(0x50)), // TODO: User Status
|
oscar.NewTLV(0x06, util.Dword(uint32(user.Status))), // TODO: User Status
|
||||||
oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(SRV_HOST)))), // External IP
|
oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(SRV_HOST)))), // External IP
|
||||||
oscar.NewTLV(0x0f, util.Dword(uint32(time.Since(user.LastActivityAt).Seconds()))), // Idle Time
|
oscar.NewTLV(0x0f, util.Dword(uint32(time.Since(user.LastActivityAt).Seconds()))), // Idle Time
|
||||||
oscar.NewTLV(0x03, util.Dword(uint32(time.Now().Unix()))), // Client Signon Time
|
oscar.NewTLV(0x03, util.Dword(uint32(time.Now().Unix()))), // Client Signon Time
|
||||||
|
|
154
0x02_location_services.go
Normal file
154
0x02_location_services.go
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"aim-oscar/aimerror"
|
||||||
|
"aim-oscar/models"
|
||||||
|
"aim-oscar/oscar"
|
||||||
|
"aim-oscar/util"
|
||||||
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/uptrace/bun"
|
||||||
|
)
|
||||||
|
|
||||||
|
type LocationServices struct {
|
||||||
|
OnlineCh chan *models.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *LocationServices) HandleSNAC(ctx context.Context, db *bun.DB, snac *oscar.SNAC) (context.Context, error) {
|
||||||
|
session, _ := oscar.SessionFromContext(ctx)
|
||||||
|
|
||||||
|
switch snac.Header.Subtype {
|
||||||
|
|
||||||
|
// Client wants to know the limits/permissions for Location services
|
||||||
|
case 0x02:
|
||||||
|
paramsSnac := oscar.NewSNAC(2, 3)
|
||||||
|
paramsSnac.Data.WriteBinary(oscar.NewTLV(1, util.Word(256))) // Max profile length TODO: error if user sends more
|
||||||
|
|
||||||
|
paramsFlap := oscar.NewFLAP(2)
|
||||||
|
paramsFlap.Data.WriteBinary(paramsSnac)
|
||||||
|
|
||||||
|
return ctx, session.Send(paramsFlap)
|
||||||
|
|
||||||
|
// Client set profile/away message
|
||||||
|
case 0x04:
|
||||||
|
user := models.UserFromContext(ctx)
|
||||||
|
if user == nil {
|
||||||
|
return ctx, aimerror.NoUserInSession
|
||||||
|
}
|
||||||
|
|
||||||
|
tlvs, err := oscar.UnmarshalTLVs(snac.Data.Bytes())
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "authentication request missing TLVs")
|
||||||
|
}
|
||||||
|
|
||||||
|
awayMessageTLV := oscar.FindTLV(tlvs, 0x4)
|
||||||
|
if awayMessageTLV != nil {
|
||||||
|
// Away message encoding is set in TLV 0x3
|
||||||
|
awayMessageMimeTLV := oscar.FindTLV(tlvs, 0x3)
|
||||||
|
if awayMessageMimeTLV == nil {
|
||||||
|
return nil, errors.New("missing away message mime TLV 0x3")
|
||||||
|
}
|
||||||
|
user.AwayMessage = string(awayMessageTLV.Data)
|
||||||
|
user.AwayMessageEncoding = string(awayMessageMimeTLV.Data)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
profileTLV := oscar.FindTLV(tlvs, 0x2)
|
||||||
|
if profileTLV != nil {
|
||||||
|
profileMimeTLV := oscar.FindTLV(tlvs, 0x1)
|
||||||
|
if profileMimeTLV == nil {
|
||||||
|
return nil, errors.New("missing away message mime TLV 0x3")
|
||||||
|
}
|
||||||
|
user.Profile = string(profileTLV.Data)
|
||||||
|
user.ProfileEncoding = string(profileMimeTLV.Data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if user.AwayMessage == "" {
|
||||||
|
user.Status = models.UserStatusOnline
|
||||||
|
} else {
|
||||||
|
user.Status = models.UserStatusAway
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := user.Update(ctx, db); err != nil {
|
||||||
|
return ctx, errors.Wrap(err, "could not set away message")
|
||||||
|
}
|
||||||
|
|
||||||
|
s.OnlineCh <- user
|
||||||
|
|
||||||
|
return models.NewContextWithUser(ctx, user), nil
|
||||||
|
|
||||||
|
// Client is asking for user information like profile, away message, online state
|
||||||
|
case 0x5:
|
||||||
|
requestType, err := snac.Data.ReadUint16()
|
||||||
|
if err != nil {
|
||||||
|
return ctx, errors.Wrap(err, "missing request type")
|
||||||
|
}
|
||||||
|
|
||||||
|
requestedUsername, err := snac.Data.ReadLPString()
|
||||||
|
if err != nil {
|
||||||
|
return ctx, errors.Wrap(err, "missing requested username")
|
||||||
|
}
|
||||||
|
|
||||||
|
requestedUser, err := models.UserByUsername(ctx, db, requestedUsername)
|
||||||
|
if err != nil {
|
||||||
|
return ctx, aimerror.FetchingUser(err, requestedUsername)
|
||||||
|
}
|
||||||
|
|
||||||
|
respSnac := oscar.NewSNAC(2, 6)
|
||||||
|
respSnac.Data.WriteLPString(requestedUser.Username)
|
||||||
|
respSnac.Data.WriteUint16(0) // TODO: warning level
|
||||||
|
|
||||||
|
tlvs := []*oscar.TLV{
|
||||||
|
oscar.NewTLV(1, util.Dword(0x80)), // user class
|
||||||
|
// oscar.NewTLV(6, util.Dword(uint32(requestedUser.Status))), // user status
|
||||||
|
// oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(SRV_HOST)))), // user external IP
|
||||||
|
oscar.NewTLV(0x0f, util.Dword(uint32(time.Since(requestedUser.LastActivityAt).Seconds()))), // idle time
|
||||||
|
oscar.NewTLV(0x03, util.Dword(uint32(time.Now().Unix()))), // TODO: signon time
|
||||||
|
// oscar.NewTLV(0x05, util.Dword(uint32(requestedUser.CreatedAt.Unix()))), // member since
|
||||||
|
}
|
||||||
|
|
||||||
|
respSnac.Data.WriteUint16(uint16(len(tlvs))) // number of TLVs
|
||||||
|
for _, tlv := range tlvs {
|
||||||
|
respSnac.Data.WriteBinary(tlv)
|
||||||
|
}
|
||||||
|
|
||||||
|
// General info (Profile)
|
||||||
|
if requestType == 1 {
|
||||||
|
respSnac.Data.WriteBinary(oscar.NewTLV(1, util.LPString(requestedUser.ProfileEncoding)))
|
||||||
|
respSnac.Data.WriteBinary(oscar.NewTLV(2, util.LPString(requestedUser.Profile)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Request Type 2 = online status, no TLVs
|
||||||
|
|
||||||
|
// Away message
|
||||||
|
if requestType == 3 {
|
||||||
|
respSnac.Data.WriteBinary(oscar.NewTLV(3, util.LPString(requestedUser.AwayMessageEncoding)))
|
||||||
|
respSnac.Data.WriteBinary(oscar.NewTLV(4, util.LPString(requestedUser.AwayMessage)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Request Type 4 - User capabilities
|
||||||
|
|
||||||
|
respFlap := oscar.NewFLAP(2)
|
||||||
|
respFlap.Data.WriteBinary(respSnac)
|
||||||
|
|
||||||
|
return ctx, session.Send(respFlap)
|
||||||
|
|
||||||
|
case 0xb:
|
||||||
|
/* Nobody seems to know what this client request is for
|
||||||
|
- http://iserverd.khstu.ru/oscar/snac_02_0b.html
|
||||||
|
- https://bugs.bitlbee.org/browser/protocols/oscar/info.c?rev=b7d3cc34f68dab7b8f7d0777711317b334fc2219#L572
|
||||||
|
|
||||||
|
But the one dump that exists looks like a TLV 0x1 with empty data
|
||||||
|
*/
|
||||||
|
unknownSnac := oscar.NewSNAC(2, 0xc)
|
||||||
|
unknownSnac.Data.WriteUint16(1)
|
||||||
|
unknownSnac.Data.WriteUint16(0)
|
||||||
|
unknownFlap := oscar.NewFLAP(2)
|
||||||
|
unknownFlap.Data.WriteBinary(unknownSnac)
|
||||||
|
return ctx, session.Send(unknownFlap)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ctx, nil
|
||||||
|
}
|
|
@ -87,7 +87,7 @@ func (b *BuddyListManagement) HandleSNAC(ctx context.Context, db *bun.DB, snac *
|
||||||
return ctx, aimerror.UserNotFound(buddyScreename)
|
return ctx, aimerror.UserNotFound(buddyScreename)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.NewDelete().Model((*models.Buddy)(nil)).Where("uin = ?", user.UIN).Where("with_uin = ?", buddy.UIN).Exec(ctx)
|
_, err = db.NewDelete().Model((*models.Buddy)(nil)).Where("source_uin = ?", user.UIN).Where("with_uin = ?", buddy.UIN).Exec(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ctx, err
|
return ctx, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,12 @@ package aimerror
|
||||||
|
|
||||||
import "github.com/pkg/errors"
|
import "github.com/pkg/errors"
|
||||||
|
|
||||||
func UserNotFound(screenname string) error {
|
func FetchingUser(err error, username string) error {
|
||||||
return errors.Errorf("no user with UIN %s", screenname)
|
return errors.Wrapf(err, "could not fetch user with username %s", username)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UserNotFound(username string) error {
|
||||||
|
return errors.Errorf("no user with UIN %s", username)
|
||||||
}
|
}
|
||||||
|
|
||||||
var NoUserInSession = errors.New("no user in session")
|
var NoUserInSession = errors.New("no user in session")
|
||||||
|
|
8
main.go
8
main.go
|
@ -99,14 +99,17 @@ func main() {
|
||||||
|
|
||||||
user := models.UserFromContext(ctx)
|
user := models.UserFromContext(ctx)
|
||||||
if user != nil {
|
if user != nil {
|
||||||
user.Status = models.UserStatusInactive
|
// tellBuddies := user.Status != models.UserStatusAway
|
||||||
|
user.Status = models.UserStatusAway
|
||||||
if err := user.Update(ctx, db); err != nil {
|
if err := user.Update(ctx, db); err != nil {
|
||||||
log.Print(errors.Wrap(err, "could not set user as inactive"))
|
log.Print(errors.Wrap(err, "could not set user as inactive"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if true {
|
||||||
onlineCh <- user
|
onlineCh <- user
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
handleFn := func(ctx context.Context, flap *oscar.FLAP) context.Context {
|
handleFn := func(ctx context.Context, flap *oscar.FLAP) context.Context {
|
||||||
session, err := oscar.SessionFromContext(ctx)
|
session, err := oscar.SessionFromContext(ctx)
|
||||||
|
@ -176,10 +179,11 @@ func main() {
|
||||||
|
|
||||||
handler := oscar.NewHandler(handleFn, handleCloseFn)
|
handler := oscar.NewHandler(handleFn, handleCloseFn)
|
||||||
|
|
||||||
RegisterService(0x17, &AuthorizationRegistrationService{})
|
|
||||||
RegisterService(0x01, &GenericServiceControls{OnlineCh: onlineCh})
|
RegisterService(0x01, &GenericServiceControls{OnlineCh: onlineCh})
|
||||||
|
RegisterService(0x02, &LocationServices{OnlineCh: onlineCh})
|
||||||
RegisterService(0x03, &BuddyListManagement{})
|
RegisterService(0x03, &BuddyListManagement{})
|
||||||
RegisterService(0x04, &ICBM{CommCh: commCh})
|
RegisterService(0x04, &ICBM{CommCh: commCh})
|
||||||
|
RegisterService(0x17, &AuthorizationRegistrationService{})
|
||||||
|
|
||||||
exitChan := make(chan os.Signal, 1)
|
exitChan := make(chan os.Signal, 1)
|
||||||
signal.Notify(exitChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGABRT)
|
signal.Notify(exitChan, os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGABRT)
|
||||||
|
|
|
@ -12,8 +12,13 @@ import (
|
||||||
type UserStatus int16
|
type UserStatus int16
|
||||||
|
|
||||||
const (
|
const (
|
||||||
UserStatusInactive UserStatus = iota
|
UserStatusOnline = 0
|
||||||
UserStatusActive
|
UserStatusAway = 1
|
||||||
|
UserStatusDnd = 2
|
||||||
|
UserStatusNA = 4
|
||||||
|
UserStatusOccupied = 0x10
|
||||||
|
UserStatusFree4Chat = 0x20
|
||||||
|
UserStatusInvisible = 0x100
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
|
@ -26,6 +31,10 @@ type User struct {
|
||||||
CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
|
CreatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
|
||||||
UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
|
UpdatedAt time.Time `bun:",nullzero,notnull,default:current_timestamp"`
|
||||||
Status UserStatus
|
Status UserStatus
|
||||||
|
Profile string
|
||||||
|
ProfileEncoding string
|
||||||
|
AwayMessage string
|
||||||
|
AwayMessageEncoding string
|
||||||
LastActivityAt time.Time `bin:"-"`
|
LastActivityAt time.Time `bin:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,8 +11,4 @@
|
||||||
- model: Message
|
- model: Message
|
||||||
rows: []
|
rows: []
|
||||||
- model: Buddy
|
- model: Buddy
|
||||||
rows:
|
rows: []
|
||||||
- source_uin: 1
|
|
||||||
with_uin: 2
|
|
||||||
- source_uin: 2
|
|
||||||
with_uin: 1
|
|
||||||
|
|
|
@ -6,7 +6,6 @@ import (
|
||||||
"aim-oscar/util"
|
"aim-oscar/util"
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"fmt"
|
|
||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -26,17 +25,28 @@ func OnlineNotification() (chan *models.User, routineFn) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if user.Status == models.UserStatusActive {
|
if user.Status == models.UserStatusOnline {
|
||||||
fmt.Printf("%s is online", user.Username)
|
log.Printf("%s is now online", user.Username)
|
||||||
|
} else if user.Status == models.UserStatusAway {
|
||||||
|
log.Printf("%s is now away", user.Username)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Find buddies who are friends with the user
|
||||||
var buddies []*models.Buddy
|
var buddies []*models.Buddy
|
||||||
err := db.NewSelect().Model(&buddies).Where("with_uin = ?", user.UIN).Relation("Source").Relation("Target").Scan(context.Background(), &buddies)
|
err := db.NewSelect().Model(&buddies).Where("with_uin = ?", user.UIN).Relation("Source").Scan(ctx, &buddies)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("could not find user's buddies: %s", err.Error())
|
log.Printf("could not find user's buddies: %s", err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, buddy := range buddies {
|
for _, buddy := range buddies {
|
||||||
|
if buddy.Source.Status == models.UserStatusAway || buddy.Source.Status == models.UserStatusDnd {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
log.Printf("telling %s that %s has a new status: %d!", buddy.Source.Username, user.Username, user.Status)
|
||||||
|
|
||||||
if s := getSession(buddy.Source.Username); s != nil {
|
if s := getSession(buddy.Source.Username); s != nil {
|
||||||
onlineSnac := oscar.NewSNAC(3, 0xb)
|
onlineSnac := oscar.NewSNAC(3, 0xb)
|
||||||
onlineSnac.Data.WriteLPString(user.Username)
|
onlineSnac.Data.WriteLPString(user.Username)
|
||||||
|
@ -44,7 +54,7 @@ func OnlineNotification() (chan *models.User, routineFn) {
|
||||||
|
|
||||||
tlvs := []*oscar.TLV{
|
tlvs := []*oscar.TLV{
|
||||||
oscar.NewTLV(1, util.Word(0)), // TODO: user class
|
oscar.NewTLV(1, util.Word(0)), // TODO: user class
|
||||||
oscar.NewTLV(0x06, util.Dword(0x50)), // TODO: User Status
|
oscar.NewTLV(0x06, util.Dword(uint32(user.Status))), // TODO: User Status
|
||||||
oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(SRV_HOST)))), // External IP
|
oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(SRV_HOST)))), // External IP
|
||||||
oscar.NewTLV(0x0f, util.Dword(uint32(time.Since(user.LastActivityAt).Seconds()))), // Idle Time
|
oscar.NewTLV(0x0f, util.Dword(uint32(time.Since(user.LastActivityAt).Seconds()))), // Idle Time
|
||||||
oscar.NewTLV(0x03, util.Dword(uint32(time.Now().Unix()))), // Client Signon Time
|
oscar.NewTLV(0x03, util.Dword(uint32(time.Now().Unix()))), // Client Signon Time
|
||||||
|
@ -59,33 +69,7 @@ func OnlineNotification() (chan *models.User, routineFn) {
|
||||||
onlineFlap := oscar.NewFLAP(2)
|
onlineFlap := oscar.NewFLAP(2)
|
||||||
onlineFlap.Data.WriteBinary(onlineSnac)
|
onlineFlap.Data.WriteBinary(onlineSnac)
|
||||||
if err := s.Send(onlineFlap); err != nil {
|
if err := s.Send(onlineFlap); err != nil {
|
||||||
log.Printf("could not tell %s that %s is online", buddy.Source.Username, buddy.Target.Username)
|
log.Printf("could not tell %s that %s is online", buddy.Source.Username, user.Username)
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if user.Status == models.UserStatusInactive {
|
|
||||||
var buddies []*models.Buddy
|
|
||||||
err := db.NewSelect().Model(&buddies).Where("with_uin = ?", user.UIN).Relation("Source").Relation("Target").Scan(context.Background(), &buddies)
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("could not find user's buddies: %s", err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, buddy := range buddies {
|
|
||||||
if s := getSession(buddy.Source.Username); s != nil {
|
|
||||||
offlineSnac := oscar.NewSNAC(3, 0xb)
|
|
||||||
offlineSnac.Data.WriteLPString(user.Username)
|
|
||||||
offlineSnac.Data.WriteUint16(0) // TODO: user warning level
|
|
||||||
offlineSnac.Data.WriteUint16(1)
|
|
||||||
offlineSnac.Data.WriteBinary(oscar.NewTLV(1, util.Dword(0x80)))
|
|
||||||
|
|
||||||
offlineFlap := oscar.NewFLAP(2)
|
|
||||||
offlineFlap.Data.WriteBinary(offlineSnac)
|
|
||||||
if err := s.Send(offlineFlap); err != nil {
|
|
||||||
log.Printf("could not tell %s that %s is offline", buddy.Source.Username, buddy.Target.Username)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,3 +60,7 @@ func Word(x uint16) []byte {
|
||||||
func Dword(x uint32) []byte {
|
func Dword(x uint32) []byte {
|
||||||
return []byte{byte(x >> 24), byte(x >> 16), byte(x >> 8), byte(x & 0xf)}
|
return []byte{byte(x >> 24), byte(x >> 16), byte(x >> 8), byte(x & 0xf)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func LPString(x string) []byte {
|
||||||
|
return append(Word(uint16(len(x))), []byte(x)...)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue