Documentation
¶
Overview ¶
Package teams is a Microsoft Teams SDK over Microsoft Graph.
It owns Teams semantics -- paths, request shapes, and the chat and channel message models -- and delegates transport, retries, paging, and error classification to msgraph-go. It does not do auth: build a github.com/albertocavalcante/msgraph-go.Client with a token source, then wrap it with New.
Three Graph behaviors shape the API. First, a $filter on a chat message listing is silently discarded unless $orderby names the same property -- no error, the whole unfiltered collection comes back instead. Second, messageType is an evolvable enum: without a Prefer header asking Graph to include unknown enum members, every system event message -- a member joining, a chat renamed, a call ending -- arrives as unknownFutureValue, which in a real conversation's history is a large minority of it. New sets that header on every request, with no option to turn it off. Third, from is null on system event messages: the event has no author in the ordinary sense, and code that assumes otherwise renders a blank sender for a message that was never really "from" anyone.
Index ¶
- Constants
- Variables
- func Granted(scopes []string, capability Capability) bool
- func IsForbidden(err error) bool
- func IsMessagingPolicyBlocked(err error) bool
- func IsNotFound(err error) bool
- func IsReservedScope(scope string) bool
- func IsThrottled(err error) bool
- func IsUnauthorized(err error) bool
- func LeastPrivilegedScope(capability Capability) (string, bool)
- func MostPrivilegedScope(capability Capability) (string, bool)
- func NeedsAdminConsent(capability Capability) bool
- func Require(scopes []string, capability Capability) error
- func ScopesFor(capability Capability) []string
- func ScopesToRequest(granted []string, capabilities ...Capability) []string
- type Activity
- type Availability
- type BodyType
- type Capability
- type Channel
- type ChannelID
- type ChannelIdentity
- type ChannelMembership
- type ChannelMessageQuery
- type ChannelQuery
- type Chat
- type ChatID
- type ChatMessage
- type ChatQuery
- type ChatType
- type ChatViewpoint
- type Client
- func (c *Client) ChannelMembers(ctx context.Context, team TeamID, channel ChannelID) iter.Seq2[ConversationMember, error]
- func (c *Client) ChannelMessages(ctx context.Context, team TeamID, channel ChannelID, query ChannelMessageQuery) iter.Seq2[ChatMessage, error]
- func (c *Client) Channels(ctx context.Context, team TeamID, query ChannelQuery) iter.Seq2[Channel, error]
- func (c *Client) ChatMembers(ctx context.Context, chat ChatID) iter.Seq2[ConversationMember, error]
- func (c *Client) Chats(ctx context.Context, query ChatQuery) iter.Seq2[Chat, error]
- func (c *Client) GetChannel(ctx context.Context, team TeamID, channel ChannelID) (Channel, *msgraph.Response, error)
- func (c *Client) GetChat(ctx context.Context, chat ChatID, query ChatQuery) (Chat, *msgraph.Response, error)
- func (c *Client) GetMessage(ctx context.Context, chat ChatID, message MessageID) (ChatMessage, *msgraph.Response, error)
- func (c *Client) GetTeam(ctx context.Context, team TeamID) (Team, *msgraph.Response, error)
- func (c *Client) Graph() *msgraph.Client
- func (c *Client) JoinedTeams(ctx context.Context) iter.Seq2[Team, error]
- func (c *Client) ListChats(ctx context.Context, query ChatQuery) (msgraph.Page[Chat], *msgraph.Response, error)
- func (c *Client) ListJoinedTeams(ctx context.Context) (msgraph.Page[Team], *msgraph.Response, error)
- func (c *Client) ListMessages(ctx context.Context, chat ChatID, query MessageQuery) (msgraph.Page[ChatMessage], *msgraph.Response, error)
- func (c *Client) Messages(ctx context.Context, chat ChatID, query MessageQuery) iter.Seq2[ChatMessage, error]
- func (c *Client) MoreReplies(ctx context.Context, message ChatMessage) iter.Seq2[ChatMessage, error]
- func (c *Client) MyPresence(ctx context.Context) (Presence, *msgraph.Response, error)
- func (c *Client) Replies(ctx context.Context, team TeamID, channel ChannelID, message MessageID, ...) iter.Seq2[ChatMessage, error]
- func (c *Client) ReplyToChannelMessage(ctx context.Context, team TeamID, channel ChannelID, message MessageID, ...) (ChatMessage, *msgraph.Response, error)
- func (c *Client) SearchMessages(ctx context.Context, query SearchQuery) iter.Seq2[SearchHit, error]
- func (c *Client) SearchPage(ctx context.Context, query SearchQuery) (hits []SearchHit, more bool, resp *msgraph.Response, err error)
- func (c *Client) Self() UserID
- func (c *Client) SendChannelMessage(ctx context.Context, team TeamID, channel ChannelID, draft Draft) (ChatMessage, *msgraph.Response, error)
- func (c *Client) SendMessage(ctx context.Context, chat ChatID, draft Draft) (ChatMessage, *msgraph.Response, error)
- func (c *Client) SetReaction(ctx context.Context, chat ChatID, message MessageID, reaction ReactionType) (*msgraph.Response, error)
- func (c *Client) SoftDeleteMessage(ctx context.Context, chat ChatID, message MessageID) (*msgraph.Response, error)
- func (c *Client) UndoSoftDeleteMessage(ctx context.Context, chat ChatID, message MessageID) (*msgraph.Response, error)
- func (c *Client) UnsetReaction(ctx context.Context, chat ChatID, message MessageID, reaction ReactionType) (*msgraph.Response, error)
- func (c *Client) UserPresence(ctx context.Context, user UserID) (Presence, *msgraph.Response, error)
- type ConversationMember
- type Draft
- type ETag
- type ErrMissingCapability
- type EventDetail
- func (d *EventDetail) CallDuration() (time.Duration, bool)
- func (d *EventDetail) Decode(into any) error
- func (d *EventDetail) Known() bool
- func (d EventDetail) MarshalJSON() ([]byte, error)
- func (d *EventDetail) MembersAdded() ([]Identity, bool)
- func (d *EventDetail) MembersRemoved() ([]Identity, bool)
- func (d *EventDetail) NewTopic() (string, bool)
- func (d *EventDetail) Summary() (EventSummary, bool)
- func (d *EventDetail) UnmarshalJSON(data []byte) error
- type EventDetailType
- type EventSummary
- type HostedContentID
- type HostedContentRef
- type Identity
- type IdentityKind
- type IdentitySet
- type IdentityType
- type Importance
- type ItemBody
- type MemberRole
- type Mention
- type MentionedIdentitySet
- type MessageAttachment
- type MessageID
- type MessageOrder
- type MessagePreview
- type MessageQuery
- type MessageType
- type OnlineMeetingInfo
- type Option
- type OutOfOfficeSettings
- type Presence
- type PresenceExpiry
- type PresenceStatusMessage
- type Reaction
- type ReactionType
- type SearchHit
- type SearchQuery
- type Team
- type TeamID
- type TeamVisibility
- type UserID
Constants ¶
const ( // MaxBodyDepth bounds how many levels of tag nesting [ChatMessage.PlainText] // tracks before it stops counting further opens. Nesting past the bound // is still stripped like any other tag; only the counter itself saturates, // so a body built from a huge run of open tags cannot grow unbounded // internal state. MaxBodyDepth = 64 // MaxBodyBytes bounds the rendered output of [ItemBody.Text] and // [ChatMessage.PlainText]. Content past the bound is truncated, never // rejected: a caller rendering a preview would rather see the first // megabyte of an oversized message than an error. MaxBodyBytes = 1 << 20 )
Bounds on how much of a message body this package will render as text.
const ( // CodeBadRequest is what a chat or channel message listing answers for an // unsupported $orderby property, an ascending sort, or a $select/$expand // it does not serve. Unlike a mismatched $filter -- which the service // drops silently -- these shapes are genuinely refused. CodeBadRequest = "BadRequest" // CodeInvalidSearchRequest is what /search/query answers for a search // body missing entityTypes, or one that asks for sortProperties on a // chatMessage search, which the service will not sort. CodeInvalidSearchRequest = "invalidRequest" )
Graph error codes Teams returns for conditions worth handling.
const ( // MaxMessagePageSize is the largest page a chat or channel message // listing serves, whatever $top asks for. A request above it is not // refused; it is silently served this many. MaxMessagePageSize = 50 // DefaultChannelMessagePageSize is what a channel message listing // returns when $top says nothing. It is well under // [MaxMessagePageSize], so leaving Top unset is not the same as asking // for the largest page the service will give. DefaultChannelMessagePageSize = 20 )
const ( FieldCreatedDateTime = "createdDateTime" FieldLastModifiedDateTime = "lastModifiedDateTime" // FieldLastMessagePreviewCreated is the property a chat listing orders by // when [ChatQuery.ByRecentMessage] is set. It lives one level into the // expanded lastMessagePreview, which is why [ChatQuery.Params] adds the // expansion automatically rather than leaving a caller to discover that // ordering by it silently does nothing without the expand. FieldLastMessagePreviewCreated = "lastMessagePreview/createdDateTime" )
Message property paths, for naming a sort property without misspelling it into a request the service quietly answers wrong instead of rejecting; see MessageQuery in messages.go.
const MaxChatPageSize = 50
MaxChatPageSize is the largest page the chat listing serves, whatever $top asks for. A request above it is not refused; it is silently served this many.
const MaxSearchPageSize = 25
MaxSearchPageSize is the largest page a message search serves, whatever Size asks for.
Variables ¶
var ( ErrNilGraphClient = errors.New("teams: nil graph client") ErrEmptyID = errors.New("teams: empty identifier") ErrEmptyBody = errors.New("teams: request body has no properties set") )
Errors returned when a call is malformed before it reaches Graph.
var ErrEmptySearchTerm = errors.New("teams: search term is empty")
ErrEmptySearchTerm means a SearchQuery carries no search term. The service accepts an empty queryString and answers with a search over nothing that reads as "no matches" rather than as a malformed request.
var ErrInvertedWindow = errors.New("teams: message window is inverted: Since is after Before")
ErrInvertedWindow means a MessageQuery's Since is after its Before.
The service does not refuse this shape: it answers 200 OK with an empty page, which reads exactly like a quiet chat rather than like a mistake. MessageQuery.Validate exists so the mistake is caught here, with a message that says which end was wrong, instead of silently as "nothing happened here."
Functions ¶
func Granted ¶
func Granted(scopes []string, capability Capability) bool
Granted reports whether the given scopes satisfy a capability.
This delegates to msgraph.ScopeSatisfies rather than a plain membership check, which is what lets a broad grant such as Group.ReadWrite.All stand in for ReadChannelMessages without this package having to repeat the nesting msgraph-go already maintains. Comparison is case-insensitive because the service echoes granted scopes back with inconsistent casing.
func IsForbidden ¶
IsForbidden reports whether the caller lacks the permission the request needed, or the tenant has otherwise refused it outright.
func IsMessagingPolicyBlocked ¶
IsMessagingPolicyBlocked reports whether a send was refused by a tenant messaging policy rather than by a missing permission.
Teams has no error code of its own for this: the response is a plain 403 Forbidden, the same status a missing scope produces, so status alone cannot tell the two apart. The distinguishing text lives in the message, which is why this checks for it -- best-effort, because Microsoft has not documented the message as stable. The distinction still matters: a missing scope is fixed by asking the user to consent to more; a messaging policy is a tenant administrator's choice that the signed-in user cannot act on no matter what they consent to, so retrying after a new sign-in only loops.
func IsNotFound ¶
IsNotFound reports whether the chat, channel, team, or message does not exist.
func IsReservedScope ¶
IsReservedScope reports whether a scope is granted implicitly rather than requested.
func IsThrottled ¶
IsThrottled reports whether Graph rate-limited the request.
func IsUnauthorized ¶
IsUnauthorized reports whether the request carried no usable token.
func LeastPrivilegedScope ¶
func LeastPrivilegedScope(capability Capability) (string, bool)
LeastPrivilegedScope returns the narrowest permission that satisfies a capability, which is what to ask for when adding one.
func MostPrivilegedScope ¶
func MostPrivilegedScope(capability Capability) (string, bool)
MostPrivilegedScope returns the broadest permission that satisfies a capability.
It exists because the identity service does not treat permissions as nested. Holding Chat.ReadWrite does not make Chat.Read obtainable: they are unrelated strings, and asking for one the user never consented to is refused outright. A caller that asked for the least privileged scope and was refused should try this before concluding the capability is unavailable, since consenting by capability commonly grants only the broader form.
func NeedsAdminConsent ¶
func NeedsAdminConsent(capability Capability) bool
NeedsAdminConsent reports whether the least privileged scope that satisfies a capability can only be granted by a tenant administrator.
Every Outlook mail permission is user-consentable: a caller missing one can always fix it by signing in again and accepting a consent screen. Half of the Teams permissions are not -- Team.ReadBasic.All, Channel.ReadBasic.All, ChannelMessage.Read.All, and Presence.Read.All are tenant-wide by construction, and a signed-in user is never offered them regardless of how many times they retry sign-in. Telling a caller to grant one is advice they cannot act on, and a caller that treats every missing capability the same way loops forever against a consent screen that will never offer it.
func Require ¶
func Require(scopes []string, capability Capability) error
Require reports an error when the given scopes do not satisfy a capability. It is the check to make before a request rather than after a refusal.
func ScopesFor ¶
func ScopesFor(capability Capability) []string
ScopesFor returns the delegated permissions that satisfy a capability. Any one of them is sufficient; the first is the least privileged.
func ScopesToRequest ¶
func ScopesToRequest(granted []string, capabilities ...Capability) []string
ScopesToRequest returns the scopes to ask for so that the given capabilities are all satisfied, keeping any already granted and adding the least privileged permission for each that is not.
The result is deduplicated and stably ordered, so re-running it produces the same sign-in line rather than a reshuffled one. The granted argument must be what consent actually covers, not the scopes a client is configured with. Passing the configuration instead makes this add the least-privileged scope for a capability the user has already granted in its broader form -- a permission that was never consented to, which the identity service refuses.
Types ¶
type Activity ¶
type Activity string
Activity is a presence activity, such as "InACall" or "Presenting". It is a plain string rather than a closed enum: the service documents this set as subject to change, and an unrecognized value should still round-trip rather than collapse to nothing.
type Availability ¶
type Availability string
Availability is a presence availability state.
These values are Capitalized -- "Available", not "available" -- unlike every other enum in this package, all of which the service spells in camelCase. That is the service's own spelling for this one resource, not an inconsistency this package introduced, and comparing a decoded value against a lowercased literal shows up as "nobody is ever available," silently, since the comparison simply never matches.
const ( AvailabilityAvailable Availability = "Available" AvailabilityAvailableIdle Availability = "AvailableIdle" AvailabilityAway Availability = "Away" AvailabilityBeRightBack Availability = "BeRightBack" AvailabilityBusy Availability = "Busy" AvailabilityBusyIdle Availability = "BusyIdle" AvailabilityDoNotDisturb Availability = "DoNotDisturb" AvailabilityOffline Availability = "Offline" AvailabilityUnknown Availability = "PresenceUnknown" )
type Capability ¶
type Capability string
Capability is something this client can be asked to do.
Every operation in this package documents the capability it needs, and the capability names the delegated permissions that satisfy it. This exists because the service is unhelpful about permissions: a request made without the right one is refused with a bare error that names nothing, so there is no way to learn from the failure what to ask for. Checking beforehand is the only way to give an answer.
const ( // ReadIdentity reads the signed-in user's own profile. ReadIdentity Capability = "read your profile" // ListChats lists chats and their members. ListChats Capability = "list chats" // ReadChatMessages reads messages in a chat. ReadChatMessages Capability = "read chat messages" // SendChatMessage sends a chat message. SendChatMessage Capability = "send a chat message" // ChangeChat reacts to or soft-deletes a chat message. ChangeChat Capability = "change chat messages" // ListTeams lists the teams the signed-in user belongs to. ListTeams Capability = "list your teams" // ListChannels lists a team's channels. ListChannels Capability = "list channels" // ReadChannelMessages reads channel messages and their replies. ReadChannelMessages Capability = "read channel messages" // SendChannelMessage posts to a channel. SendChannelMessage Capability = "send a channel message" // SearchMessages runs a full-text message search. SearchMessages Capability = "search messages" // ReadOwnPresence reads the signed-in user's own presence. ReadOwnPresence Capability = "read your presence" // ReadPresence reads another user's presence. ReadPresence Capability = "read others' presence" )
func Capabilities ¶
func Capabilities() []Capability
Capabilities lists every capability, narrowest first.
func MissingFor ¶
func MissingFor(scopes []string, capabilities ...Capability) []Capability
MissingFor returns the capabilities the given scopes do not satisfy, in a stable order.
func UserConsentable ¶
func UserConsentable() []Capability
UserConsentable returns the capabilities a signed-in user can grant by consenting, in the stable Capabilities order. It excludes anything NeedsAdminConsent reports true for.
type Channel ¶
type Channel struct {
ID ChannelID `json:"id,omitempty"`
DisplayName string `json:"displayName,omitempty"`
Description string `json:"description,omitempty"`
// Email is expensive for the service to populate. See [ChannelQuery.Select].
Email string `json:"email,omitempty"`
WebURL string `json:"webUrl,omitempty"`
Membership ChannelMembership `json:"membershipType,omitempty"`
CreatedDateTime time.Time `json:"createdDateTime,omitzero"`
IsArchived bool `json:"isArchived,omitempty"`
IsFavoriteByDefault bool `json:"isFavoriteByDefault,omitempty"`
TenantID string `json:"tenantId,omitempty"`
}
Channel is a channel within a team.
Membership matters beyond description: a private or shared channel keeps its messages on a back end the team's own message endpoints do not reach. Client.ChannelMessages against one of these returns an empty listing, not a permission error, which reads as "no messages yet" rather than "you are looking in the wrong place."
type ChannelIdentity ¶
type ChannelIdentity struct {
TeamID TeamID `json:"teamId,omitempty"`
ChannelID ChannelID `json:"channelId,omitempty"`
}
ChannelIdentity names the team and channel a channel message belongs to.
type ChannelMembership ¶
type ChannelMembership string
ChannelMembership is Graph's evolvable channelMembershipType enum.
const ( ChannelStandard ChannelMembership = "standard" ChannelPrivate ChannelMembership = "private" ChannelUnknownFutureValue ChannelMembership = "unknownFutureValue" )
type ChannelMessageQuery ¶
type ChannelMessageQuery struct {
MessageQuery
// WithReplies expands each root message's first page of replies. This is
// the one $expand a channel message listing accepts; every other
// $select or $expand is refused with 400, same as on a chat message
// listing. A thread longer than the expansion's cap is truncated with a
// per-message continuation link on the message itself -- see
// [Client.MoreReplies].
WithReplies bool
}
ChannelMessageQuery selects, windows, and orders a channel message listing.
func (ChannelMessageQuery) Params ¶
func (q ChannelMessageQuery) Params() msgraph.Params
Params renders the query as Graph query parameters.
type ChannelQuery ¶
type ChannelQuery struct {
// Select limits the properties Graph returns. Excluding "email" is worth
// doing deliberately: the service computes it from the channel's
// underlying group mailbox, which costs measurably more than the rest of
// the channel's properties combined, and a listing rendering only names
// and descriptions does not need to pay for it.
Select []string
// Where is a typed $filter.
Where msgraph.Expr
// Custom carries query parameters this package does not model.
Custom url.Values
}
ChannelQuery selects and filters a channel listing.
Unlike a chat or channel message listing, a channel listing genuinely supports $filter and $select, which is why -- unlike MessageQuery and ChatQuery -- this has a Select and a Where field instead of a closed set of booleans.
func (ChannelQuery) Params ¶
func (q ChannelQuery) Params() msgraph.Params
Params renders the query as Graph query parameters.
type Chat ¶
type Chat struct {
ID ChatID `json:"id,omitempty"`
// Topic is null for a one-on-one chat: it has no name of its own. See
// [Chat.DisplayName].
Topic string `json:"topic,omitempty"`
Type ChatType `json:"chatType,omitempty"`
CreatedDateTime time.Time `json:"createdDateTime,omitzero"`
LastUpdatedDateTime time.Time `json:"lastUpdatedDateTime,omitzero"`
WebURL string `json:"webUrl,omitempty"`
TenantID string `json:"tenantId,omitempty"`
// Viewpoint is the signed-in user's own read state, populated only when
// requested. See [Chat.Unread].
Viewpoint *ChatViewpoint `json:"viewpoint,omitempty"`
// LastMessagePreview is populated only when [ChatQuery.WithLastMessagePreview]
// or [ChatQuery.ByRecentMessage] was set.
LastMessagePreview *MessagePreview `json:"lastMessagePreview,omitempty"`
// Members is populated only when [ChatQuery.WithMembers] was set, and is
// truncated at 25 with no continuation link; see [Client.ChatMembers]
// for a chat that might have more.
Members []ConversationMember `json:"members,omitempty"`
OnlineMeetingInfo *OnlineMeetingInfo `json:"onlineMeetingInfo,omitempty"`
}
Chat is a one-on-one, group, or meeting chat.
func (Chat) DisplayName ¶
DisplayName returns a name for the chat: Topic when the service sent one, otherwise -- for a one-on-one chat, whose Topic is always null -- the other participant's display name, found by excluding self from Members.
Members is truncated at 25 with no continuation link (see Client.ChatMembers), which does not affect a one-on-one chat -- it only ever has two members -- but does mean this cannot reliably name "the other participants" for a large group chat with no topic of its own; it only ever looks at what ChatQuery.WithMembers already fetched.
func (Chat) Unread ¶
Unread reports whether the chat has a message newer than the signed-in user's last read mark.
known is false when either half of that comparison was never asked for: LastMessagePreview was not expanded, or the chat was never opened (a nil ChatViewpoint.LastMessageReadDateTime). Reporting "read" in either case would mark every chat in a listing that simply did not request the preview as read, which is a worse answer than admitting the question cannot be answered from what was fetched.
type ChatMessage ¶
type ChatMessage struct {
ID MessageID `json:"id,omitempty"`
// ETag is the message's version tag, exposed on the wire as @odata.etag.
ETag ETag `json:"@odata.etag,omitempty"`
MessageType MessageType `json:"messageType,omitempty"`
CreatedDateTime time.Time `json:"createdDateTime,omitzero"`
LastModifiedDateTime time.Time `json:"lastModifiedDateTime,omitzero"`
// LastEditedDateTime is set only when a human edited the message after
// posting it, which is what makes it the right field for
// [ChatMessage.Edited]. LastModifiedDateTime is the wrong one to use for
// that: it also moves when someone reacts to the message, so a message
// nobody has touched would misreport as edited the first time it gets a
// thumbs up.
LastEditedDateTime *time.Time `json:"lastEditedDateTime,omitempty"`
// DeletedDateTime is set when the message was soft-deleted. The message
// still exists at this ID with its body cleared by the service, rather
// than the resource disappearing.
DeletedDateTime *time.Time `json:"deletedDateTime,omitempty"`
// From is null on a system event message: nothing sent it, the service
// generated it. Dereferencing this unconditionally is the most common way
// a Teams client renders a blank sender for a membership change instead
// of recognizing the message as an event. See [ChatMessage.Sender] and
// [ChatMessage.IsSystemEvent].
From *IdentitySet `json:"from,omitempty"`
Body *ItemBody `json:"body,omitempty"`
Subject string `json:"subject,omitempty"`
Summary string `json:"summary,omitempty"`
Mentions []Mention `json:"mentions,omitempty"`
Reactions []Reaction `json:"reactions,omitempty"`
Attachments []MessageAttachment `json:"attachments,omitempty"`
Importance Importance `json:"importance,omitempty"`
Locale string `json:"locale,omitempty"`
WebURL string `json:"webUrl,omitempty"`
// EventDetail is set only on a system event message, whichever
// MessageType it decoded to. [ChatMessage.IsSystemEvent] checks this
// rather than MessageType, which is what keeps it correct even when a
// proxy strips the Prefer header this package always sends and the type
// arrives as [MessageTypeUnknownFutureValue] instead.
EventDetail *EventDetail `json:"eventDetail,omitempty"`
ChatID ChatID `json:"chatId,omitempty"`
ChannelIdentity ChannelIdentity `json:"channelIdentity,omitempty"`
ReplyToID MessageID `json:"replyToId,omitempty"`
// Replies is populated only when the request expanded them. An empty
// slice here does not mean the message has no replies; it means they were
// not asked for.
Replies []ChatMessage `json:"replies,omitempty"`
// RepliesNextLink continues a truncated, expanded replies list. It is a
// second pagination nested inside the parent collection's own nextLink; a
// caller following only the outer link truncates every long thread
// silently, with no error and no gap it can detect.
RepliesNextLink string `json:"replies@odata.nextLink,omitempty"`
}
ChatMessage is a message in a chat or a channel.
Graph omits any property that was not requested or expanded, so a field being zero can mean either "empty" or "not populated for this request".
func (ChatMessage) Deleted ¶
func (m ChatMessage) Deleted() bool
Deleted reports whether the message was soft-deleted. It still exists at this ID; only its content is gone.
func (ChatMessage) Edited ¶
func (m ChatMessage) Edited() bool
Edited reports whether a human edited the message after posting it. See the field comment on ChatMessage.LastEditedDateTime for why this is not LastModifiedDateTime.
func (ChatMessage) InlineImages ¶
func (m ChatMessage) InlineImages() []HostedContentRef
InlineImages returns the hosted-content images referenced by <img> tags in the message body, in document order.
func (ChatMessage) IsSystemEvent ¶
func (m ChatMessage) IsSystemEvent() bool
IsSystemEvent reports whether the message is service-generated rather than authored. It checks EventDetail rather than MessageType, so it stays correct even when a proxy strips the Prefer header this package always sends and the type decodes to MessageTypeUnknownFutureValue instead of MessageTypeSystemEventMessage.
func (ChatMessage) Mine ¶
func (m ChatMessage) Mine(self UserID) bool
Mine reports whether self authored the message. It is false for a system event message and false when self is empty, rather than matching against a sender the caller did not actually ask about.
func (ChatMessage) PlainText ¶
func (m ChatMessage) PlainText() string
PlainText renders the message body as text, resolving <at id="N"> mentions against m.Mentions, converting <br>, <p>, and <div> to newlines, replacing an inline image with a bracketed marker, and stripping every other tag. It does no I/O and is a pure function of m: everything it needs is already in the value.
func (ChatMessage) Sender ¶
func (m ChatMessage) Sender() (Identity, bool)
Sender returns the message's author, reporting false for a system event message, which has none.
type ChatQuery ¶
type ChatQuery struct {
// WithMembers expands the chat's members, truncated at 25 with no
// continuation link. See [Client.ChatMembers] for a chat that might have
// more.
WithMembers bool
// WithLastMessagePreview expands the chat's most recent message.
WithLastMessagePreview bool
// ByRecentMessage orders chats by their most recent message, newest
// first, automatically adding the lastMessagePreview expansion -- see
// [ChatQuery.Params] for why that addition is not optional.
ByRecentMessage bool
// Top asks for a page size, clamped to [MaxChatPageSize].
Top int
// Custom carries query parameters this package does not model.
Custom url.Values
}
ChatQuery selects and expands a chat listing.
There is no Select, Where, or Sort field. The service serves exactly one ordering -- most recently updated, once ByRecentMessage asks for it -- and exactly two expansions, so those are booleans rather than an open-ended filter or sort a caller could aim at a property the service does not index.
func (ChatQuery) Params ¶
Params renders the query as Graph query parameters.
ByRecentMessage always adds the lastMessagePreview expansion, whether or not WithLastMessagePreview was also set. The service refuses to order a chat listing by a property inside an expansion it was not asked to perform -- ordering by FieldLastMessagePreviewCreated without expanding lastMessagePreview answers with an error the caller has no way to spell a request around, short of always remembering to set both fields together. Folding the expansion into ByRecentMessage removes that footgun: asking to sort by recent message is, by construction, also asking for the data the sort needs.
type ChatViewpoint ¶
type ChatViewpoint struct {
IsHidden bool `json:"isHidden,omitempty"`
// LastMessageReadDateTime is a pointer because "never opened this chat"
// and "opened it at the zero time" are different facts. Comparing a
// plain [time.Time] against a message's CreatedDateTime cannot tell them
// apart; a nil here can.
LastMessageReadDateTime *time.Time `json:"lastMessageReadDateTime,omitempty"`
}
ChatViewpoint is the signed-in user's own view of a chat: what nobody else's copy of the same chat resource would show.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
Client is a Microsoft Teams client over Microsoft Graph.
It owns Teams semantics -- paths, request shapes, and models -- and delegates transport, retries, paging, and error classification to msgraph.Client.
func New ¶
New creates a Teams client over an existing Graph client.
It always appends msgraph.PreferIncludeUnknownEnumMembers to the Prefer directives sent with every request, and there is no option to turn this off. Without it, every system event message -- a member joining, a chat renamed, a call ending -- decodes to MessageTypeUnknownFutureValue instead of MessageTypeSystemEventMessage, silently and with no error; in a real chat's history that is a large minority of it. There is no legitimate reason to want that, so it is not a choice this package offers.
func (*Client) ChannelMembers ¶
func (c *Client) ChannelMembers(ctx context.Context, team TeamID, channel ChannelID) iter.Seq2[ConversationMember, error]
ChannelMembers iterates a channel's members directly, following pagination, for the same reason and with the same truncation hazard as Client.ChatMembers.
Needs ListChannels.
func (*Client) ChannelMessages ¶
func (c *Client) ChannelMessages(ctx context.Context, team TeamID, channel ChannelID, query ChannelMessageQuery) iter.Seq2[ChatMessage, error]
ChannelMessages iterates a channel's messages, following pagination.
This returns ROOT messages only, at any page depth -- however far a caller pages, no reply ever appears in this collection, whether or not ChannelMessageQuery.WithReplies was set. A transcript built from this alone shows every conversation opener and none of the conversation. Read a thread with Client.Replies and Client.MoreReplies, or expand the first page of each thread inline with WithReplies.
Needs ReadChannelMessages.
func (*Client) Channels ¶
func (c *Client) Channels(ctx context.Context, team TeamID, query ChannelQuery) iter.Seq2[Channel, error]
Channels iterates a team's channels.
Needs ListChannels.
func (*Client) ChatMembers ¶
ChatMembers iterates a chat's members directly, following pagination.
This exists because $expand=members on Client.GetChat or Client.Chats truncates at 25 members with no continuation link -- see ChatQuery.WithMembers. A chat reporting exactly 25 members from an expansion almost certainly has more; this endpoint is the only way to read them all. Member ids returned here are opaque strings scoped to the chat; do not parse them.
Needs ListChats.
func (*Client) Chats ¶
Chats iterates the signed-in user's chats, following pagination.
Needs ListChats.
func (*Client) GetChannel ¶
func (c *Client) GetChannel(ctx context.Context, team TeamID, channel ChannelID) (Channel, *msgraph.Response, error)
GetChannel fetches a single channel.
Needs ListChannels.
func (*Client) GetChat ¶
func (c *Client) GetChat(ctx context.Context, chat ChatID, query ChatQuery) (Chat, *msgraph.Response, error)
GetChat fetches a single chat.
Needs ListChats.
func (*Client) GetMessage ¶
func (c *Client) GetMessage(ctx context.Context, chat ChatID, message MessageID) (ChatMessage, *msgraph.Response, error)
GetMessage fetches a single chat message.
There is no query parameter, unlike Client.GetChat or Client.Messages: $select and $expand are unsupported on a message resource just as they are on the listing, so a query argument here would only let a caller build a request the service answers with 400.
Needs ReadChatMessages.
func (*Client) GetTeam ¶
GetTeam fetches a single team, populating every property -- including Visibility, InternalID, and WebURL, which a value from Client.JoinedTeams leaves null.
Needs ListTeams.
func (*Client) Graph ¶
Graph returns the underlying Graph client for calls this package does not model.
func (*Client) JoinedTeams ¶
JoinedTeams iterates the teams the signed-in user belongs to.
GET /me/joinedTeams accepts no OData query options at all -- no $select, $filter, $orderby, or $expand -- which is why this takes no query parameter, unlike every other listing in this package.
The listing also populates only id, displayName, description, isArchived, and tenantId. Every other property, including Visibility, InternalID, and WebURL, comes back null. Reading Visibility off a value from this iterator sees the empty string and, if compared against TeamVisibilityPublic or read as "falsy", concludes the team is public -- a different claim from "this property was never loaded." Call Client.GetTeam on the team's ID to read the rest.
Needs ListTeams.
func (*Client) ListChats ¶
func (c *Client) ListChats(ctx context.Context, query ChatQuery) (msgraph.Page[Chat], *msgraph.Response, error)
ListChats returns one page of chats plus the msgraph.Response metadata. Use Client.Chats to page automatically.
Needs ListChats.
func (*Client) ListJoinedTeams ¶
func (c *Client) ListJoinedTeams(ctx context.Context) (msgraph.Page[Team], *msgraph.Response, error)
ListJoinedTeams returns one page of joined teams plus the msgraph.Response metadata. Use Client.JoinedTeams to page automatically. See Client.JoinedTeams for which properties are actually populated.
Needs ListTeams.
func (*Client) ListMessages ¶
func (c *Client) ListMessages(ctx context.Context, chat ChatID, query MessageQuery) (msgraph.Page[ChatMessage], *msgraph.Response, error)
ListMessages returns one page of a chat's messages plus the msgraph.Response metadata. Use Client.Messages to page automatically.
Needs ReadChatMessages.
func (*Client) Messages ¶
func (c *Client) Messages(ctx context.Context, chat ChatID, query MessageQuery) iter.Seq2[ChatMessage, error]
Messages iterates a chat's messages, following pagination.
Needs ReadChatMessages.
func (*Client) MoreReplies ¶
func (c *Client) MoreReplies(ctx context.Context, message ChatMessage) iter.Seq2[ChatMessage, error]
MoreReplies continues a truncated, expanded replies list.
ChatMessage.RepliesNextLink is a second pagination nested inside the message collection's own @odata.nextLink: the outer link pages root messages and says nothing about a reply thread that did not fit in the expansion. Nothing in Client.ChannelMessages's own iteration follows it, so a caller reading only the outer link truncates every long thread silently, mid-argument, with no error and no gap it can detect.
This follows the link directly with Client.Graph, which is safe because msgraph-go pins requests to the configured Graph host regardless of what an absolute link names. A message with no RepliesNextLink yields nothing, not an error: there is nothing more to fetch.
Needs ReadChannelMessages.
func (*Client) MyPresence ¶
MyPresence reads the signed-in user's own presence.
Needs ReadOwnPresence.
func (*Client) Replies ¶
func (c *Client) Replies(ctx context.Context, team TeamID, channel ChannelID, message MessageID, query MessageQuery) iter.Seq2[ChatMessage, error]
Replies iterates the replies to a channel message, following pagination.
The reply thread is one level deep: a reply's own id cannot be passed here to read further replies, because there are none -- see Client.ReplyToChannelMessage.
Needs ReadChannelMessages.
func (*Client) ReplyToChannelMessage ¶
func (c *Client) ReplyToChannelMessage(ctx context.Context, team TeamID, channel ChannelID, message MessageID, draft Draft) (ChatMessage, *msgraph.Response, error)
ReplyToChannelMessage posts a reply to a root channel message.
The thread is one level deep. Posting to a reply's own id as though it were a root message fails, because a reply has no replies collection of its own -- see Client.Replies.
Needs SendChannelMessage.
func (*Client) SearchMessages ¶
SearchMessages runs a full-text message search, following the service's offset-based continuation until moreResultsAvailable is false.
This is the one collection in this package with no @odata.nextLink: the endpoint pages a search by advancing From by the page size instead, and nothing in Client.Graph or the foundation's paginate helper models that. A run is bounded by the client's WithMaxPages the same way every other listing here is, so a service that never stops reporting more results available cannot make this loop forever.
Needs SearchMessages.
func (*Client) SearchPage ¶
func (c *Client) SearchPage(ctx context.Context, query SearchQuery) (hits []SearchHit, more bool, resp *msgraph.Response, err error)
SearchPage runs one page of a message search.
With Chat.Read alone -- and no channel-message permission -- this returns chat hits and no channel hits, with no error and nothing in the response distinguishing that from a search that genuinely found nothing in channels. An answer that looks complete is not the same as one that searched everywhere it could have.
Needs SearchMessages.
func (*Client) Self ¶
Self returns the signed-in user's ID configured with WithSelf, or the zero UserID if none was set.
func (*Client) SendChannelMessage ¶
func (c *Client) SendChannelMessage(ctx context.Context, team TeamID, channel ChannelID, draft Draft) (ChatMessage, *msgraph.Response, error)
SendChannelMessage posts a new root message to a channel and returns the created copy.
Needs SendChannelMessage.
func (*Client) SendMessage ¶
func (c *Client) SendMessage(ctx context.Context, chat ChatID, draft Draft) (ChatMessage, *msgraph.Response, error)
SendMessage posts a new message to a chat and returns the created copy.
Needs SendChatMessage.
func (*Client) SetReaction ¶
func (c *Client) SetReaction(ctx context.Context, chat ChatID, message MessageID, reaction ReactionType) (*msgraph.Response, error)
SetReaction reacts to a message.
Reacting again with the same type is idempotent. Reacting with a different type does not replace the first: the message ends up carrying both reactions from the same person, which surprises anyone expecting a radio button where the service implements a set. Call Client.UnsetReaction with the old type first to swap one reaction for another.
Needs ChangeChat.
func (*Client) SoftDeleteMessage ¶
func (c *Client) SoftDeleteMessage(ctx context.Context, chat ChatID, message MessageID) (*msgraph.Response, error)
SoftDeleteMessage deletes a message.
The message is not removed. It keeps its id, keeps appearing in every listing that already reached it, and reports ChatMessage.Deleted true with its body emptied by the service. A caller that wants a soft-deleted message gone from a rendered transcript has to filter it out itself; the listing will not do that for them.
Needs ChangeChat.
func (*Client) UndoSoftDeleteMessage ¶
func (c *Client) UndoSoftDeleteMessage(ctx context.Context, chat ChatID, message MessageID) (*msgraph.Response, error)
UndoSoftDeleteMessage restores a message soft-deleted with Client.SoftDeleteMessage. The body the service emptied on delete is not restored; only the deletedDateTime marker is cleared.
Needs ChangeChat.
func (*Client) UnsetReaction ¶
func (c *Client) UnsetReaction(ctx context.Context, chat ChatID, message MessageID, reaction ReactionType) (*msgraph.Response, error)
UnsetReaction removes a reaction of the given type. Removing a reaction that was never set is not an error.
Needs ChangeChat.
type ConversationMember ¶
type ConversationMember struct {
// ID identifies this membership record, not the user. It is an opaque
// string scoped to the chat or channel; do not parse it. See
// [ConversationMember.UserID] for the user's own identifier.
ID string `json:"id,omitempty"`
DisplayName string `json:"displayName,omitempty"`
UserID UserID `json:"userId,omitempty"`
Email string `json:"email,omitempty"`
Roles []MemberRole `json:"roles,omitempty"`
// VisibleHistoryStartDateTime is set when a member was added to an
// existing chat and can see history only from that point forward. A nil
// value means the member sees the whole history, not that this property
// was not loaded -- member listings always populate it.
VisibleHistoryStartDateTime *time.Time `json:"visibleHistoryStartDateTime,omitempty"`
}
ConversationMember is one member of a chat or channel.
type Draft ¶
type Draft struct {
Body *ItemBody `json:"body"`
Subject string `json:"subject,omitempty"`
Importance Importance `json:"importance,omitempty"`
Mentions []Mention `json:"mentions,omitempty"`
Attachments []MessageAttachment `json:"attachments,omitempty"`
}
Draft is the set of properties Graph accepts when creating a chat or channel message.
It is a separate type from ChatMessage because the send endpoint accepts only a handful of properties and silently discards the rest: posting a round-tripped ChatMessage sends an id, an etag, and timestamps that Graph ignores, which reads as "it worked" right up until a caller relies on one of those fields meaning something in the response.
type ErrMissingCapability ¶
type ErrMissingCapability struct {
// Capability is what was needed.
Capability Capability
// Accepts lists the permissions that would satisfy it.
Accepts []string
// AdminOnly is true when none of Accepts can be granted by the
// signed-in user; see [NeedsAdminConsent].
AdminOnly bool
}
ErrMissingCapability reports a permission the caller does not hold. It is returned before the request is sent, so nothing is attempted that the service would only refuse.
func (*ErrMissingCapability) Error ¶
func (e *ErrMissingCapability) Error() string
type EventDetail ¶
type EventDetail struct {
Type EventDetailType `json:"@odata.type,omitempty"`
Initiator *IdentitySet `json:"initiator,omitempty"`
Raw json.RawMessage `json:"-"`
}
EventDetail is a system event message's event payload.
It is a discriminator plus a raw copy of the JSON rather than a Go interface with one implementation per variant. Graph has added roughly forty of these without a version bump, and an interface registry makes an unrecognized variant decode to nil -- which renders a message as a blank line where somebody left the team, was added to a call, or had a message pinned, with no indication anything was lost. Keeping the raw bytes means an unknown variant still round-trips and still answers EventDetail.Decode for whatever shape a caller already knows to expect from it.
func (*EventDetail) CallDuration ¶
func (d *EventDetail) CallDuration() (time.Duration, bool)
CallDuration returns how long the call lasted from a callEnded event, reporting false for any other event type and for a duration this package cannot parse. A zero-length call and an unreadable one are different facts, and collapsing them both into a zero time.Duration would hide which one happened.
func (*EventDetail) Decode ¶
func (d *EventDetail) Decode(into any) error
Decode unmarshals the raw event payload into into, giving access to a variant-specific field this type does not model directly.
func (*EventDetail) Known ¶
func (d *EventDetail) Known() bool
Known reports whether Type is one of the variants this package recognizes. It is false for a nil receiver and for any @odata.type Graph has added since this package was last updated -- which EventDetail.Decode still answers, using Raw.
func (EventDetail) MarshalJSON ¶
func (d EventDetail) MarshalJSON() ([]byte, error)
MarshalJSON returns Raw when it is populated, so a value decoded from the service round-trips byte for byte including whatever variant-specific fields this type does not model. A value built by hand, with no Raw, falls back to marshaling the typed fields.
func (*EventDetail) MembersAdded ¶
func (d *EventDetail) MembersAdded() ([]Identity, bool)
MembersAdded returns the members named by a membersAdded or membersJoined event, reporting false for any other event type.
func (*EventDetail) MembersRemoved ¶
func (d *EventDetail) MembersRemoved() ([]Identity, bool)
MembersRemoved returns the members named by a membersDeleted or membersLeft event, reporting false for any other event type.
func (*EventDetail) NewTopic ¶
func (d *EventDetail) NewTopic() (string, bool)
NewTopic returns the chat's new name from a chatRenamed event, reporting false for any other event type.
func (*EventDetail) Summary ¶
func (d *EventDetail) Summary() (EventSummary, bool)
Summary builds an EventSummary for a known event type, reporting false for a nil receiver or an event type EventDetail.Known does not recognize.
func (*EventDetail) UnmarshalJSON ¶
func (d *EventDetail) UnmarshalJSON(data []byte) error
UnmarshalJSON decodes the typed fields and keeps a copy of the raw bytes for EventDetail.Decode and for round-tripping an unrecognized variant.
The copy is load-bearing: encoding/json reuses its scratch buffer across calls to a custom UnmarshalJSON, so data is only valid for the duration of this call, and keeping a reference to it instead of a copy would corrupt Raw the next time anything on the same decoder runs.
type EventDetailType ¶
type EventDetailType string
EventDetailType is the @odata.type of a system event message's eventDetail, naming which of the roughly forty variants Graph has added over the years it is.
const ( EventDetailMembersAdded EventDetailType = "#microsoft.graph.membersAddedEventMessageDetail" EventDetailMembersDeleted EventDetailType = "#microsoft.graph.membersDeletedEventMessageDetail" EventDetailMembersJoined EventDetailType = "#microsoft.graph.membersJoinedEventMessageDetail" EventDetailMembersLeft EventDetailType = "#microsoft.graph.membersLeftEventMessageDetail" EventDetailChatRenamed EventDetailType = "#microsoft.graph.chatRenamedEventMessageDetail" EventDetailChannelAdded EventDetailType = "#microsoft.graph.channelAddedEventMessageDetail" EventDetailChannelRenamed EventDetailType = "#microsoft.graph.channelRenamedEventMessageDetail" EventDetailCallStarted EventDetailType = "#microsoft.graph.callStartedEventMessageDetail" EventDetailCallEnded EventDetailType = "#microsoft.graph.callEndedEventMessageDetail" EventDetailMeetingStarted EventDetailType = "#microsoft.graph.meetingStartedEventMessageDetail" EventDetailMeetingEnded EventDetailType = "#microsoft.graph.meetingEndedEventMessageDetail" EventDetailTeamsAppInstalled EventDetailType = "#microsoft.graph.teamsAppInstalledEventMessageDetail" EventDetailMessagePinned EventDetailType = "#microsoft.graph.messagePinnedEventMessageDetail" EventDetailMessageUnpinned EventDetailType = "#microsoft.graph.messageUnpinnedEventMessageDetail" )
type EventSummary ¶
type EventSummary struct {
Event EventDetailType
Actor Identity
Subjects []Identity
Topic string
Duration time.Duration
}
EventSummary is a structured summary of a system event, built from whichever of EventDetail's typed accessors apply to its Event type.
This is a struct rather than an assembled sentence because wording, tense, and language are display decisions this package does not make on a caller's behalf.
type HostedContentID ¶
type HostedContentID string
HostedContentID identifies an inline image or file referenced from a message body.
func (HostedContentID) String ¶
func (i HostedContentID) String() string
type HostedContentRef ¶
type HostedContentRef struct {
ID HostedContentID
URL string
Alt string
}
HostedContentRef is an inline image ChatMessage.InlineImages found in a message body, referencing the chat or channel's hostedContents collection rather than carrying the image bytes itself.
type Identity ¶
type Identity struct {
ID string `json:"id,omitempty"`
DisplayName string `json:"displayName,omitempty"`
Type IdentityType `json:"userIdentityType,omitempty"`
}
Identity is a single Graph identity resource: a person, application, or device with an id and a display name.
type IdentityKind ¶
type IdentityKind string
IdentityKind names which slot of an IdentitySet was populated.
const ( IdentityKindUser IdentityKind = "user" IdentityKindApplication IdentityKind = "application" IdentityKindDevice IdentityKind = "device" )
type IdentitySet ¶
type IdentitySet struct {
User *Identity `json:"user,omitempty"`
Application *Identity `json:"application,omitempty"`
Device *Identity `json:"device,omitempty"`
}
IdentitySet is Graph's identitySet resource: at most one of User, Application, or Device is populated, naming whatever sent the message. On a system event message every field is nil, because nothing sent it.
func (*IdentitySet) Actor ¶
func (s *IdentitySet) Actor() (Identity, IdentityKind, bool)
Actor returns whichever identity is populated and reports which kind it was. It is nil-receiver-safe: a caller holding a *IdentitySet obtained from ChatMessage.From does not have to check for nil before calling this, only before doing anything with the returned bool.
func (*IdentitySet) Name ¶
func (s *IdentitySet) Name() string
Name returns the populated identity's display name, or the empty string when the set is nil or empty. It is nil-receiver-safe for the same reason IdentitySet.Actor is.
type IdentityType ¶
type IdentityType string
IdentityType is Graph's teamworkUserIdentityType enum, present on identities that come from presence and meeting-participant endpoints. A chat message's sender does not carry one; the zero value there means "not applicable", not "aadUser".
const ( IdentityTypeAADUser IdentityType = "aadUser" IdentityTypeOnPremiseAADUser IdentityType = "onPremiseAadUser" IdentityTypeAnonymousGuest IdentityType = "anonymousGuest" IdentityTypeFederatedUser IdentityType = "federatedUser" IdentityTypePersonalMicrosoftAccount IdentityType = "personalMicrosoftAccount" IdentityTypeSkypeUser IdentityType = "skypeUser" IdentityTypePhoneUser IdentityType = "phoneUser" IdentityTypeUnknownFutureValue IdentityType = "unknownFutureValue" )
type Importance ¶
type Importance string
Importance is a chat message's importance level.
const ( ImportanceNormal Importance = "normal" ImportanceHigh Importance = "high" ImportanceUrgent Importance = "urgent" )
type ItemBody ¶
type ItemBody struct {
ContentType BodyType `json:"contentType,omitempty"`
Content string `json:"content,omitempty"`
}
ItemBody is a message or draft body.
func (*ItemBody) Text ¶
Text returns the body's content, unescaped of HTML entities when the content type is HTML but with markup left untouched otherwise -- this is the raw body, not a rendering of it. It is nil-safe and does no mention resolution, since an ItemBody on its own carries no mentions list to resolve against; see ChatMessage.PlainText for that.
type MemberRole ¶
type MemberRole string
MemberRole is a conversation member's role. It is a plain string rather than a closed enum for the same reason ReactionType is: the set is not exhaustively documented and an unrecognized value should still round-trip rather than collapse to nothing.
const ( // MemberRoleOwner marks a member who can manage the chat or channel. MemberRoleOwner MemberRole = "owner" // MemberRoleGuest marks a member signed in from outside the tenant. The // absence of a role -- an empty Roles slice -- means an ordinary member, // not "role not loaded": member listings always populate Roles. MemberRoleGuest MemberRole = "guest" )
type Mention ¶
type Mention struct {
ID int32 `json:"id"`
MentionText string `json:"mentionText,omitempty"`
Mentioned *MentionedIdentitySet `json:"mentioned,omitempty"`
}
Mention is one entry in a chat message's mentions list, matched against an <at id="N"> tag in the body by Mention.ID.
type MentionedIdentitySet ¶
type MentionedIdentitySet struct {
User *Identity `json:"user,omitempty"`
Application *Identity `json:"application,omitempty"`
Device *Identity `json:"device,omitempty"`
Conversation *Identity `json:"conversation,omitempty"`
Tag *Identity `json:"tag,omitempty"`
}
MentionedIdentitySet is Graph's chatMessageMentionedIdentitySet: wider than IdentitySet because a mention can also name the conversation itself or a tag, neither of which is ever the sender of a message.
type MessageAttachment ¶
type MessageAttachment struct {
ID string `json:"id,omitempty"`
ContentType string `json:"contentType,omitempty"`
ContentURL string `json:"contentUrl,omitempty"`
Content string `json:"content,omitempty"`
Name string `json:"name,omitempty"`
ThumbnailURL string `json:"thumbnailUrl,omitempty"`
}
MessageAttachment is a chat or channel message's attachment metadata.
type MessageID ¶
type MessageID string
MessageID identifies a chat or channel message.
It is epoch milliseconds, assigned by Graph when the message was created, and it is unique only within the chat, channel, or reply thread it was posted to -- the same numeric ID can name two entirely different messages in two different chats. That is why nothing in this package accepts a MessageID on its own: every operation also names the chat, channel, or thread the message belongs to.
func (MessageID) Time ¶
Time parses a MessageID back into the instant Graph assigned it, reporting false rather than the zero time when the ID is not a decimal number. ID "0" and an ID that is not numeric at all are different facts, and collapsing both into time.Time{} would make a malformed ID indistinguishable from a message posted at the Unix epoch.
type MessageOrder ¶
type MessageOrder string
MessageOrder names the property a message listing is sorted and windowed by. See MessageQuery for why this is one field rather than a separate filter property and sort property.
const ( // ByCreated orders by createdDateTime. It is MessageQuery's zero value. ByCreated MessageOrder = "createdDateTime" // ByLastModified orders by lastModifiedDateTime, which also moves when a // message is reacted to, edited, or soft-deleted -- so a listing ordered // by it surfaces recently-touched messages, not only recently-sent ones. ByLastModified MessageOrder = "lastModifiedDateTime" )
func Newest ¶
func Newest() MessageOrder
Newest orders a message listing by creation time, newest first. This is also MessageQuery's zero value for Order, so passing it is never required -- it exists for a call site that wants to say the ordering out loud rather than lean on an implicit default.
type MessagePreview ¶
type MessagePreview struct {
ID MessageID `json:"id,omitempty"`
CreatedDateTime time.Time `json:"createdDateTime,omitzero"`
From *IdentitySet `json:"from,omitempty"`
Body *ItemBody `json:"body,omitempty"`
MessageType MessageType `json:"messageType,omitempty"`
IsDeleted bool `json:"isDeleted,omitempty"`
EventDetail *EventDetail `json:"eventDetail,omitempty"`
}
MessagePreview is the abbreviated message Graph attaches to a chat when ChatQuery.WithLastMessagePreview or ChatQuery.ByRecentMessage is set.
It is a separate type from ChatMessage on purpose. The service populates only the handful of properties below; reusing ChatMessage here would present forty zero-valued fields -- Mentions, Reactions, Attachments, and the rest -- as though the message genuinely had none of them, rather than as "not part of a preview."
type MessageQuery ¶
type MessageQuery struct {
// Order is the property the listing is filtered and sorted on. The zero
// value is [ByCreated].
Order MessageOrder
// Since is an inclusive lower bound on Order. The zero time omits it.
Since time.Time
// Before is an exclusive upper bound on Order. The zero time omits it.
Before time.Time
// Top asks for a page size, clamped to [MaxMessagePageSize]. Zero asks
// for the service's own default rather than the maximum.
Top int
// Custom carries query parameters this package does not model.
Custom url.Values
}
MessageQuery selects and windows a chat or channel message listing.
Why Order is one field, not a filter property and a sort property ¶
A Teams message listing is indexed on exactly one property at a time, and the service does not tell you when a request asks for two: a $filter on createdDateTime combined with $orderby on lastModifiedDateTime is not refused. It is answered 200 OK, with the filter silently discarded and the whole collection returned instead. A caller asking for the last day of a chat gets a year of it back, and a UI that trusts the response renders a year of messages as though they were all from today, with nothing in the response shape to suggest anything went wrong.
Splitting Order from Since/Before -- as two independent fields a caller could set inconsistently -- would make that mistake something this type permits and even encourages, since the zero value of an unset sort field looks like "no preference" rather than "different from the filter." Folding them into a single Order field removes the inconsistent state from the type entirely: MessageQuery.Params always renders $filter and $orderby against the same property, because there is only one property to name. See FieldLastMessagePreviewCreated and ChatQuery for the same hazard one level up, on the chat listing that orders by a message inside it.
Why there is no Select, Expand, Where, or Descending field ¶
A chat or channel message listing refuses $select and $expand outright with 400 Bad Request -- unlike the filter mismatch above, this one really is an error, which is itself worth knowing, since the two look identical from the caller's side until one of them silently succeeds. (The one exception, expanding replies on a channel listing, is ChannelMessageQuery.WithReplies.) And the service only ever serves descending order; an ascending $orderby is refused the same way. Fields that a request would only be refused for existing are not offered here at all, so the type cannot express the requests that never work.
func (MessageQuery) Params ¶
func (q MessageQuery) Params() msgraph.Params
Params renders the query as Graph query parameters, always filtering and ordering on the same property -- see the type doc for why that coupling is the entire reason this type exists -- and always ordering descending, since ascending is refused.
func (MessageQuery) Validate ¶
func (q MessageQuery) Validate() error
Validate reports ErrInvertedWindow when Since is after Before. Call it before sending the query; every operation in this file does, so this is mainly useful to a caller building a MessageQuery to hand to Client.ListMessages or Client.Messages and wanting to fail before the round trip rather than after it.
type MessageType ¶
type MessageType string
MessageType is Graph's evolvable chatMessageType enum. See doc.go for what it costs a caller who does not send msgraph.PreferIncludeUnknownEnumMembers: every system event message decodes to MessageTypeUnknownFutureValue instead of MessageTypeSystemEventMessage.
const ( MessageTypeMessage MessageType = "message" MessageTypeChatEvent MessageType = "chatEvent" MessageTypeTyping MessageType = "typing" MessageTypeSystemEventMessage MessageType = "systemEventMessage" MessageTypeUnknownFutureValue MessageType = "unknownFutureValue" )
type OnlineMeetingInfo ¶
type OnlineMeetingInfo struct {
Organizer *IdentitySet `json:"organizer,omitempty"`
JoinWebURL string `json:"joinWebUrl,omitempty"`
}
OnlineMeetingInfo is the minimal join information Graph attaches to a meeting chat.
type Option ¶
Option configures a Client.
func WithMaxPages ¶
WithMaxPages bounds how many pages a single listing will follow, replacing msgraph.DefaultMaxPages.
The bound exists because refusing to repeat a link is not enough. A service that mints a fresh nextLink every time is followed forever, and the set of links already seen grows by one URL per page while it happens, so the run consumes requests and memory without end. A page count is the only thing that stops it.
func WithPageCapNotify ¶
WithPageCapNotify registers a callback told when a request was served fewer items than it asked for because an endpoint capped it -- Teams listings do this silently, with no error and no indication in the response that anything was capped. notify is called with the number requested and the number actually served.
func WithPrefer ¶
func WithPrefer(directives ...msgraph.PreferDirective) Option
WithPrefer adds arbitrary Prefer directives to every request.
func WithSelf ¶
WithSelf records the signed-in user's ID, so ChatMessage.Mine and future call sites that need to distinguish "I sent this" from "someone else sent this" do not each have to be told separately.
type OutOfOfficeSettings ¶
type OutOfOfficeSettings struct {
IsOutOfOffice bool `json:"isOutOfOffice,omitempty"`
Message string `json:"message,omitempty"`
}
OutOfOfficeSettings is a user's out-of-office state, as reported by presence.
type Presence ¶
type Presence struct {
ID UserID `json:"id,omitempty"`
Availability Availability `json:"availability,omitempty"`
Activity Activity `json:"activity,omitempty"`
OutOfOffice *OutOfOfficeSettings `json:"outOfOfficeSettings,omitempty"`
StatusMessage *PresenceStatusMessage `json:"statusMessage,omitempty"`
}
Presence is a user's Teams presence.
type PresenceExpiry ¶
type PresenceExpiry struct {
DateTime string `json:"dateTime,omitempty"`
TimeZone string `json:"timeZone,omitempty"`
}
PresenceExpiry is Graph's dateTimeTimeZone shape, used by a presence status message's expiry. TimeZone is whatever zone name the service sent, which -- as elsewhere in Graph -- is not guaranteed to be an IANA name.
type PresenceStatusMessage ¶
type PresenceStatusMessage struct {
Message *ItemBody `json:"message,omitempty"`
ExpiryDateTime *PresenceExpiry `json:"expiryDateTime,omitempty"`
}
PresenceStatusMessage is a user-set status message shown alongside presence.
type Reaction ¶
type Reaction struct {
ReactionType ReactionType `json:"reactionType,omitempty"`
CreatedDateTime time.Time `json:"createdDateTime,omitzero"`
// User is Graph's chatMessageReactionIdentitySet, which happens to share
// IdentitySet's shape: the wire nests the reacting person one level
// deeper than it looks, under this object's own "user" key, rather than
// holding the identity directly. IdentitySet already models a "user" key
// holding an Identity, so reusing it here decodes the extra nesting for
// free -- the reacting person is User.User, reached the same way as any
// other IdentitySet, via [IdentitySet.Actor] or [IdentitySet.Name].
User *IdentitySet `json:"user,omitempty"`
}
Reaction is one reaction on a chat message.
type ReactionType ¶
type ReactionType string
ReactionType names a message reaction. It is a plain string rather than a closed enum: a tenant can enable custom emoji reactions beyond the six built in ones, and a reaction using one arrives as whatever string the tenant named it. Treating this as an exhaustive switch would silently drop every custom reaction a message received.
const ( ReactionLike ReactionType = "like" ReactionHeart ReactionType = "heart" ReactionLaugh ReactionType = "laugh" ReactionSurprised ReactionType = "surprised" ReactionSad ReactionType = "sad" ReactionAngry ReactionType = "angry" )
type SearchHit ¶
type SearchHit struct {
Rank int32 `json:"rank"`
Summary string `json:"summary,omitempty"`
Message ChatMessage `json:"resource"`
}
SearchHit is one match from a message search.
type SearchQuery ¶
type SearchQuery struct {
// Term is the text to search for.
Term string
// From is the zero-based offset of the first hit to return.
From int
// Size is the page size, clamped to [MaxSearchPageSize]. Zero, like a
// negative value, is treated as [MaxSearchPageSize] rather than as "ask
// the service for its own default": unlike an OData $top, this number
// also has to be added to From between pages, and a page size this
// package never learned cannot be added correctly.
Size int
// Fields restricts which properties of each hit's resource the service
// returns.
Fields []string
}
SearchQuery is a full-text message search.
There is no sort field: the endpoint refuses sortProperties for a chatMessage search outright, answering 400. Message hits always come back in relevance order, and a caller wanting them chronological has to sort the hits itself after fetching them.
type Team ¶
type Team struct {
ID TeamID `json:"id,omitempty"`
DisplayName string `json:"displayName,omitempty"`
Description string `json:"description,omitempty"`
// InternalID is the messaging thread id backing the team's General
// channel conversation, not the team's own identifier. Passing it where
// a TeamID is expected -- or a TeamID where this is expected -- 404s;
// the two are different opaque strings that happen to share a shape.
InternalID string `json:"internalId,omitempty"`
WebURL string `json:"webUrl,omitempty"`
Visibility TeamVisibility `json:"visibility,omitempty"`
IsArchived bool `json:"isArchived,omitempty"`
TenantID string `json:"tenantId,omitempty"`
}
Team is a Microsoft Teams team.
A field being its zero value on a value returned from Client.JoinedTeams does not mean the property is genuinely empty; see Client.JoinedTeams.
type TeamVisibility ¶
type TeamVisibility string
TeamVisibility says who can find and join a team without an invitation.
const ( TeamVisibilityPrivate TeamVisibility = "private" TeamVisibilityPublic TeamVisibility = "public" TeamVisibilityHiddenMembership TeamVisibility = "hiddenMembership" )