// OpenTween - Client of Twitter // Copyright (c) 2013 kim_upsilon (@kim_upsilon) // All rights reserved. // // This file is part of OpenTween. // // This program is free software; you can redistribute it and/or modify it // under the terms of the GNU General Public License as published by the Free // Software Foundation; either version 3 of the License, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, but // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License // for more details. // // You should have received a copy of the GNU General Public License along // with this program. If not, see , or write to // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor, // Boston, MA 02110-1301, USA. using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections; using System.Linq; using System.Runtime.Serialization.Json; using System.Text; using System.Xml; using System.Xml.Linq; using System.Xml.XPath; using OpenTween.Api.DataModel; using System.Net.Http.Headers; namespace OpenTween.Api { public class TwitterApiStatus { public TwitterApiAccessLevel AccessLevel { get; set; } public EndpointLimits AccessLimit { get; } public ApiLimit MediaUploadLimit { get; set; } public class AccessLimitUpdatedEventArgs : EventArgs { public string EndpointName { get; } public AccessLimitUpdatedEventArgs(string endpointName) { this.EndpointName = endpointName; } } public event EventHandler AccessLimitUpdated; public TwitterApiStatus() { this.AccessLimit = new EndpointLimits(this); } public void Reset() { this.AccessLevel = TwitterApiAccessLevel.Anonymous; this.AccessLimit.Clear(); this.MediaUploadLimit = null; } internal static ApiLimit ParseRateLimit(IDictionary header, string prefix) { var limitCount = (int?)ParseHeaderValue(header, prefix + "Limit"); var limitRemain = (int?)ParseHeaderValue(header, prefix + "Remaining"); var limitReset = ParseHeaderValue(header, prefix + "Reset"); if (limitCount == null || limitRemain == null || limitReset == null) return null; var limitResetDate = UnixEpoch.AddSeconds(limitReset.Value).ToLocalTime(); return new ApiLimit(limitCount.Value, limitRemain.Value, limitResetDate); } internal static TwitterApiAccessLevel? ParseAccessLevel(IDictionary header, string headerName) { if (!header.ContainsKey(headerName)) return null; // たまに出てくる空文字列は無視する if (string.IsNullOrEmpty(header[headerName])) return null; switch (header[headerName]) { case "read-write-directmessages": case "read-write-privatemessages": return TwitterApiAccessLevel.ReadWriteAndDirectMessage; case "read-write": return TwitterApiAccessLevel.ReadWrite; case "read": return TwitterApiAccessLevel.Read; default: MyCommon.TraceOut("Unknown ApiAccessLevel:" + header[headerName]); return TwitterApiAccessLevel.ReadWriteAndDirectMessage; } } internal static long? ParseHeaderValue(IDictionary dict, params string[] keys) { foreach (var key in keys) { if (!dict.ContainsKey(key)) continue; long result; if (long.TryParse(dict[key], out result)) return result; } return null; } public void UpdateFromHeader(HttpResponseHeaders header, string endpointName) { this.UpdateFromHeader(header.ToDictionary(x => x.Key, x => string.Join(",", x.Value)), endpointName); } public void UpdateFromHeader(IDictionary header, string endpointName) { var rateLimit = TwitterApiStatus.ParseRateLimit(header, "X-Rate-Limit-"); if (rateLimit != null) this.AccessLimit[endpointName] = rateLimit; var mediaLimit = TwitterApiStatus.ParseRateLimit(header, "X-MediaRateLimit-"); if (mediaLimit != null) this.MediaUploadLimit = mediaLimit; var accessLevel = TwitterApiStatus.ParseAccessLevel(header, "X-Access-Level"); if (accessLevel.HasValue) this.AccessLevel = accessLevel.Value; } private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); public void UpdateFromJson(string json) { using (var jsonReader = JsonReaderWriterFactory.CreateJsonReader(Encoding.UTF8.GetBytes(json), XmlDictionaryReaderQuotas.Max)) { var xElm = XElement.Load(jsonReader); XNamespace a = "item"; var q = from res in xElm.Element("resources").Descendants(a + "item") // a:item 要素を列挙 select new { endpointName = res.Attribute("item").Value, limit = new ApiLimit( int.Parse(res.Element("limit").Value), int.Parse(res.Element("remaining").Value), UnixEpoch.AddSeconds(long.Parse(res.Element("reset").Value)).ToLocalTime() ), }; this.AccessLimit.AddAll(q.ToDictionary(x => x.endpointName, x => x.limit)); } } protected virtual void OnAccessLimitUpdated(AccessLimitUpdatedEventArgs e) { this.AccessLimitUpdated?.Invoke(this, e); } public class EndpointLimits : IEnumerable> { public TwitterApiStatus Owner { get; } private ConcurrentDictionary innerDict = new ConcurrentDictionary(); public EndpointLimits(TwitterApiStatus owner) { this.Owner = owner; } public ApiLimit this[string endpoint] { get { ApiLimit limit; return this.innerDict.TryGetValue(endpoint, out limit) ? limit : null; } set { this.innerDict[endpoint] = value; this.Owner.OnAccessLimitUpdated(new AccessLimitUpdatedEventArgs(endpoint)); } } public void Clear() { this.innerDict.Clear(); this.Owner.OnAccessLimitUpdated(new AccessLimitUpdatedEventArgs(null)); } public void AddAll(IDictionary resources) { foreach (var res in resources) { this.innerDict[res.Key] = res.Value; } this.Owner.OnAccessLimitUpdated(new AccessLimitUpdatedEventArgs(null)); } public IEnumerator> GetEnumerator() { return this.innerDict.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } } } }