1 // OpenTween - Client of Twitter
2 // Copyright (c) 2013 kim_upsilon (@kim_upsilon) <https://upsilo.net/~upsilon/>
3 // All rights reserved.
5 // This file is part of OpenTween.
7 // This program is free software; you can redistribute it and/or modify it
8 // under the terms of the GNU General Public License as published by the Free
9 // Software Foundation; either version 3 of the License, or (at your option)
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14 // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
17 // You should have received a copy of the GNU General Public License along
18 // with this program. If not, see <http://www.gnu.org/licenses/>, or write to
19 // the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
20 // Boston, MA 02110-1301, USA.
25 using System.Collections.Concurrent;
26 using System.Collections.Generic;
27 using System.Collections;
29 using System.Runtime.Serialization.Json;
32 using System.Xml.Linq;
33 using System.Xml.XPath;
34 using OpenTween.Api.DataModel;
35 using System.Net.Http.Headers;
37 namespace OpenTween.Api
39 public class TwitterApiStatus
41 public TwitterApiAccessLevel AccessLevel { get; set; }
42 public EndpointLimits AccessLimit { get; }
43 public ApiLimit? MediaUploadLimit { get; set; }
45 public class AccessLimitUpdatedEventArgs : EventArgs
47 public string? EndpointName { get; }
49 public AccessLimitUpdatedEventArgs(string? endpointName)
50 => this.EndpointName = endpointName;
52 public event EventHandler<AccessLimitUpdatedEventArgs> AccessLimitUpdated;
54 public TwitterApiStatus()
55 => this.AccessLimit = new EndpointLimits(this);
59 this.AccessLevel = TwitterApiAccessLevel.Anonymous;
60 this.AccessLimit.Clear();
61 this.MediaUploadLimit = null;
64 internal static ApiLimit? ParseRateLimit(IDictionary<string, string> header, string prefix)
66 var limitCount = (int?)ParseHeaderValue(header, prefix + "Limit");
67 var limitRemain = (int?)ParseHeaderValue(header, prefix + "Remaining");
68 var limitReset = ParseHeaderValue(header, prefix + "Reset");
70 if (limitCount == null || limitRemain == null || limitReset == null)
73 var limitResetDate = DateTimeUtc.FromUnixTime(limitReset.Value);
74 return new ApiLimit(limitCount.Value, limitRemain.Value, limitResetDate);
77 internal static TwitterApiAccessLevel? ParseAccessLevel(IDictionary<string, string> header, string headerName)
79 if (!header.ContainsKey(headerName))
83 if (string.IsNullOrEmpty(header[headerName]))
86 switch (header[headerName])
88 case "read-write-directmessages":
89 case "read-write-privatemessages":
90 return TwitterApiAccessLevel.ReadWriteAndDirectMessage;
92 return TwitterApiAccessLevel.ReadWrite;
94 return TwitterApiAccessLevel.Read;
96 MyCommon.TraceOut("Unknown ApiAccessLevel:" + header[headerName]);
97 return TwitterApiAccessLevel.ReadWriteAndDirectMessage;
101 internal static long? ParseHeaderValue(IDictionary<string, string> dict, params string[] keys)
103 foreach (var key in keys)
105 if (!dict.ContainsKey(key)) continue;
107 if (long.TryParse(dict[key], out var result))
114 public void UpdateFromHeader(HttpResponseHeaders header, string endpointName)
115 => this.UpdateFromHeader(header.ToDictionary(x => x.Key, x => string.Join(",", x.Value), StringComparer.OrdinalIgnoreCase), endpointName);
117 public void UpdateFromHeader(IDictionary<string, string> header, string endpointName)
119 var rateLimit = TwitterApiStatus.ParseRateLimit(header, "X-Rate-Limit-");
120 if (rateLimit != null)
121 this.AccessLimit[endpointName] = rateLimit;
123 var mediaLimit = TwitterApiStatus.ParseRateLimit(header, "X-MediaRateLimit-");
124 if (mediaLimit != null)
125 this.MediaUploadLimit = mediaLimit;
127 var accessLevel = TwitterApiStatus.ParseAccessLevel(header, "X-Access-Level");
128 if (accessLevel.HasValue)
129 this.AccessLevel = accessLevel.Value;
132 public void UpdateFromJson(TwitterRateLimits json)
135 from res in json.Resources
136 from item in res.Value
138 EndpointName: item.Key,
141 item.Value.Remaining,
142 DateTimeUtc.FromUnixTime(item.Value.Reset)
146 this.AccessLimit.AddAll(rateLimits.ToDictionary(x => x.EndpointName, x => x.Limit));
149 protected virtual void OnAccessLimitUpdated(AccessLimitUpdatedEventArgs e)
150 => this.AccessLimitUpdated?.Invoke(this, e);
152 public class EndpointLimits : IEnumerable<KeyValuePair<string, ApiLimit>>
154 public TwitterApiStatus Owner { get; }
156 private readonly ConcurrentDictionary<string, ApiLimit> innerDict
157 = new ConcurrentDictionary<string, ApiLimit>();
159 public EndpointLimits(TwitterApiStatus owner)
160 => this.Owner = owner;
162 public ApiLimit? this[string endpoint]
164 get => this.innerDict.TryGetValue(endpoint, out var limit) ? limit : null;
168 this.innerDict.TryRemove(endpoint, out var _);
170 this.innerDict[endpoint] = value;
172 this.Owner.OnAccessLimitUpdated(new AccessLimitUpdatedEventArgs(endpoint));
178 this.innerDict.Clear();
179 this.Owner.OnAccessLimitUpdated(new AccessLimitUpdatedEventArgs(null));
182 public void AddAll(IDictionary<string, ApiLimit> resources)
184 foreach (var (key, value) in resources)
186 this.innerDict[key] = value;
189 this.Owner.OnAccessLimitUpdated(new AccessLimitUpdatedEventArgs(null));
192 public IEnumerator<KeyValuePair<string, ApiLimit>> GetEnumerator()
193 => this.innerDict.GetEnumerator();
195 IEnumerator IEnumerable.GetEnumerator()
196 => this.GetEnumerator();