Изучая возможность установить VPN-соединения между двумя компьютерами, находящимися за разными NAT'ами с постоянно меняющимися IP-адресами, возникла идея реализовать это с помощью TeamViewer'а, для чего почитал его API. Нужной информации к сожалению там не оказалось, зато оказались другие интересные возможности, например функции для создания вебинаров или персональных сессий, когда задав некоторые параметры мы получаем ссылки на два приложения: клиентское (кому нужна помощь) и техническое (кто будет помогать), загрузив и запустив которые нет необходимости вводить какие-либо ID, соединение создаётся автоматически. Информации по API TeamViewer'а в сети близко к нулю, а потому думаю для многих это будет интересная тема.
В мануале приводится следующее описание API:
The TeamViewer API is a REST API which uses the already existing HTTP methods to create (POST), read (GET), change (PUT) or delete (DELETE) single items or a collection of items. The following table shows the general use cases for these HTTP methods.
GET POST PUT DELETE Collection retrieve list of items in this col-lection create new item in this collection - - Single item retrieve item data - changes the item deletes this item GET POST PUT DELETE Collection Collection retrieve list of items in this col-lection create new item in this collection - - Single item Single item retrieve item data - changes the item deletes this item
The basic URI scheme for all API functions is: https://host/path/to/re-sources[/id][/verb][?param1=value1]
The TeamViewer API can be found at https://webapi.teamviewer.com/api/v1
Parameters in the URI are only allowed for GET and DELETE. Generally there should be no need for any pa-rameters for DELETE, though. POST and PUT need to have the parameters in the body formatted as JSON or XML.
То есть, отправляем HTTP-запрос с параметрами в url или в формате json, в ответ получаем код ошибки или результат выполнения запроса в формате json. Авторизация производится через OAuth или с помощью токена, который можно получить в консоли управления на сайте.
Бесплатным пользователям доступны следующие функции API:
- управление учётной записью
- управление сеансами
- управление группами
- управление конференциями
- управление устройствами
- управление контактами
Для примера работы с API посмотрим два примера: работа с устройствами и работа с конференциями.
Запрос к API формируется классом Connection (практически полная копия RestConnection из примера с сайта TeamViewer'а), который строит запрос на основании данных класса Properties (RestProperties).
namespace TeamViewerApi.TeamViewer.Rest { using System; using System.Net; public class Properties { private string accessToken; private string url; private TeamViewer.Method method; private string postData; private string contentType; public Properties(string accessToken, string url, TeamViewer.Method method) { this.accessToken = accessToken; this.url = url; this.method = method; this.contentType = TeamViewer.HttpContentType.Json; } public Properties(string accessToken, string url, TeamViewer.Method method, string postData) : this(accessToken, url, method) { this.postData = postData; } public string AccessToken { get { return this.accessToken; } } public string URL { get { return this.url; } } public TeamViewer.Method Method { get { return this.method; } } public string PostData { get { return this.postData; } } public string ContentType { get { return this.contentType; } } } }
namespace TeamViewerApi.TeamViewer.Rest
{
using System;
using System.IO;
using System.Net;
using System.Text;
public class Connection
{
private Properties properties;
public Connection(Properties properties)
{
this.properties = properties;
}
///<summary>
/// Send request
///</summary>
///<returns>Result of the request</returns>
public string SendRequest()
{
HttpWebRequest webReq = (HttpWebRequest)WebRequest.Create(this.properties.URL);
webReq.ContentType = this.properties.ContentType;
webReq.Method = this.properties.Method.ToString();
webReq.Headers.Add("Authorization", string.Format("Bearer {0}", this.properties.AccessToken));
if ((webReq.Method == WebRequestMethods.Http.Post || webReq.Method == WebRequestMethods.Http.Put) && !string.IsNullOrEmpty(this.properties.PostData))
{
var postBytes = Encoding.UTF8.GetBytes(this.properties.PostData);
webReq.ContentLength = postBytes.Length;
Stream stream = webReq.GetRequestStream();
stream.Write(postBytes, 0, postBytes.Length);
stream.Close();
}
string result = string.Empty;
try
{
HttpWebResponse response = (HttpWebResponse)webReq.GetResponse();
using (Stream stream = response.GetResponseStream())
if (stream != null)
using (StreamReader streamReader = new StreamReader(stream))
result = streamReader.ReadToEnd();
}
catch (WebException ex)
{
System.Diagnostics.Debug.WriteLine("{0:hh:mm:ss} TeamViewer.Rest.SendRequest: {1} {2} => {3}", DateTime.Now, this.properties.Method.ToString(), this.properties.URL, ex.Message);
}
return result;
}
}
}
Работа с устройствами
Для управления устройствами сделаем 2 класса: Devices и Device. Класс Devices будет иметь всего одно свойство Items - массив элементов Device и метод GetDevices для получения всего списка устройств.
namespace TeamViewerApi.TeamViewer
{
using System;
using System.Net;
using System.Runtime.Serialization;
using Newtonsoft.Json;
[DataContract]
public class Devices
{
[DataMember(Name = "devices")]
public Device[] Items { get; set; }
///<summary>
/// Get devices for the account
///</summary>
///<param name="accessToken">Token</param>
public static Devices GetDevices(string accessToken, string state = "", string groupId = "")
{
string url = TeamViewer.URL.DEVICES;
if (!string.IsNullOrEmpty(state))
url += "?online_state=" + state;
if (!string.IsNullOrEmpty(groupId))
{
if (!string.IsNullOrEmpty(state))
url += "&groupid=true";
else
url += "?groupid=true";
}
Rest.Properties properties = new Rest.Properties(accessToken, url, TeamViewer.Method.GET);
Rest.Connection connection = new Rest.Connection(properties);
string json = connection.SendRequest();
Devices result = null;
if (!string.IsNullOrEmpty(json))
result = JsonConvert.DeserializeObject<Devices>(json);
return result;
}
}
}
Класс Device содержит все свойства устройства и методы сохранения изменений, добавления и удаления устройства.
namespace TeamViewerApi.TeamViewer
{
using System;
using System.Net;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Helpers;
[DataContract]
public class Device
{
private static IgnorableSerializerContractResolver jsonSaveResolver;
private static IgnorableSerializerContractResolver jsonCreateResolver;
static Device()
{
Device.jsonSaveResolver = new IgnorableSerializerContractResolver();
Device.jsonSaveResolver.Ignore(typeof(Device), "device_id");
Device.jsonSaveResolver.Ignore(typeof(Device), "remotecontrol_id");
Device.jsonSaveResolver.Ignore(typeof(Device), "online_state");
Device.jsonSaveResolver.Ignore(typeof(Device), "supported_features");
Device.jsonCreateResolver = new IgnorableSerializerContractResolver();
Device.jsonCreateResolver.Ignore(typeof(Device), "device_id");
Device.jsonCreateResolver.Ignore(typeof(Device), "online_state");
Device.jsonCreateResolver.Ignore(typeof(Device), "supported_features");
}
[DataMember(Name = "device_id")]
public string DeviceId { get; set; }
[DataMember(Name = "remotecontrol_id")]
public string RemoteControlId { get; set; }
[DataMember(Name = "groupid")]
public string GroupId { get; set; }
[DataMember(Name = "alias")]
public string Alias { get; set; }
[DataMember(Name = "password")]
public string Password { get; set; }
[DataMember(Name = "Description")]
public string Description { get; set; }
[DataMember(Name = "online_state")]
public string Online { get; set; }
[DataMember(Name = "supported_features")]
public string Features { get; set; }
///<summary>
/// Save device changes
///</summary>
///<param name="accessToken">Token</param>
///<param name="device">Device</param>
public static void SaveDevice(string accessToken, Device device)
{
string json = JsonConvert.SerializeObject(device, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, ContractResolver = Device.jsonSaveResolver });
string url = string.Format("{0}/{1}", TeamViewer.URL.DEVICES, device.DeviceId);
Rest.Properties properties = new Rest.Properties(accessToken, url, TeamViewer.Method.PUT, json);
Rest.Connection connection = new Rest.Connection(properties);
connection.SendRequest();
}
///<summary>
/// Create new device
///</summary>
///<param name="accessToken">Token</param>
///<param name="device">Device</param>
public static Device CreateDevice(string accessToken, Device device)
{
string json = JsonConvert.SerializeObject(device, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, ContractResolver = Device.jsonCreateResolver });
Rest.Properties properties = new Rest.Properties(accessToken, TeamViewer.URL.DEVICES, TeamViewer.Method.POST, json);
Rest.Connection connection = new Rest.Connection(properties);
json = connection.SendRequest();
Device result = null;
if (!string.IsNullOrEmpty(json))
result = JsonConvert.DeserializeObject<Device>(json);
return result;
}
///<summary>
/// Delete device
///</summary>
///<param name="accessToken">Token</param>
///<param name="device">Device</param>
public static void DeleteDevice(string accessToken, Device device)
{
string url = string.Format("{0}/{1}", TeamViewer.URL.DEVICES, device.DeviceId);
Rest.Properties properties = new Rest.Properties(accessToken, url, TeamViewer.Method.DELETE);
Rest.Connection connection = new Rest.Connection(properties);
connection.SendRequest();
}
}
}
Работа с конференциями
Также понадобится 2 класса: Meeting и Meetings для хранения массива элементов Meeting
namespace TeamViewerApi.TeamViewer { using System; using System.Net; using System.Runtime.Serialization; using Newtonsoft.Json; [DataContract] public class Meetings { [DataMember(Name = "meetings")] public Meeting[] Items { get; set; } ///<summary> /// Get meetings for the account ///</summary> ///<param name="accessToken">Token</param> public static Meetings GetMeetings(string accessToken, DateTime? dateStart = null, DateTime? dateEnd = null) { string url = TeamViewer.URL.MEETINGS; if (dateStart.HasValue) url += "?from_date=" + dateStart.Value.ToUniversalTime().ToString("yyyy-MM-dd"); if (dateEnd.HasValue) { if (dateStart.HasValue) url += "&to_date=" + dateEnd.Value.ToUniversalTime().ToString("yyyy-MM-dd"); else url += "?to_date=" + dateEnd.Value.ToUniversalTime().ToString("yyyy-MM-dd"); } Rest.Properties properties = new Rest.Properties(accessToken, url, Rest.Method.GET); Rest.Connection connection = new Rest.Connection(properties); string json = connection.SendRequest(); Meetings result = null; if (!string.IsNullOrEmpty(json)) result = JsonConvert.DeserializeObject<Meetings>(json); return result; } } }
namespace TeamViewerApi.TeamViewer
{
using System;
using System.ComponentModel;
using System.Net;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using Helpers;
[DataContract]
public class Meeting
{
private static IgnorableSerializerContractResolver jsonSaveResolver;
private static IgnorableSerializerContractResolver jsonCreateResolver;
static Meeting()
{
Meeting.jsonSaveResolver = new IgnorableSerializerContractResolver();
Meeting.jsonSaveResolver.Ignore(typeof(Meeting), "id");
Meeting.jsonSaveResolver.Ignore(typeof(Meeting), "participant_web_link");
Meeting.jsonCreateResolver = new IgnorableSerializerContractResolver();
Meeting.jsonCreateResolver.Ignore(typeof(Meeting), "id");
Meeting.jsonCreateResolver.Ignore(typeof(Meeting), "participant_web_link");
}
[DataMember(Name = "id")]
public string MeetingId { get; set; }
[DataMember(Name = "subject")]
public string Subject { get; set; }
public DateTime? DateStart { get; set; }
[DataMember(Name = "start")]
public string DateStartString
{
get
{
if (!this.DateStart.HasValue)
return null;
else
return this.DateStart.Value.ToUniversalTime().ToString("yyyy-MM-ddThh:mm:ssZ");
}
set
{
if (value == null)
this.DateStart = null;
else
{
DateTime temp;
if (DateTime.TryParse(value, out temp))
this.DateStart = temp;
}
}
}
public DateTime? DateEnd { get; set; }
[DataMember(Name = "end")]
public string DateEndString
{
get
{
if (!this.DateEnd.HasValue)
return null;
else
return this.DateEnd.Value.ToUniversalTime().ToString("yyyy-MM-ddThh:mm:ssZ");
}
set
{
if (value == null)
this.DateEnd = null;
else
{
DateTime temp;
if (DateTime.TryParse(value, out temp))
this.DateEnd = temp;
}
}
}
[DataMember(Name = "password")]
public string Password { get; set; }
[DataMember(Name = "conference_call_information")]
public ConferenceCallInformation ConferenceInformation { get; set; }
[DataMember(Name = "participant_web_link")]
public string Link { get; set; }
///<summary>
/// Get meeting
///</summary>
///<param name="accessToken">Token</param>
///<param name="meetingId">Meeting Id</param>
public static Meeting GetMeeting(string accessToken, string meetingId)
{
string url = string.Format("{0}/{1}", TeamViewer.URL.MEETINGS, meetingId);
Rest.Properties properties = new Rest.Properties(accessToken, url, Rest.Method.GET);
Rest.Connection connection = new Rest.Connection(properties);
string json = connection.SendRequest();
Meeting result = null;
if (!string.IsNullOrEmpty(json))
result = JsonConvert.DeserializeObject<Meeting>(json);
return result;
}
///<summary>
/// Save changes in a meeting
///</summary>
///<param name="accessToken">Token</param>
///<param name="meeting">Meeting</param>
public static void SaveMeeting(string accessToken, Meeting meeting)
{
string json = JsonConvert.SerializeObject(meeting, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, ContractResolver = Meeting.jsonSaveResolver });
string url = string.Format("{0}/{1}", TeamViewer.URL.MEETINGS, meeting.MeetingId);
Rest.Properties properties = new Rest.Properties(accessToken, url, Rest.Method.PUT, json);
Rest.Connection connection = new Rest.Connection(properties);
connection.SendRequest();
}
///<summary>
/// Creates new meeting
///</summary>
///<param name="accessToken">Token</param>
///<param name="meeting">Meeting</param>
public static Meeting CreateMeeting(string accessToken, Meeting meeting)
{
string json = JsonConvert.SerializeObject(meeting, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore, ContractResolver = Meeting.jsonCreateResolver });
Rest.Properties properties = new Rest.Properties(accessToken, TeamViewer.URL.MEETINGS, Rest.Method.POST, json);
Rest.Connection connection = new Rest.Connection(properties);
json = connection.SendRequest();
Meeting result = null;
if (!string.IsNullOrEmpty(json))
result = JsonConvert.DeserializeObject<Meeting>(json);
return result;
}
///<summary>
/// Creates new instant meeting
///</summary>
///<param name="accessToken">Token</param>
public static Meeting CreateInstantMeeting(string accessToken)
{
string json = "{\"instant\":true}";
Rest.Properties properties = new Rest.Properties(accessToken, TeamViewer.URL.MEETINGS, Rest.Method.POST, json);
Rest.Connection connection = new Rest.Connection(properties);
json = connection.SendRequest();
Meeting result = null;
if (!string.IsNullOrEmpty(json))
result = JsonConvert.DeserializeObject<Meeting>(json);
return result;
}
///<summary>
/// Delete meeting
///</summary>
///<param name="accessToken">Token</param>
///<param name="meeting">Meeting</param>
public static void DeleteMeeting(string accessToken, Meeting meeting)
{
string url = string.Format("{0}/{1}", TeamViewer.URL.MEETINGS, meeting.MeetingId);
Rest.Properties properties = new Rest.Properties(accessToken, url, Rest.Method.DELETE);
Rest.Connection connection = new Rest.Connection(properties);
connection.SendRequest();
}
///<summary>
/// Get meeting invitation text
///</summary>
///<param name="accessToken">Token</param>
///<param name="meeting">Meeting</param>
public static string GetInvitationText(string accessToken, string meetingId, string language = "", string timezone = "")
{
string url = string.Format("{0}/{1}/invitation", TeamViewer.URL.MEETINGS, meetingId);
if (!string.IsNullOrWhiteSpace(language))
url += "?language=" + language;
if (!string.IsNullOrWhiteSpace(timezone))
{
if (!string.IsNullOrWhiteSpace(language))
url += "&timezone=" + timezone;
else
url += "?timezone=" + timezone;
}
Rest.Properties properties = new Rest.Properties(accessToken, url, Rest.Method.GET);
Rest.Connection connection = new Rest.Connection(properties);
string json = connection.SendRequest();
if (!string.IsNullOrEmpty(json))
{
Invitation invitation = JsonConvert.DeserializeObject<Invitation>(json);
if (invitation != null)
return invitation.Text;
}
return string.Empty;
}
}
}
Конференции могут быть двух типов: запланированные и "мгновенные" (instant). Запланированные могут иметь тему конференции, дату начала, окончания, пароль для присоединения. Мгновенные конференции не имеют ни одного из этих свойств и при создании они не сохраняются в списке конференций аккаунта.
Во вложении находится программа реализующая основные возможности API.