2020-07-18 07:08:25 -07:00
// Copyright (c) 2020 Gary Kim <gary@garykim.dev>, All Rights Reserved
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package user
import (
2020-08-30 04:49:26 -07:00
"crypto/tls"
2020-07-26 05:51:07 -07:00
"encoding/json"
2020-08-30 04:49:26 -07:00
"errors"
2020-07-26 05:51:07 -07:00
"strings"
2020-07-18 07:08:25 -07:00
"github.com/monaco-io/request"
2020-07-26 05:51:07 -07:00
2020-08-30 04:49:26 -07:00
"gomod.garykim.dev/nc-talk/constants"
2020-07-26 05:51:07 -07:00
"gomod.garykim.dev/nc-talk/ocs"
2020-07-18 07:08:25 -07:00
)
const (
ocsCapabilitiesEndpoint = "/ocs/v2.php/cloud/capabilities"
2020-08-30 06:19:51 -07:00
ocsRoomsEndpoint = "/ocs/v2.php/apps/spreed/api/v2/room"
2020-07-18 07:08:25 -07:00
)
2020-08-30 04:49:26 -07:00
var (
// ErrUserIsNil is returned when a funciton is called with an nil user.
ErrUserIsNil = errors . New ( "user is nil" )
)
2020-07-18 07:08:25 -07:00
// TalkUser represents a user of Nextcloud Talk
type TalkUser struct {
User string
Pass string
NextcloudURL string
2020-08-30 04:49:26 -07:00
Config * TalkUserConfig
2020-07-18 07:08:25 -07:00
capabilities * Capabilities
}
2020-08-30 04:49:26 -07:00
// TalkUserConfig is configuration options for TalkUsers
type TalkUserConfig struct {
TLSConfig * tls . Config
}
2020-07-18 07:08:25 -07:00
// Capabilities describes the capabilities that the Nextcloud Talk instance is capable of. Visit https://nextcloud-talk.readthedocs.io/en/latest/capabilities/ for more info.
type Capabilities struct {
2020-07-26 05:51:07 -07:00
AttachmentsFolder string ` ocscapability:"config => attachments => folder" `
ChatMaxLength int
2020-07-18 07:08:25 -07:00
Audio bool ` ocscapability:"audio" `
Video bool ` ocscapability:"video" `
Chat bool ` ocscapability:"chat" `
GuestSignaling bool ` ocscapability:"guest-signaling" `
EmptyGroupRoom bool ` ocscapability:"empty-group-room" `
GuestDisplayNames bool ` ocscapability:"guest-display-names" `
MultiRoomUsers bool ` ocscapability:"multi-room-users" `
ChatV2 bool ` ocscapability:"chat-v2" `
Favorites bool ` ocscapability:"favorites" `
LastRoomActivity bool ` ocscapability:"last-room-activity" `
NoPing bool ` ocscapability:"no-ping" `
SystemMessages bool ` ocscapability:"system-messages" `
MentionFlag bool ` ocscapability:"mention-flag" `
InCallFlags bool ` ocscapability:"in-call-flags" `
InviteByMail bool ` ocscapability:"invite-by-mail" `
NotificationLevels bool ` ocscapability:"notification-levels" `
InviteGroupsAndMails bool ` ocscapability:"invite-groups-and-mails" `
LockedOneToOneRooms bool ` ocscapability:"locked-one-to-one-rooms" `
ReadOnlyRooms bool ` ocscapability:"read-only-rooms" `
ChatReadMarker bool ` ocscapability:"chat-read-marker" `
WebinaryLobby bool ` ocscapability:"webinary-lobby" `
StartCallFlag bool ` ocscapability:"start-call-flag" `
ChatReplies bool ` ocscapability:"chat-replies" `
CirclesSupport bool ` ocscapability:"circles-support" `
AttachmentsAllowed bool ` ocscapability:"config => attachments => allowed" `
ConversationsCanCreate bool ` ocscapability:"config => conversations => can-create" `
ForceMute bool ` ocscapability:"force-mute" `
ConversationV2 bool ` ocscapability:"conversation-v2" `
ChatReferenceID bool ` ocscapability:"chat-reference-id" `
}
2020-08-30 06:19:51 -07:00
// RoomInfo contains information about a room
type RoomInfo struct {
Token string ` json:"token" `
Name string ` json:"name" `
DisplayName string ` json:"displayName" `
SessionID string ` json:"sessionId" `
ObjectType string ` json:"objectType" `
ObjectID string ` json:"objectId" `
Type int ` json:"type" `
ParticipantType int ` json:"participantType" `
ParticipantFlags int ` json:"participantFlags" `
ReadOnly int ` json:"readOnly" `
LastPing int ` json:"lastPing" `
LastActivity int ` json:"lastActivity" `
NotificationLevel int ` json:"notificationLevel" `
LobbyState int ` json:"lobbyState" `
LobbyTimer int ` json:"lobbyTimer" `
UnreadMessages int ` json:"unreadMessages" `
LastReadMessage int ` json:"lastReadMessage" `
HasPassword bool ` json:"hasPassword" `
HasCall bool ` json:"hasCall" `
CanStartCall bool ` json:"canStartCall" `
CanDeleteConversation bool ` json:"canDeleteConversation" `
CanLeaveConversation bool ` json:"canLeaveConversation" `
IsFavorite bool ` json:"isFavorite" `
UnreadMention bool ` json:"unreadMention" `
LastMessage ocs . TalkRoomMessageData ` json:"lastMessage" `
}
2020-08-30 04:49:26 -07:00
// NewUser returns a TalkUser instance
// The url should be the full URL of the Nextcloud instance (e.g. https://cloud.mydomain.me)
func NewUser ( url string , username string , password string , config * TalkUserConfig ) ( * TalkUser , error ) {
return & TalkUser {
NextcloudURL : url ,
User : username ,
Pass : password ,
Config : config ,
} , nil
}
2020-07-18 07:08:25 -07:00
// RequestClient returns a monaco-io that is preconfigured to make OCS API calls
func ( t * TalkUser ) RequestClient ( client request . Client ) * request . Client {
if client . Header == nil {
client . Header = make ( map [ string ] string )
}
if client . Header [ "OCS-APIRequest" ] == "" {
client . Header [ "OCS-APIRequest" ] = "true"
}
if client . Header [ "Accept" ] == "" {
client . Header [ "Accept" ] = "application/json"
}
client . BasicAuth = request . BasicAuth {
Username : t . User ,
Password : t . Pass ,
}
2020-07-26 05:51:07 -07:00
// Set Nextcloud URL if there is no host
if ! strings . HasPrefix ( client . URL , t . NextcloudURL ) {
client . URL = t . NextcloudURL + "/" + client . URL
}
2020-08-30 04:49:26 -07:00
// Set TLS Config
if t . Config != nil {
client . TLSConfig = t . Config . TLSConfig
}
2020-07-18 07:08:25 -07:00
return & client
}
2020-08-30 06:19:51 -07:00
// GetRooms returns a list of all rooms the user is in
func ( t * TalkUser ) GetRooms ( ) ( * [ ] RoomInfo , error ) {
client := t . RequestClient ( request . Client {
URL : ocsRoomsEndpoint ,
} )
res , err := client . Do ( )
if err != nil {
return nil , err
}
var roomsRequest struct {
OCS struct {
Data [ ] RoomInfo ` json:"data" `
} ` json:"ocs" `
}
err = json . Unmarshal ( res . Data , & roomsRequest )
if err != nil {
return nil , err
}
return & roomsRequest . OCS . Data , nil
}
2020-07-18 07:08:25 -07:00
// Capabilities returns an instance of Capabilities that describes what the Nextcloud Talk instance supports
func ( t * TalkUser ) Capabilities ( ) ( * Capabilities , error ) {
if t . capabilities != nil {
return t . capabilities , nil
}
client := t . RequestClient ( request . Client {
URL : ocsCapabilitiesEndpoint ,
} )
res , err := client . Do ( )
if err != nil {
return nil , err
}
2020-07-26 05:51:07 -07:00
capabilitiesRequest := & struct {
Ocs ocs . Capabilities ` json:"ocs" `
} { }
err = json . Unmarshal ( res . Data , capabilitiesRequest )
2020-07-18 07:08:25 -07:00
if err != nil {
return nil , err
}
2020-07-26 05:51:07 -07:00
sc := capabilitiesRequest . Ocs . Data . Capabilities . SpreedCapabilities
2020-07-18 07:08:25 -07:00
2020-07-26 05:51:07 -07:00
tr := & Capabilities {
Audio : sliceContains ( sc . Features , "audio" ) ,
Video : sliceContains ( sc . Features , "video" ) ,
Chat : sliceContains ( sc . Features , "chat" ) ,
GuestSignaling : sliceContains ( sc . Features , "guest-signaling" ) ,
EmptyGroupRoom : sliceContains ( sc . Features , "empty-group-room" ) ,
GuestDisplayNames : sliceContains ( sc . Features , "guest-display-names" ) ,
MultiRoomUsers : sliceContains ( sc . Features , "multi-room-users" ) ,
ChatV2 : sliceContains ( sc . Features , "chat-v2" ) ,
Favorites : sliceContains ( sc . Features , "favorites" ) ,
LastRoomActivity : sliceContains ( sc . Features , "last-room-activity" ) ,
NoPing : sliceContains ( sc . Features , "no-ping" ) ,
SystemMessages : sliceContains ( sc . Features , "system-messages" ) ,
MentionFlag : sliceContains ( sc . Features , "mention-flag" ) ,
InCallFlags : sliceContains ( sc . Features , "in-call-flags" ) ,
InviteByMail : sliceContains ( sc . Features , "invite-by-mail" ) ,
NotificationLevels : sliceContains ( sc . Features , "notification-levels" ) ,
InviteGroupsAndMails : sliceContains ( sc . Features , "invite-groups-and-mails" ) ,
LockedOneToOneRooms : sliceContains ( sc . Features , "locked-one-to-one-rooms" ) ,
ReadOnlyRooms : sliceContains ( sc . Features , "read-only-rooms" ) ,
ChatReadMarker : sliceContains ( sc . Features , "chat-read-marker" ) ,
WebinaryLobby : sliceContains ( sc . Features , "webinary-lobby" ) ,
StartCallFlag : sliceContains ( sc . Features , "start-call-flag" ) ,
ChatReplies : sliceContains ( sc . Features , "chat-replies" ) ,
CirclesSupport : sliceContains ( sc . Features , "circles-support" ) ,
AttachmentsAllowed : sc . Config . Attachments . Allowed ,
AttachmentsFolder : sc . Config . Attachments . Folder ,
ConversationsCanCreate : sc . Config . Conversations . CanCreate ,
ForceMute : sliceContains ( sc . Features , "force-mute" ) ,
ConversationV2 : sliceContains ( sc . Features , "conversation-v2" ) ,
ChatReferenceID : sliceContains ( sc . Features , "chat-reference-id" ) ,
ChatMaxLength : sc . Config . Chat . MaxLength ,
2020-07-18 07:08:25 -07:00
}
t . capabilities = tr
return tr , nil
}
2020-07-26 05:51:07 -07:00
// sliceContains does the slice contain the string
func sliceContains ( s [ ] string , search string ) bool {
for _ , n := range s {
if n == search {
return true
}
}
return false
}
2020-08-30 04:49:26 -07:00
// DownloadFile downloads the file at the given path
//
// Meant to be used with rich object string's path.
func ( t * TalkUser ) DownloadFile ( path string ) ( data * [ ] byte , err error ) {
url := t . NextcloudURL + constants . RemoteDavEndpoint ( t . User , "files" ) + path
c := t . RequestClient ( request . Client {
URL : url ,
} )
res , err := c . Do ( )
if err != nil || res . StatusCode ( ) != 200 {
return
}
data = & res . Data
return
}