package services import ( "aim-oscar/aimerror" "aim-oscar/models" "aim-oscar/oscar" "aim-oscar/util" "context" "time" "github.com/pkg/errors" "github.com/uptrace/bun" ) var ServiceVersions map[uint16]uint16 func init() { ServiceVersions = make(map[uint16]uint16) ServiceVersions[1] = 3 ServiceVersions[2] = 1 ServiceVersions[3] = 1 ServiceVersions[4] = 1 ServiceVersions[17] = 1 } type GenericServiceControls struct { OnlineCh chan *models.User ServerHostname string } func (g *GenericServiceControls) HandleSNAC(ctx context.Context, db *bun.DB, snac *oscar.SNAC) (context.Context, error) { session, _ := oscar.SessionFromContext(ctx) switch snac.Header.Subtype { // Client is ONLINE and READY case 0x02: user := models.UserFromContext(ctx) if user != nil { user.Status = models.UserStatusOnline if err := user.Update(ctx, db); err != nil { return ctx, errors.Wrap(err, "could not set user as active") } g.OnlineCh <- user // Find all of the buddies that are online and tell the user var buddies []*models.Buddy err := db.NewSelect().Model(&buddies).Where("with_uin = ?", user.UIN).Relation("Source").Scan(context.Background(), &buddies) if err != nil { return ctx, errors.Wrapf(err, "could not find user's buddies: %s", err.Error()) } for _, buddy := range buddies { if buddy.Source.Status != models.UserStatusOnline { continue } onlineSnac := oscar.NewSNAC(3, 0xb) onlineSnac.Data.WriteLPString(buddy.Source.ScreenName) onlineSnac.Data.WriteUint16(0) // TODO: user warning level tlvs := []*oscar.TLV{ oscar.NewTLV(1, util.Word(0)), // TODO: user class oscar.NewTLV(0x06, util.Dword(uint32(user.Status))), // TODO: User Status // oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(g.ServerHostname)))), // todo: External IP of the client? 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(0x05, util.Dword(uint32(user.CreatedAt.Unix()))), // Member since } onlineSnac.Data.WriteUint16(uint16(len(tlvs))) for _, tlv := range tlvs { onlineSnac.Data.WriteBinary(tlv) } onlineFlap := oscar.NewFLAP(2) onlineFlap.Data.WriteBinary(onlineSnac) if err := session.Send(onlineFlap); err != nil { return ctx, errors.Wrapf(err, "could not tell %s that %s is online", buddy.Source.ScreenName, buddy.Target.ScreenName) } } return models.NewContextWithUser(ctx, user), nil } return ctx, nil // Client wants to know the rate limits for all services case 0x06: rateSnac := oscar.NewSNAC(1, 7) rateSnac.Data.WriteUint16(1) // one rate class // Define a Rate Class rc := oscar.Buffer{} rc.WriteUint16(1) // ID rc.WriteUint32(80) // Window Size rc.WriteUint32(2500) // Clear level rc.WriteUint32(2000) // Alert level rc.WriteUint32(1500) // Limit level rc.WriteUint32(800) // Disconnect level rc.WriteUint32(3400) // Current level (fake) rc.WriteUint32(6000) // Max level rc.WriteUint32(0) // Last time ? rc.WriteUint8(0) // Current state ? rateSnac.Data.Write(rc.Bytes()) // Define a Rate Group rg := oscar.Buffer{} rg.WriteUint16(1) // ID // 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. rg.WriteUint16(uint16(len(ServiceVersions)) * 0x21) // Number of rate groups for family := range ServiceVersions { for subtype := 0; subtype < 0x21; subtype++ { rg.WriteUint16(family) rg.WriteUint16(uint16(subtype)) } } rateSnac.Data.Write(rg.Bytes()) rateFlap := oscar.NewFLAP(2) rateFlap.Data.WriteBinary(rateSnac) return ctx, session.Send(rateFlap) // Client wants their own online information case 0x0e: user := models.UserFromContext(ctx) if user == nil { return ctx, aimerror.NoUserInSession } onlineSnac := oscar.NewSNAC(1, 0xf) onlineSnac.Data.WriteUint8(uint8(len(user.ScreenName))) onlineSnac.Data.WriteString(user.ScreenName) onlineSnac.Data.WriteUint16(0) // warning level user.Status = models.UserStatusOnline if err := user.Update(ctx, db); err != nil { return ctx, errors.Wrap(err, "could not set user as active") } tlvs := []*oscar.TLV{ oscar.NewTLV(0x01, util.Dword(0)), // User Class oscar.NewTLV(0x06, util.Dword(uint32(user.Status))), // TODO: User Status // oscar.NewTLV(0x0a, util.Dword(binary.BigEndian.Uint32([]byte(g.ServerHostname)))), // External IP of the client? 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(0x01e, util.Dword(0x0)), // Unknown value oscar.NewTLV(0x05, util.Dword(uint32(user.CreatedAt.Unix()))), // Member since } onlineSnac.Data.WriteUint16(uint16(len(tlvs))) for _, tlv := range tlvs { onlineSnac.Data.WriteBinary(tlv) } onlineFlap := oscar.NewFLAP(2) onlineFlap.Data.WriteBinary(onlineSnac) return models.NewContextWithUser(ctx, user), session.Send(onlineFlap) // Client wants to know the ServiceVersions of all of the services offered case 0x17: versionsSnac := oscar.NewSNAC(1, 0x18) for family, version := range ServiceVersions { versionsSnac.Data.WriteUint16(family) versionsSnac.Data.WriteUint16(version) } versionsFlap := oscar.NewFLAP(2) versionsFlap.Data.WriteBinary(versionsSnac) return ctx, session.Send(versionsFlap) } return ctx, nil }