| 1 | package qq |
| 2 | |
| 3 | import ( |
| 4 | "context" |
| 5 | "errors" |
| 6 | "fmt" |
| 7 | ) |
| 8 | |
| 9 | // QQ rejects Identify when it contains an intent the bot is not authorized |
| 10 | // for. Try the private-guild profile first to preserve MESSAGE_CREATE support, |
| 11 | // then remember a public-guild fallback for this adapter lifetime if rejected. |
| 12 | const ( |
| 13 | intentGuilds = 1 << 0 |
| 14 | intentGuildMembers = 1 << 1 |
| 15 | intentPrivateGuildMessages = 1 << 9 |
| 16 | intentDirectMessage = 1 << 12 |
| 17 | intentGroupAndC2C = 1 << 25 |
| 18 | intentPublicGuildMessages = 1 << 30 |
| 19 | |
| 20 | qqSharedIdentifyIntents = intentGuilds | intentGuildMembers | intentDirectMessage | intentGroupAndC2C |
| 21 | qqPrivateIdentifyIntents = qqSharedIdentifyIntents | intentPrivateGuildMessages |
| 22 | qqPublicIdentifyIntents = qqSharedIdentifyIntents | intentPublicGuildMessages |
| 23 | ) |
| 24 | |
| 25 | var errQQIdentifyRejected = errors.New("qq gateway identify rejected") |
| 26 | |
| 27 | func connectQQGatewayWithIntentFallback(ctx context.Context, token string, selected *int, connect func(context.Context, string, int) error, onFallback func()) (bool, error) { |
| 28 | err := connect(ctx, token, *selected) |
| 29 | if *selected != qqPrivateIdentifyIntents || !errors.Is(err, errQQIdentifyRejected) { |
| 30 | return false, err |
| 31 | } |
| 32 | if ctx.Err() != nil { |
| 33 | return false, ctx.Err() |
| 34 | } |
| 35 | *selected = qqPublicIdentifyIntents |
| 36 | if onFallback != nil { |
| 37 | onFallback() |
| 38 | } |
| 39 | return true, connect(ctx, token, *selected) |
| 40 | } |
| 41 | |
| 42 | func validateQQReadyPayload(msg gatewayPayload) error { |
| 43 | if msg.Op == opInvalid { |
| 44 | return fmt.Errorf("%w: op=%d", errQQIdentifyRejected, msg.Op) |
| 45 | } |
| 46 | if msg.Op != opDispatch || msg.T != "READY" { |
| 47 | return fmt.Errorf("expected op=%d READY, got op=%d event=%q", opDispatch, msg.Op, msg.T) |
| 48 | } |
| 49 | return nil |
| 50 | } |
| 51 |