├── .gitignore
├── LICENSE
├── NdgrClientSharp.Protocol
├── NdgrClientSharp.Protocol.csproj
└── src
│ └── generated
│ ├── Atoms.g.cs
│ ├── Message.g.cs
│ ├── Moderator.g.cs
│ ├── Origin.g.cs
│ ├── Payload.g.cs
│ └── State.g.cs
├── NdgrClientSharp.Test
├── GlobalUsings.cs
├── NdgrClientSharp.Test.csproj
├── NdgrLiveCommentFetcherSpec.cs
├── NdgrPastCommentFetcherSpec.cs
└── NdgrProtobufStreamReaderSpec.cs
├── NdgrClientSharp.sln
├── NdgrClientSharp
├── NdgrApi
│ ├── INdgrApiClient.cs
│ └── NdgrApiClient.cs
├── NdgrClientSharp.csproj
├── NdgrLiveCommentFetcher.cs
├── NdgrPastCommentFetcher.cs
├── NdgrSnapshotFetcher.cs
└── Utilities
│ ├── NdgrProtobufStreamReader.cs
│ ├── PooledArray.cs
│ └── PooledBuffer.cs
├── README.md
└── Sandbox
├── Sandbox.cs
└── Sandbox.csproj
/.gitignore:
--------------------------------------------------------------------------------
1 | # User-specific files
2 | *.suo
3 | *.user
4 | *.sln.docstates
5 |
6 | # Build results
7 |
8 | .idea/
9 | [Dd]ebug/
10 | [Rr]elease/
11 | x64/
12 | [Bb]in/
13 | [Oo]bj/
14 |
15 | # MSTest test Results
16 | [Tt]est[Rr]esult*/
17 | [Bb]uild[Ll]og.*
18 |
19 | *_i.c
20 | *_p.c
21 | *_i.h
22 | *.ilk
23 | *.meta
24 | *.obj
25 | *.pch
26 | *.pdb
27 | *.pgc
28 | *.pgd
29 | *.rsp
30 | *.sbr
31 | *.tlb
32 | *.tli
33 | *.tlh
34 | *.tmp
35 | *.tmp_proj
36 | *.log
37 | *.vspscc
38 | *.vssscc
39 | .builds
40 | *.pidb
41 | *.log
42 | *.svclog
43 | *.scc
44 |
45 | # Visual C++ cache files
46 | ipch/
47 | *.aps
48 | *.ncb
49 | *.opensdf
50 | *.sdf
51 | *.cachefile
52 |
53 | # Visual Studio profiler
54 | *.psess
55 | *.vsp
56 | *.vspx
57 |
58 | # Guidance Automation Toolkit
59 | *.gpState
60 |
61 | # ReSharper is a .NET coding add-in
62 | _ReSharper*/
63 | *.[Rr]e[Ss]harper
64 | *.DotSettings.user
65 |
66 | # Click-Once directory
67 | publish/
68 |
69 | # Publish Web Output
70 | *.Publish.xml
71 | *.pubxml
72 | *.azurePubxml
73 |
74 | # NuGet Packages Directory
75 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line
76 | packages/
77 | ## TODO: If the tool you use requires repositories.config, also uncomment the next line
78 | !packages/repositories.config
79 |
80 | # Windows Azure Build Output
81 | csx/
82 | *.build.csdef
83 |
84 | # Windows Store app package directory
85 | AppPackages/
86 |
87 | # Others
88 | sql/
89 | *.Cache
90 | ClientBin/
91 | [Ss]tyle[Cc]op.*
92 | ![Ss]tyle[Cc]op.targets
93 | ~$*
94 | *~
95 | *.dbmdl
96 | *.[Pp]ublish.xml
97 |
98 | *.publishsettings
99 |
100 | # RIA/Silverlight projects
101 | Generated_Code/
102 |
103 | # Backup & report files from converting an old project file to a newer
104 | # Visual Studio version. Backup files are not needed, because we have git ;-)
105 | _UpgradeReport_Files/
106 | Backup*/
107 | UpgradeLog*.XML
108 | UpgradeLog*.htm
109 |
110 | # SQL Server files
111 | App_Data/*.mdf
112 | App_Data/*.ldf
113 |
114 | # =========================
115 | # Windows detritus
116 | # =========================
117 |
118 | # Windows image file caches
119 | Thumbs.db
120 | ehthumbs.db
121 |
122 | # Folder config file
123 | Desktop.ini
124 |
125 | # Recycle Bin used on file shares
126 | $RECYCLE.BIN/
127 |
128 | # Mac desktop service store files
129 | .DS_Store
130 |
131 | _NCrunch*
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 TORISOUP
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
--------------------------------------------------------------------------------
/NdgrClientSharp.Protocol/NdgrClientSharp.Protocol.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | enable
5 | NdgrClientSharp.Protocol
6 | netstandard2.1;netstandard2.0
7 | 8
8 | 0.1.7
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/NdgrClientSharp.Protocol/src/generated/Message.g.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Generated by the protocol buffer compiler. DO NOT EDIT!
3 | // source: dwango/nicolive/chat/data/message.proto
4 | //
5 | #pragma warning disable 1591, 0612, 3021, 8981
6 | #region Designer generated code
7 |
8 | using pb = global::Google.Protobuf;
9 | using pbc = global::Google.Protobuf.Collections;
10 | using pbr = global::Google.Protobuf.Reflection;
11 | using scg = global::System.Collections.Generic;
12 | namespace Dwango.Nicolive.Chat.Data {
13 |
14 | /// Holder for reflection information generated from dwango/nicolive/chat/data/message.proto
15 | public static partial class MessageReflection {
16 |
17 | #region Descriptor
18 | /// File descriptor for dwango/nicolive/chat/data/message.proto
19 | public static pbr::FileDescriptor Descriptor {
20 | get { return descriptor; }
21 | }
22 | private static pbr::FileDescriptor descriptor;
23 |
24 | static MessageReflection() {
25 | byte[] descriptorData = global::System.Convert.FromBase64String(
26 | string.Concat(
27 | "Cidkd2FuZ28vbmljb2xpdmUvY2hhdC9kYXRhL21lc3NhZ2UucHJvdG8SGWR3",
28 | "YW5nby5uaWNvbGl2ZS5jaGF0LmRhdGEaJWR3YW5nby9uaWNvbGl2ZS9jaGF0",
29 | "L2RhdGEvYXRvbXMucHJvdG8aL2R3YW5nby9uaWNvbGl2ZS9jaGF0L2RhdGEv",
30 | "YXRvbXMvbW9kZXJhdG9yLnByb3RvIt4ECg9OaWNvbGl2ZU1lc3NhZ2USLwoE",
31 | "Y2hhdBgBIAEoCzIfLmR3YW5nby5uaWNvbGl2ZS5jaGF0LmRhdGEuQ2hhdEgA",
32 | "EkwKE3NpbXBsZV9ub3RpZmljYXRpb24YByABKAsyLS5kd2FuZ28ubmljb2xp",
33 | "dmUuY2hhdC5kYXRhLlNpbXBsZU5vdGlmaWNhdGlvbkgAEi8KBGdpZnQYCCAB",
34 | "KAsyHy5kd2FuZ28ubmljb2xpdmUuY2hhdC5kYXRhLkdpZnRIABIzCgZuaWNv",
35 | "YWQYCSABKAsyIS5kd2FuZ28ubmljb2xpdmUuY2hhdC5kYXRhLk5pY29hZEgA",
36 | "EjwKC2dhbWVfdXBkYXRlGA0gASgLMiUuZHdhbmdvLm5pY29saXZlLmNoYXQu",
37 | "ZGF0YS5HYW1lVXBkYXRlSAASPAoLdGFnX3VwZGF0ZWQYESABKAsyJS5kd2Fu",
38 | "Z28ubmljb2xpdmUuY2hhdC5kYXRhLlRhZ1VwZGF0ZWRIABJOChFtb2RlcmF0",
39 | "b3JfdXBkYXRlZBgSIAEoCzIxLmR3YW5nby5uaWNvbGl2ZS5jaGF0LmRhdGEu",
40 | "YXRvbXMuTW9kZXJhdG9yVXBkYXRlZEgAEkQKDHNzbmdfdXBkYXRlZBgTIAEo",
41 | "CzIsLmR3YW5nby5uaWNvbGl2ZS5jaGF0LmRhdGEuYXRvbXMuU1NOR1VwZGF0",
42 | "ZWRIABI6Cg9vdmVyZmxvd2VkX2NoYXQYFCABKAsyHy5kd2FuZ28ubmljb2xp",
43 | "dmUuY2hhdC5kYXRhLkNoYXRIAEIGCgRkYXRhSgQIAhAHSgQIChANSgQIDhAR",
44 | "YgZwcm90bzM="));
45 | descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
46 | new pbr::FileDescriptor[] { global::Dwango.Nicolive.Chat.Data.AtomsReflection.Descriptor, global::Dwango.Nicolive.Chat.Data.Atoms.ModeratorReflection.Descriptor, },
47 | new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
48 | new pbr::GeneratedClrTypeInfo(typeof(global::Dwango.Nicolive.Chat.Data.NicoliveMessage), global::Dwango.Nicolive.Chat.Data.NicoliveMessage.Parser, new[]{ "Chat", "SimpleNotification", "Gift", "Nicoad", "GameUpdate", "TagUpdated", "ModeratorUpdated", "SsngUpdated", "OverflowedChat" }, new[]{ "Data" }, null, null, null)
49 | }));
50 | }
51 | #endregion
52 |
53 | }
54 | #region Messages
55 | [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
56 | public sealed partial class NicoliveMessage : pb::IMessage
57 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
58 | , pb::IBufferMessage
59 | #endif
60 | {
61 | private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new NicoliveMessage());
62 | private pb::UnknownFieldSet _unknownFields;
63 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
64 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
65 | public static pb::MessageParser Parser { get { return _parser; } }
66 |
67 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
68 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
69 | public static pbr::MessageDescriptor Descriptor {
70 | get { return global::Dwango.Nicolive.Chat.Data.MessageReflection.Descriptor.MessageTypes[0]; }
71 | }
72 |
73 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
74 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
75 | pbr::MessageDescriptor pb::IMessage.Descriptor {
76 | get { return Descriptor; }
77 | }
78 |
79 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
80 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
81 | public NicoliveMessage() {
82 | OnConstruction();
83 | }
84 |
85 | partial void OnConstruction();
86 |
87 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
88 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
89 | public NicoliveMessage(NicoliveMessage other) : this() {
90 | switch (other.DataCase) {
91 | case DataOneofCase.Chat:
92 | Chat = other.Chat.Clone();
93 | break;
94 | case DataOneofCase.SimpleNotification:
95 | SimpleNotification = other.SimpleNotification.Clone();
96 | break;
97 | case DataOneofCase.Gift:
98 | Gift = other.Gift.Clone();
99 | break;
100 | case DataOneofCase.Nicoad:
101 | Nicoad = other.Nicoad.Clone();
102 | break;
103 | case DataOneofCase.GameUpdate:
104 | GameUpdate = other.GameUpdate.Clone();
105 | break;
106 | case DataOneofCase.TagUpdated:
107 | TagUpdated = other.TagUpdated.Clone();
108 | break;
109 | case DataOneofCase.ModeratorUpdated:
110 | ModeratorUpdated = other.ModeratorUpdated.Clone();
111 | break;
112 | case DataOneofCase.SsngUpdated:
113 | SsngUpdated = other.SsngUpdated.Clone();
114 | break;
115 | case DataOneofCase.OverflowedChat:
116 | OverflowedChat = other.OverflowedChat.Clone();
117 | break;
118 | }
119 |
120 | _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
121 | }
122 |
123 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
124 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
125 | public NicoliveMessage Clone() {
126 | return new NicoliveMessage(this);
127 | }
128 |
129 | /// Field number for the "chat" field.
130 | public const int ChatFieldNumber = 1;
131 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
132 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
133 | public global::Dwango.Nicolive.Chat.Data.Chat Chat {
134 | get { return dataCase_ == DataOneofCase.Chat ? (global::Dwango.Nicolive.Chat.Data.Chat) data_ : null; }
135 | set {
136 | data_ = value;
137 | dataCase_ = value == null ? DataOneofCase.None : DataOneofCase.Chat;
138 | }
139 | }
140 |
141 | /// Field number for the "simple_notification" field.
142 | public const int SimpleNotificationFieldNumber = 7;
143 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
144 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
145 | public global::Dwango.Nicolive.Chat.Data.SimpleNotification SimpleNotification {
146 | get { return dataCase_ == DataOneofCase.SimpleNotification ? (global::Dwango.Nicolive.Chat.Data.SimpleNotification) data_ : null; }
147 | set {
148 | data_ = value;
149 | dataCase_ = value == null ? DataOneofCase.None : DataOneofCase.SimpleNotification;
150 | }
151 | }
152 |
153 | /// Field number for the "gift" field.
154 | public const int GiftFieldNumber = 8;
155 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
156 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
157 | public global::Dwango.Nicolive.Chat.Data.Gift Gift {
158 | get { return dataCase_ == DataOneofCase.Gift ? (global::Dwango.Nicolive.Chat.Data.Gift) data_ : null; }
159 | set {
160 | data_ = value;
161 | dataCase_ = value == null ? DataOneofCase.None : DataOneofCase.Gift;
162 | }
163 | }
164 |
165 | /// Field number for the "nicoad" field.
166 | public const int NicoadFieldNumber = 9;
167 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
168 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
169 | public global::Dwango.Nicolive.Chat.Data.Nicoad Nicoad {
170 | get { return dataCase_ == DataOneofCase.Nicoad ? (global::Dwango.Nicolive.Chat.Data.Nicoad) data_ : null; }
171 | set {
172 | data_ = value;
173 | dataCase_ = value == null ? DataOneofCase.None : DataOneofCase.Nicoad;
174 | }
175 | }
176 |
177 | /// Field number for the "game_update" field.
178 | public const int GameUpdateFieldNumber = 13;
179 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
180 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
181 | public global::Dwango.Nicolive.Chat.Data.GameUpdate GameUpdate {
182 | get { return dataCase_ == DataOneofCase.GameUpdate ? (global::Dwango.Nicolive.Chat.Data.GameUpdate) data_ : null; }
183 | set {
184 | data_ = value;
185 | dataCase_ = value == null ? DataOneofCase.None : DataOneofCase.GameUpdate;
186 | }
187 | }
188 |
189 | /// Field number for the "tag_updated" field.
190 | public const int TagUpdatedFieldNumber = 17;
191 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
192 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
193 | public global::Dwango.Nicolive.Chat.Data.TagUpdated TagUpdated {
194 | get { return dataCase_ == DataOneofCase.TagUpdated ? (global::Dwango.Nicolive.Chat.Data.TagUpdated) data_ : null; }
195 | set {
196 | data_ = value;
197 | dataCase_ = value == null ? DataOneofCase.None : DataOneofCase.TagUpdated;
198 | }
199 | }
200 |
201 | /// Field number for the "moderator_updated" field.
202 | public const int ModeratorUpdatedFieldNumber = 18;
203 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
204 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
205 | public global::Dwango.Nicolive.Chat.Data.Atoms.ModeratorUpdated ModeratorUpdated {
206 | get { return dataCase_ == DataOneofCase.ModeratorUpdated ? (global::Dwango.Nicolive.Chat.Data.Atoms.ModeratorUpdated) data_ : null; }
207 | set {
208 | data_ = value;
209 | dataCase_ = value == null ? DataOneofCase.None : DataOneofCase.ModeratorUpdated;
210 | }
211 | }
212 |
213 | /// Field number for the "ssng_updated" field.
214 | public const int SsngUpdatedFieldNumber = 19;
215 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
216 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
217 | public global::Dwango.Nicolive.Chat.Data.Atoms.SSNGUpdated SsngUpdated {
218 | get { return dataCase_ == DataOneofCase.SsngUpdated ? (global::Dwango.Nicolive.Chat.Data.Atoms.SSNGUpdated) data_ : null; }
219 | set {
220 | data_ = value;
221 | dataCase_ = value == null ? DataOneofCase.None : DataOneofCase.SsngUpdated;
222 | }
223 | }
224 |
225 | /// Field number for the "overflowed_chat" field.
226 | public const int OverflowedChatFieldNumber = 20;
227 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
228 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
229 | public global::Dwango.Nicolive.Chat.Data.Chat OverflowedChat {
230 | get { return dataCase_ == DataOneofCase.OverflowedChat ? (global::Dwango.Nicolive.Chat.Data.Chat) data_ : null; }
231 | set {
232 | data_ = value;
233 | dataCase_ = value == null ? DataOneofCase.None : DataOneofCase.OverflowedChat;
234 | }
235 | }
236 |
237 | private object data_;
238 | /// Enum of possible cases for the "data" oneof.
239 | public enum DataOneofCase {
240 | None = 0,
241 | Chat = 1,
242 | SimpleNotification = 7,
243 | Gift = 8,
244 | Nicoad = 9,
245 | GameUpdate = 13,
246 | TagUpdated = 17,
247 | ModeratorUpdated = 18,
248 | SsngUpdated = 19,
249 | OverflowedChat = 20,
250 | }
251 | private DataOneofCase dataCase_ = DataOneofCase.None;
252 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
253 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
254 | public DataOneofCase DataCase {
255 | get { return dataCase_; }
256 | }
257 |
258 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
259 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
260 | public void ClearData() {
261 | dataCase_ = DataOneofCase.None;
262 | data_ = null;
263 | }
264 |
265 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
266 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
267 | public override bool Equals(object other) {
268 | return Equals(other as NicoliveMessage);
269 | }
270 |
271 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
272 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
273 | public bool Equals(NicoliveMessage other) {
274 | if (ReferenceEquals(other, null)) {
275 | return false;
276 | }
277 | if (ReferenceEquals(other, this)) {
278 | return true;
279 | }
280 | if (!object.Equals(Chat, other.Chat)) return false;
281 | if (!object.Equals(SimpleNotification, other.SimpleNotification)) return false;
282 | if (!object.Equals(Gift, other.Gift)) return false;
283 | if (!object.Equals(Nicoad, other.Nicoad)) return false;
284 | if (!object.Equals(GameUpdate, other.GameUpdate)) return false;
285 | if (!object.Equals(TagUpdated, other.TagUpdated)) return false;
286 | if (!object.Equals(ModeratorUpdated, other.ModeratorUpdated)) return false;
287 | if (!object.Equals(SsngUpdated, other.SsngUpdated)) return false;
288 | if (!object.Equals(OverflowedChat, other.OverflowedChat)) return false;
289 | if (DataCase != other.DataCase) return false;
290 | return Equals(_unknownFields, other._unknownFields);
291 | }
292 |
293 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
294 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
295 | public override int GetHashCode() {
296 | int hash = 1;
297 | if (dataCase_ == DataOneofCase.Chat) hash ^= Chat.GetHashCode();
298 | if (dataCase_ == DataOneofCase.SimpleNotification) hash ^= SimpleNotification.GetHashCode();
299 | if (dataCase_ == DataOneofCase.Gift) hash ^= Gift.GetHashCode();
300 | if (dataCase_ == DataOneofCase.Nicoad) hash ^= Nicoad.GetHashCode();
301 | if (dataCase_ == DataOneofCase.GameUpdate) hash ^= GameUpdate.GetHashCode();
302 | if (dataCase_ == DataOneofCase.TagUpdated) hash ^= TagUpdated.GetHashCode();
303 | if (dataCase_ == DataOneofCase.ModeratorUpdated) hash ^= ModeratorUpdated.GetHashCode();
304 | if (dataCase_ == DataOneofCase.SsngUpdated) hash ^= SsngUpdated.GetHashCode();
305 | if (dataCase_ == DataOneofCase.OverflowedChat) hash ^= OverflowedChat.GetHashCode();
306 | hash ^= (int) dataCase_;
307 | if (_unknownFields != null) {
308 | hash ^= _unknownFields.GetHashCode();
309 | }
310 | return hash;
311 | }
312 |
313 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
314 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
315 | public override string ToString() {
316 | return pb::JsonFormatter.ToDiagnosticString(this);
317 | }
318 |
319 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
320 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
321 | public void WriteTo(pb::CodedOutputStream output) {
322 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
323 | output.WriteRawMessage(this);
324 | #else
325 | if (dataCase_ == DataOneofCase.Chat) {
326 | output.WriteRawTag(10);
327 | output.WriteMessage(Chat);
328 | }
329 | if (dataCase_ == DataOneofCase.SimpleNotification) {
330 | output.WriteRawTag(58);
331 | output.WriteMessage(SimpleNotification);
332 | }
333 | if (dataCase_ == DataOneofCase.Gift) {
334 | output.WriteRawTag(66);
335 | output.WriteMessage(Gift);
336 | }
337 | if (dataCase_ == DataOneofCase.Nicoad) {
338 | output.WriteRawTag(74);
339 | output.WriteMessage(Nicoad);
340 | }
341 | if (dataCase_ == DataOneofCase.GameUpdate) {
342 | output.WriteRawTag(106);
343 | output.WriteMessage(GameUpdate);
344 | }
345 | if (dataCase_ == DataOneofCase.TagUpdated) {
346 | output.WriteRawTag(138, 1);
347 | output.WriteMessage(TagUpdated);
348 | }
349 | if (dataCase_ == DataOneofCase.ModeratorUpdated) {
350 | output.WriteRawTag(146, 1);
351 | output.WriteMessage(ModeratorUpdated);
352 | }
353 | if (dataCase_ == DataOneofCase.SsngUpdated) {
354 | output.WriteRawTag(154, 1);
355 | output.WriteMessage(SsngUpdated);
356 | }
357 | if (dataCase_ == DataOneofCase.OverflowedChat) {
358 | output.WriteRawTag(162, 1);
359 | output.WriteMessage(OverflowedChat);
360 | }
361 | if (_unknownFields != null) {
362 | _unknownFields.WriteTo(output);
363 | }
364 | #endif
365 | }
366 |
367 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
368 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
369 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
370 | void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
371 | if (dataCase_ == DataOneofCase.Chat) {
372 | output.WriteRawTag(10);
373 | output.WriteMessage(Chat);
374 | }
375 | if (dataCase_ == DataOneofCase.SimpleNotification) {
376 | output.WriteRawTag(58);
377 | output.WriteMessage(SimpleNotification);
378 | }
379 | if (dataCase_ == DataOneofCase.Gift) {
380 | output.WriteRawTag(66);
381 | output.WriteMessage(Gift);
382 | }
383 | if (dataCase_ == DataOneofCase.Nicoad) {
384 | output.WriteRawTag(74);
385 | output.WriteMessage(Nicoad);
386 | }
387 | if (dataCase_ == DataOneofCase.GameUpdate) {
388 | output.WriteRawTag(106);
389 | output.WriteMessage(GameUpdate);
390 | }
391 | if (dataCase_ == DataOneofCase.TagUpdated) {
392 | output.WriteRawTag(138, 1);
393 | output.WriteMessage(TagUpdated);
394 | }
395 | if (dataCase_ == DataOneofCase.ModeratorUpdated) {
396 | output.WriteRawTag(146, 1);
397 | output.WriteMessage(ModeratorUpdated);
398 | }
399 | if (dataCase_ == DataOneofCase.SsngUpdated) {
400 | output.WriteRawTag(154, 1);
401 | output.WriteMessage(SsngUpdated);
402 | }
403 | if (dataCase_ == DataOneofCase.OverflowedChat) {
404 | output.WriteRawTag(162, 1);
405 | output.WriteMessage(OverflowedChat);
406 | }
407 | if (_unknownFields != null) {
408 | _unknownFields.WriteTo(ref output);
409 | }
410 | }
411 | #endif
412 |
413 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
414 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
415 | public int CalculateSize() {
416 | int size = 0;
417 | if (dataCase_ == DataOneofCase.Chat) {
418 | size += 1 + pb::CodedOutputStream.ComputeMessageSize(Chat);
419 | }
420 | if (dataCase_ == DataOneofCase.SimpleNotification) {
421 | size += 1 + pb::CodedOutputStream.ComputeMessageSize(SimpleNotification);
422 | }
423 | if (dataCase_ == DataOneofCase.Gift) {
424 | size += 1 + pb::CodedOutputStream.ComputeMessageSize(Gift);
425 | }
426 | if (dataCase_ == DataOneofCase.Nicoad) {
427 | size += 1 + pb::CodedOutputStream.ComputeMessageSize(Nicoad);
428 | }
429 | if (dataCase_ == DataOneofCase.GameUpdate) {
430 | size += 1 + pb::CodedOutputStream.ComputeMessageSize(GameUpdate);
431 | }
432 | if (dataCase_ == DataOneofCase.TagUpdated) {
433 | size += 2 + pb::CodedOutputStream.ComputeMessageSize(TagUpdated);
434 | }
435 | if (dataCase_ == DataOneofCase.ModeratorUpdated) {
436 | size += 2 + pb::CodedOutputStream.ComputeMessageSize(ModeratorUpdated);
437 | }
438 | if (dataCase_ == DataOneofCase.SsngUpdated) {
439 | size += 2 + pb::CodedOutputStream.ComputeMessageSize(SsngUpdated);
440 | }
441 | if (dataCase_ == DataOneofCase.OverflowedChat) {
442 | size += 2 + pb::CodedOutputStream.ComputeMessageSize(OverflowedChat);
443 | }
444 | if (_unknownFields != null) {
445 | size += _unknownFields.CalculateSize();
446 | }
447 | return size;
448 | }
449 |
450 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
451 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
452 | public void MergeFrom(NicoliveMessage other) {
453 | if (other == null) {
454 | return;
455 | }
456 | switch (other.DataCase) {
457 | case DataOneofCase.Chat:
458 | if (Chat == null) {
459 | Chat = new global::Dwango.Nicolive.Chat.Data.Chat();
460 | }
461 | Chat.MergeFrom(other.Chat);
462 | break;
463 | case DataOneofCase.SimpleNotification:
464 | if (SimpleNotification == null) {
465 | SimpleNotification = new global::Dwango.Nicolive.Chat.Data.SimpleNotification();
466 | }
467 | SimpleNotification.MergeFrom(other.SimpleNotification);
468 | break;
469 | case DataOneofCase.Gift:
470 | if (Gift == null) {
471 | Gift = new global::Dwango.Nicolive.Chat.Data.Gift();
472 | }
473 | Gift.MergeFrom(other.Gift);
474 | break;
475 | case DataOneofCase.Nicoad:
476 | if (Nicoad == null) {
477 | Nicoad = new global::Dwango.Nicolive.Chat.Data.Nicoad();
478 | }
479 | Nicoad.MergeFrom(other.Nicoad);
480 | break;
481 | case DataOneofCase.GameUpdate:
482 | if (GameUpdate == null) {
483 | GameUpdate = new global::Dwango.Nicolive.Chat.Data.GameUpdate();
484 | }
485 | GameUpdate.MergeFrom(other.GameUpdate);
486 | break;
487 | case DataOneofCase.TagUpdated:
488 | if (TagUpdated == null) {
489 | TagUpdated = new global::Dwango.Nicolive.Chat.Data.TagUpdated();
490 | }
491 | TagUpdated.MergeFrom(other.TagUpdated);
492 | break;
493 | case DataOneofCase.ModeratorUpdated:
494 | if (ModeratorUpdated == null) {
495 | ModeratorUpdated = new global::Dwango.Nicolive.Chat.Data.Atoms.ModeratorUpdated();
496 | }
497 | ModeratorUpdated.MergeFrom(other.ModeratorUpdated);
498 | break;
499 | case DataOneofCase.SsngUpdated:
500 | if (SsngUpdated == null) {
501 | SsngUpdated = new global::Dwango.Nicolive.Chat.Data.Atoms.SSNGUpdated();
502 | }
503 | SsngUpdated.MergeFrom(other.SsngUpdated);
504 | break;
505 | case DataOneofCase.OverflowedChat:
506 | if (OverflowedChat == null) {
507 | OverflowedChat = new global::Dwango.Nicolive.Chat.Data.Chat();
508 | }
509 | OverflowedChat.MergeFrom(other.OverflowedChat);
510 | break;
511 | }
512 |
513 | _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
514 | }
515 |
516 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
517 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
518 | public void MergeFrom(pb::CodedInputStream input) {
519 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
520 | input.ReadRawMessage(this);
521 | #else
522 | uint tag;
523 | while ((tag = input.ReadTag()) != 0) {
524 | if ((tag & 7) == 4) {
525 | // Abort on any end group tag.
526 | return;
527 | }
528 | switch(tag) {
529 | default:
530 | _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
531 | break;
532 | case 10: {
533 | global::Dwango.Nicolive.Chat.Data.Chat subBuilder = new global::Dwango.Nicolive.Chat.Data.Chat();
534 | if (dataCase_ == DataOneofCase.Chat) {
535 | subBuilder.MergeFrom(Chat);
536 | }
537 | input.ReadMessage(subBuilder);
538 | Chat = subBuilder;
539 | break;
540 | }
541 | case 58: {
542 | global::Dwango.Nicolive.Chat.Data.SimpleNotification subBuilder = new global::Dwango.Nicolive.Chat.Data.SimpleNotification();
543 | if (dataCase_ == DataOneofCase.SimpleNotification) {
544 | subBuilder.MergeFrom(SimpleNotification);
545 | }
546 | input.ReadMessage(subBuilder);
547 | SimpleNotification = subBuilder;
548 | break;
549 | }
550 | case 66: {
551 | global::Dwango.Nicolive.Chat.Data.Gift subBuilder = new global::Dwango.Nicolive.Chat.Data.Gift();
552 | if (dataCase_ == DataOneofCase.Gift) {
553 | subBuilder.MergeFrom(Gift);
554 | }
555 | input.ReadMessage(subBuilder);
556 | Gift = subBuilder;
557 | break;
558 | }
559 | case 74: {
560 | global::Dwango.Nicolive.Chat.Data.Nicoad subBuilder = new global::Dwango.Nicolive.Chat.Data.Nicoad();
561 | if (dataCase_ == DataOneofCase.Nicoad) {
562 | subBuilder.MergeFrom(Nicoad);
563 | }
564 | input.ReadMessage(subBuilder);
565 | Nicoad = subBuilder;
566 | break;
567 | }
568 | case 106: {
569 | global::Dwango.Nicolive.Chat.Data.GameUpdate subBuilder = new global::Dwango.Nicolive.Chat.Data.GameUpdate();
570 | if (dataCase_ == DataOneofCase.GameUpdate) {
571 | subBuilder.MergeFrom(GameUpdate);
572 | }
573 | input.ReadMessage(subBuilder);
574 | GameUpdate = subBuilder;
575 | break;
576 | }
577 | case 138: {
578 | global::Dwango.Nicolive.Chat.Data.TagUpdated subBuilder = new global::Dwango.Nicolive.Chat.Data.TagUpdated();
579 | if (dataCase_ == DataOneofCase.TagUpdated) {
580 | subBuilder.MergeFrom(TagUpdated);
581 | }
582 | input.ReadMessage(subBuilder);
583 | TagUpdated = subBuilder;
584 | break;
585 | }
586 | case 146: {
587 | global::Dwango.Nicolive.Chat.Data.Atoms.ModeratorUpdated subBuilder = new global::Dwango.Nicolive.Chat.Data.Atoms.ModeratorUpdated();
588 | if (dataCase_ == DataOneofCase.ModeratorUpdated) {
589 | subBuilder.MergeFrom(ModeratorUpdated);
590 | }
591 | input.ReadMessage(subBuilder);
592 | ModeratorUpdated = subBuilder;
593 | break;
594 | }
595 | case 154: {
596 | global::Dwango.Nicolive.Chat.Data.Atoms.SSNGUpdated subBuilder = new global::Dwango.Nicolive.Chat.Data.Atoms.SSNGUpdated();
597 | if (dataCase_ == DataOneofCase.SsngUpdated) {
598 | subBuilder.MergeFrom(SsngUpdated);
599 | }
600 | input.ReadMessage(subBuilder);
601 | SsngUpdated = subBuilder;
602 | break;
603 | }
604 | case 162: {
605 | global::Dwango.Nicolive.Chat.Data.Chat subBuilder = new global::Dwango.Nicolive.Chat.Data.Chat();
606 | if (dataCase_ == DataOneofCase.OverflowedChat) {
607 | subBuilder.MergeFrom(OverflowedChat);
608 | }
609 | input.ReadMessage(subBuilder);
610 | OverflowedChat = subBuilder;
611 | break;
612 | }
613 | }
614 | }
615 | #endif
616 | }
617 |
618 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
619 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
620 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
621 | void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
622 | uint tag;
623 | while ((tag = input.ReadTag()) != 0) {
624 | if ((tag & 7) == 4) {
625 | // Abort on any end group tag.
626 | return;
627 | }
628 | switch(tag) {
629 | default:
630 | _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
631 | break;
632 | case 10: {
633 | global::Dwango.Nicolive.Chat.Data.Chat subBuilder = new global::Dwango.Nicolive.Chat.Data.Chat();
634 | if (dataCase_ == DataOneofCase.Chat) {
635 | subBuilder.MergeFrom(Chat);
636 | }
637 | input.ReadMessage(subBuilder);
638 | Chat = subBuilder;
639 | break;
640 | }
641 | case 58: {
642 | global::Dwango.Nicolive.Chat.Data.SimpleNotification subBuilder = new global::Dwango.Nicolive.Chat.Data.SimpleNotification();
643 | if (dataCase_ == DataOneofCase.SimpleNotification) {
644 | subBuilder.MergeFrom(SimpleNotification);
645 | }
646 | input.ReadMessage(subBuilder);
647 | SimpleNotification = subBuilder;
648 | break;
649 | }
650 | case 66: {
651 | global::Dwango.Nicolive.Chat.Data.Gift subBuilder = new global::Dwango.Nicolive.Chat.Data.Gift();
652 | if (dataCase_ == DataOneofCase.Gift) {
653 | subBuilder.MergeFrom(Gift);
654 | }
655 | input.ReadMessage(subBuilder);
656 | Gift = subBuilder;
657 | break;
658 | }
659 | case 74: {
660 | global::Dwango.Nicolive.Chat.Data.Nicoad subBuilder = new global::Dwango.Nicolive.Chat.Data.Nicoad();
661 | if (dataCase_ == DataOneofCase.Nicoad) {
662 | subBuilder.MergeFrom(Nicoad);
663 | }
664 | input.ReadMessage(subBuilder);
665 | Nicoad = subBuilder;
666 | break;
667 | }
668 | case 106: {
669 | global::Dwango.Nicolive.Chat.Data.GameUpdate subBuilder = new global::Dwango.Nicolive.Chat.Data.GameUpdate();
670 | if (dataCase_ == DataOneofCase.GameUpdate) {
671 | subBuilder.MergeFrom(GameUpdate);
672 | }
673 | input.ReadMessage(subBuilder);
674 | GameUpdate = subBuilder;
675 | break;
676 | }
677 | case 138: {
678 | global::Dwango.Nicolive.Chat.Data.TagUpdated subBuilder = new global::Dwango.Nicolive.Chat.Data.TagUpdated();
679 | if (dataCase_ == DataOneofCase.TagUpdated) {
680 | subBuilder.MergeFrom(TagUpdated);
681 | }
682 | input.ReadMessage(subBuilder);
683 | TagUpdated = subBuilder;
684 | break;
685 | }
686 | case 146: {
687 | global::Dwango.Nicolive.Chat.Data.Atoms.ModeratorUpdated subBuilder = new global::Dwango.Nicolive.Chat.Data.Atoms.ModeratorUpdated();
688 | if (dataCase_ == DataOneofCase.ModeratorUpdated) {
689 | subBuilder.MergeFrom(ModeratorUpdated);
690 | }
691 | input.ReadMessage(subBuilder);
692 | ModeratorUpdated = subBuilder;
693 | break;
694 | }
695 | case 154: {
696 | global::Dwango.Nicolive.Chat.Data.Atoms.SSNGUpdated subBuilder = new global::Dwango.Nicolive.Chat.Data.Atoms.SSNGUpdated();
697 | if (dataCase_ == DataOneofCase.SsngUpdated) {
698 | subBuilder.MergeFrom(SsngUpdated);
699 | }
700 | input.ReadMessage(subBuilder);
701 | SsngUpdated = subBuilder;
702 | break;
703 | }
704 | case 162: {
705 | global::Dwango.Nicolive.Chat.Data.Chat subBuilder = new global::Dwango.Nicolive.Chat.Data.Chat();
706 | if (dataCase_ == DataOneofCase.OverflowedChat) {
707 | subBuilder.MergeFrom(OverflowedChat);
708 | }
709 | input.ReadMessage(subBuilder);
710 | OverflowedChat = subBuilder;
711 | break;
712 | }
713 | }
714 | }
715 | }
716 | #endif
717 |
718 | }
719 |
720 | #endregion
721 |
722 | }
723 |
724 | #endregion Designer generated code
725 |
--------------------------------------------------------------------------------
/NdgrClientSharp.Protocol/src/generated/Origin.g.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Generated by the protocol buffer compiler. DO NOT EDIT!
3 | // source: dwango/nicolive/chat/data/origin.proto
4 | //
5 | #pragma warning disable 1591, 0612, 3021, 8981
6 | #region Designer generated code
7 |
8 | using pb = global::Google.Protobuf;
9 | using pbc = global::Google.Protobuf.Collections;
10 | using pbr = global::Google.Protobuf.Reflection;
11 | using scg = global::System.Collections.Generic;
12 | namespace Dwango.Nicolive.Chat.Data {
13 |
14 | /// Holder for reflection information generated from dwango/nicolive/chat/data/origin.proto
15 | public static partial class OriginReflection {
16 |
17 | #region Descriptor
18 | /// File descriptor for dwango/nicolive/chat/data/origin.proto
19 | public static pbr::FileDescriptor Descriptor {
20 | get { return descriptor; }
21 | }
22 | private static pbr::FileDescriptor descriptor;
23 |
24 | static OriginReflection() {
25 | byte[] descriptorData = global::System.Convert.FromBase64String(
26 | string.Concat(
27 | "CiZkd2FuZ28vbmljb2xpdmUvY2hhdC9kYXRhL29yaWdpbi5wcm90bxIZZHdh",
28 | "bmdvLm5pY29saXZlLmNoYXQuZGF0YSJzCg5OaWNvbGl2ZU9yaWdpbhI+CgRj",
29 | "aGF0GAEgASgLMi4uZHdhbmdvLm5pY29saXZlLmNoYXQuZGF0YS5OaWNvbGl2",
30 | "ZU9yaWdpbi5DaGF0SAAaFwoEQ2hhdBIPCgdsaXZlX2lkGAEgASgDQggKBm9y",
31 | "aWdpbmIGcHJvdG8z"));
32 | descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
33 | new pbr::FileDescriptor[] { },
34 | new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
35 | new pbr::GeneratedClrTypeInfo(typeof(global::Dwango.Nicolive.Chat.Data.NicoliveOrigin), global::Dwango.Nicolive.Chat.Data.NicoliveOrigin.Parser, new[]{ "Chat" }, new[]{ "Origin" }, null, null, new pbr::GeneratedClrTypeInfo[] { new pbr::GeneratedClrTypeInfo(typeof(global::Dwango.Nicolive.Chat.Data.NicoliveOrigin.Types.Chat), global::Dwango.Nicolive.Chat.Data.NicoliveOrigin.Types.Chat.Parser, new[]{ "LiveId" }, null, null, null, null)})
36 | }));
37 | }
38 | #endregion
39 |
40 | }
41 | #region Messages
42 | [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
43 | public sealed partial class NicoliveOrigin : pb::IMessage
44 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
45 | , pb::IBufferMessage
46 | #endif
47 | {
48 | private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new NicoliveOrigin());
49 | private pb::UnknownFieldSet _unknownFields;
50 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
51 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
52 | public static pb::MessageParser Parser { get { return _parser; } }
53 |
54 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
55 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
56 | public static pbr::MessageDescriptor Descriptor {
57 | get { return global::Dwango.Nicolive.Chat.Data.OriginReflection.Descriptor.MessageTypes[0]; }
58 | }
59 |
60 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
61 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
62 | pbr::MessageDescriptor pb::IMessage.Descriptor {
63 | get { return Descriptor; }
64 | }
65 |
66 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
67 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
68 | public NicoliveOrigin() {
69 | OnConstruction();
70 | }
71 |
72 | partial void OnConstruction();
73 |
74 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
75 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
76 | public NicoliveOrigin(NicoliveOrigin other) : this() {
77 | switch (other.OriginCase) {
78 | case OriginOneofCase.Chat:
79 | Chat = other.Chat.Clone();
80 | break;
81 | }
82 |
83 | _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
84 | }
85 |
86 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
87 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
88 | public NicoliveOrigin Clone() {
89 | return new NicoliveOrigin(this);
90 | }
91 |
92 | /// Field number for the "chat" field.
93 | public const int ChatFieldNumber = 1;
94 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
95 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
96 | public global::Dwango.Nicolive.Chat.Data.NicoliveOrigin.Types.Chat Chat {
97 | get { return originCase_ == OriginOneofCase.Chat ? (global::Dwango.Nicolive.Chat.Data.NicoliveOrigin.Types.Chat) origin_ : null; }
98 | set {
99 | origin_ = value;
100 | originCase_ = value == null ? OriginOneofCase.None : OriginOneofCase.Chat;
101 | }
102 | }
103 |
104 | private object origin_;
105 | /// Enum of possible cases for the "origin" oneof.
106 | public enum OriginOneofCase {
107 | None = 0,
108 | Chat = 1,
109 | }
110 | private OriginOneofCase originCase_ = OriginOneofCase.None;
111 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
112 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
113 | public OriginOneofCase OriginCase {
114 | get { return originCase_; }
115 | }
116 |
117 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
118 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
119 | public void ClearOrigin() {
120 | originCase_ = OriginOneofCase.None;
121 | origin_ = null;
122 | }
123 |
124 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
125 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
126 | public override bool Equals(object other) {
127 | return Equals(other as NicoliveOrigin);
128 | }
129 |
130 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
131 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
132 | public bool Equals(NicoliveOrigin other) {
133 | if (ReferenceEquals(other, null)) {
134 | return false;
135 | }
136 | if (ReferenceEquals(other, this)) {
137 | return true;
138 | }
139 | if (!object.Equals(Chat, other.Chat)) return false;
140 | if (OriginCase != other.OriginCase) return false;
141 | return Equals(_unknownFields, other._unknownFields);
142 | }
143 |
144 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
145 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
146 | public override int GetHashCode() {
147 | int hash = 1;
148 | if (originCase_ == OriginOneofCase.Chat) hash ^= Chat.GetHashCode();
149 | hash ^= (int) originCase_;
150 | if (_unknownFields != null) {
151 | hash ^= _unknownFields.GetHashCode();
152 | }
153 | return hash;
154 | }
155 |
156 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
157 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
158 | public override string ToString() {
159 | return pb::JsonFormatter.ToDiagnosticString(this);
160 | }
161 |
162 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
163 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
164 | public void WriteTo(pb::CodedOutputStream output) {
165 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
166 | output.WriteRawMessage(this);
167 | #else
168 | if (originCase_ == OriginOneofCase.Chat) {
169 | output.WriteRawTag(10);
170 | output.WriteMessage(Chat);
171 | }
172 | if (_unknownFields != null) {
173 | _unknownFields.WriteTo(output);
174 | }
175 | #endif
176 | }
177 |
178 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
179 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
180 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
181 | void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
182 | if (originCase_ == OriginOneofCase.Chat) {
183 | output.WriteRawTag(10);
184 | output.WriteMessage(Chat);
185 | }
186 | if (_unknownFields != null) {
187 | _unknownFields.WriteTo(ref output);
188 | }
189 | }
190 | #endif
191 |
192 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
193 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
194 | public int CalculateSize() {
195 | int size = 0;
196 | if (originCase_ == OriginOneofCase.Chat) {
197 | size += 1 + pb::CodedOutputStream.ComputeMessageSize(Chat);
198 | }
199 | if (_unknownFields != null) {
200 | size += _unknownFields.CalculateSize();
201 | }
202 | return size;
203 | }
204 |
205 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
206 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
207 | public void MergeFrom(NicoliveOrigin other) {
208 | if (other == null) {
209 | return;
210 | }
211 | switch (other.OriginCase) {
212 | case OriginOneofCase.Chat:
213 | if (Chat == null) {
214 | Chat = new global::Dwango.Nicolive.Chat.Data.NicoliveOrigin.Types.Chat();
215 | }
216 | Chat.MergeFrom(other.Chat);
217 | break;
218 | }
219 |
220 | _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
221 | }
222 |
223 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
224 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
225 | public void MergeFrom(pb::CodedInputStream input) {
226 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
227 | input.ReadRawMessage(this);
228 | #else
229 | uint tag;
230 | while ((tag = input.ReadTag()) != 0) {
231 | if ((tag & 7) == 4) {
232 | // Abort on any end group tag.
233 | return;
234 | }
235 | switch(tag) {
236 | default:
237 | _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
238 | break;
239 | case 10: {
240 | global::Dwango.Nicolive.Chat.Data.NicoliveOrigin.Types.Chat subBuilder = new global::Dwango.Nicolive.Chat.Data.NicoliveOrigin.Types.Chat();
241 | if (originCase_ == OriginOneofCase.Chat) {
242 | subBuilder.MergeFrom(Chat);
243 | }
244 | input.ReadMessage(subBuilder);
245 | Chat = subBuilder;
246 | break;
247 | }
248 | }
249 | }
250 | #endif
251 | }
252 |
253 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
254 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
255 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
256 | void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
257 | uint tag;
258 | while ((tag = input.ReadTag()) != 0) {
259 | if ((tag & 7) == 4) {
260 | // Abort on any end group tag.
261 | return;
262 | }
263 | switch(tag) {
264 | default:
265 | _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
266 | break;
267 | case 10: {
268 | global::Dwango.Nicolive.Chat.Data.NicoliveOrigin.Types.Chat subBuilder = new global::Dwango.Nicolive.Chat.Data.NicoliveOrigin.Types.Chat();
269 | if (originCase_ == OriginOneofCase.Chat) {
270 | subBuilder.MergeFrom(Chat);
271 | }
272 | input.ReadMessage(subBuilder);
273 | Chat = subBuilder;
274 | break;
275 | }
276 | }
277 | }
278 | }
279 | #endif
280 |
281 | #region Nested types
282 | /// Container for nested types declared in the NicoliveOrigin message type.
283 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
284 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
285 | public static partial class Types {
286 | [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
287 | public sealed partial class Chat : pb::IMessage
288 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
289 | , pb::IBufferMessage
290 | #endif
291 | {
292 | private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new Chat());
293 | private pb::UnknownFieldSet _unknownFields;
294 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
295 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
296 | public static pb::MessageParser Parser { get { return _parser; } }
297 |
298 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
299 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
300 | public static pbr::MessageDescriptor Descriptor {
301 | get { return global::Dwango.Nicolive.Chat.Data.NicoliveOrigin.Descriptor.NestedTypes[0]; }
302 | }
303 |
304 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
305 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
306 | pbr::MessageDescriptor pb::IMessage.Descriptor {
307 | get { return Descriptor; }
308 | }
309 |
310 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
311 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
312 | public Chat() {
313 | OnConstruction();
314 | }
315 |
316 | partial void OnConstruction();
317 |
318 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
319 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
320 | public Chat(Chat other) : this() {
321 | liveId_ = other.liveId_;
322 | _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
323 | }
324 |
325 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
326 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
327 | public Chat Clone() {
328 | return new Chat(this);
329 | }
330 |
331 | /// Field number for the "live_id" field.
332 | public const int LiveIdFieldNumber = 1;
333 | private long liveId_;
334 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
335 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
336 | public long LiveId {
337 | get { return liveId_; }
338 | set {
339 | liveId_ = value;
340 | }
341 | }
342 |
343 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
344 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
345 | public override bool Equals(object other) {
346 | return Equals(other as Chat);
347 | }
348 |
349 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
350 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
351 | public bool Equals(Chat other) {
352 | if (ReferenceEquals(other, null)) {
353 | return false;
354 | }
355 | if (ReferenceEquals(other, this)) {
356 | return true;
357 | }
358 | if (LiveId != other.LiveId) return false;
359 | return Equals(_unknownFields, other._unknownFields);
360 | }
361 |
362 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
363 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
364 | public override int GetHashCode() {
365 | int hash = 1;
366 | if (LiveId != 0L) hash ^= LiveId.GetHashCode();
367 | if (_unknownFields != null) {
368 | hash ^= _unknownFields.GetHashCode();
369 | }
370 | return hash;
371 | }
372 |
373 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
374 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
375 | public override string ToString() {
376 | return pb::JsonFormatter.ToDiagnosticString(this);
377 | }
378 |
379 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
380 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
381 | public void WriteTo(pb::CodedOutputStream output) {
382 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
383 | output.WriteRawMessage(this);
384 | #else
385 | if (LiveId != 0L) {
386 | output.WriteRawTag(8);
387 | output.WriteInt64(LiveId);
388 | }
389 | if (_unknownFields != null) {
390 | _unknownFields.WriteTo(output);
391 | }
392 | #endif
393 | }
394 |
395 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
396 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
397 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
398 | void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
399 | if (LiveId != 0L) {
400 | output.WriteRawTag(8);
401 | output.WriteInt64(LiveId);
402 | }
403 | if (_unknownFields != null) {
404 | _unknownFields.WriteTo(ref output);
405 | }
406 | }
407 | #endif
408 |
409 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
410 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
411 | public int CalculateSize() {
412 | int size = 0;
413 | if (LiveId != 0L) {
414 | size += 1 + pb::CodedOutputStream.ComputeInt64Size(LiveId);
415 | }
416 | if (_unknownFields != null) {
417 | size += _unknownFields.CalculateSize();
418 | }
419 | return size;
420 | }
421 |
422 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
423 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
424 | public void MergeFrom(Chat other) {
425 | if (other == null) {
426 | return;
427 | }
428 | if (other.LiveId != 0L) {
429 | LiveId = other.LiveId;
430 | }
431 | _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
432 | }
433 |
434 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
435 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
436 | public void MergeFrom(pb::CodedInputStream input) {
437 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
438 | input.ReadRawMessage(this);
439 | #else
440 | uint tag;
441 | while ((tag = input.ReadTag()) != 0) {
442 | if ((tag & 7) == 4) {
443 | // Abort on any end group tag.
444 | return;
445 | }
446 | switch(tag) {
447 | default:
448 | _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
449 | break;
450 | case 8: {
451 | LiveId = input.ReadInt64();
452 | break;
453 | }
454 | }
455 | }
456 | #endif
457 | }
458 |
459 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
460 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
461 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
462 | void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
463 | uint tag;
464 | while ((tag = input.ReadTag()) != 0) {
465 | if ((tag & 7) == 4) {
466 | // Abort on any end group tag.
467 | return;
468 | }
469 | switch(tag) {
470 | default:
471 | _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
472 | break;
473 | case 8: {
474 | LiveId = input.ReadInt64();
475 | break;
476 | }
477 | }
478 | }
479 | }
480 | #endif
481 |
482 | }
483 |
484 | }
485 | #endregion
486 |
487 | }
488 |
489 | #endregion
490 |
491 | }
492 |
493 | #endregion Designer generated code
494 |
--------------------------------------------------------------------------------
/NdgrClientSharp.Protocol/src/generated/State.g.cs:
--------------------------------------------------------------------------------
1 | //
2 | // Generated by the protocol buffer compiler. DO NOT EDIT!
3 | // source: dwango/nicolive/chat/data/state.proto
4 | //
5 | #pragma warning disable 1591, 0612, 3021, 8981
6 | #region Designer generated code
7 |
8 | using pb = global::Google.Protobuf;
9 | using pbc = global::Google.Protobuf.Collections;
10 | using pbr = global::Google.Protobuf.Reflection;
11 | using scg = global::System.Collections.Generic;
12 | namespace Dwango.Nicolive.Chat.Data {
13 |
14 | /// Holder for reflection information generated from dwango/nicolive/chat/data/state.proto
15 | public static partial class StateReflection {
16 |
17 | #region Descriptor
18 | /// File descriptor for dwango/nicolive/chat/data/state.proto
19 | public static pbr::FileDescriptor Descriptor {
20 | get { return descriptor; }
21 | }
22 | private static pbr::FileDescriptor descriptor;
23 |
24 | static StateReflection() {
25 | byte[] descriptorData = global::System.Convert.FromBase64String(
26 | string.Concat(
27 | "CiVkd2FuZ28vbmljb2xpdmUvY2hhdC9kYXRhL3N0YXRlLnByb3RvEhlkd2Fu",
28 | "Z28ubmljb2xpdmUuY2hhdC5kYXRhGiVkd2FuZ28vbmljb2xpdmUvY2hhdC9k",
29 | "YXRhL2F0b21zLnByb3RvGi9kd2FuZ28vbmljb2xpdmUvY2hhdC9kYXRhL2F0",
30 | "b21zL21vZGVyYXRvci5wcm90byLaBgoNTmljb2xpdmVTdGF0ZRI+CgpzdGF0",
31 | "aXN0aWNzGAEgASgLMiUuZHdhbmdvLm5pY29saXZlLmNoYXQuZGF0YS5TdGF0",
32 | "aXN0aWNzSACIAQESOAoHZW5xdWV0ZRgCIAEoCzIiLmR3YW5nby5uaWNvbGl2",
33 | "ZS5jaGF0LmRhdGEuRW5xdWV0ZUgBiAEBEj0KCm1vdmVfb3JkZXIYAyABKAsy",
34 | "JC5kd2FuZ28ubmljb2xpdmUuY2hhdC5kYXRhLk1vdmVPcmRlckgCiAEBEjgK",
35 | "B21hcnF1ZWUYBCABKAsyIi5kd2FuZ28ubmljb2xpdmUuY2hhdC5kYXRhLk1h",
36 | "cnF1ZWVIA4gBARJBCgxjb21tZW50X2xvY2sYBSABKAsyJi5kd2FuZ28ubmlj",
37 | "b2xpdmUuY2hhdC5kYXRhLkNvbW1lbnRMb2NrSASIAQESQQoMY29tbWVudF9t",
38 | "b2RlGAYgASgLMiYuZHdhbmdvLm5pY29saXZlLmNoYXQuZGF0YS5Db21tZW50",
39 | "TW9kZUgFiAEBEj8KC3RyaWFsX3BhbmVsGAcgASgLMiUuZHdhbmdvLm5pY29s",
40 | "aXZlLmNoYXQuZGF0YS5UcmlhbFBhbmVsSAaIAQESQQoMZmluZ2VyX3ByaW50",
41 | "GAggASgLMiYuZHdhbmdvLm5pY29saXZlLmNoYXQuZGF0YS5GaW5nZXJQcmlu",
42 | "dEgHiAEBEkUKDnByb2dyYW1fc3RhdHVzGAkgASgLMiguZHdhbmdvLm5pY29s",
43 | "aXZlLmNoYXQuZGF0YS5Qcm9ncmFtU3RhdHVzSAiIAQESXQoXbW9kZXJhdGlv",
44 | "bl9hbm5vdW5jZW1lbnQYCiABKAsyNy5kd2FuZ28ubmljb2xpdmUuY2hhdC5k",
45 | "YXRhLmF0b21zLk1vZGVyYXRpb25Bbm5vdW5jZW1lbnRICYgBAUINCgtfc3Rh",
46 | "dGlzdGljc0IKCghfZW5xdWV0ZUINCgtfbW92ZV9vcmRlckIKCghfbWFycXVl",
47 | "ZUIPCg1fY29tbWVudF9sb2NrQg8KDV9jb21tZW50X21vZGVCDgoMX3RyaWFs",
48 | "X3BhbmVsQg8KDV9maW5nZXJfcHJpbnRCEQoPX3Byb2dyYW1fc3RhdHVzQhoK",
49 | "GF9tb2RlcmF0aW9uX2Fubm91bmNlbWVudGIGcHJvdG8z"));
50 | descriptor = pbr::FileDescriptor.FromGeneratedCode(descriptorData,
51 | new pbr::FileDescriptor[] { global::Dwango.Nicolive.Chat.Data.AtomsReflection.Descriptor, global::Dwango.Nicolive.Chat.Data.Atoms.ModeratorReflection.Descriptor, },
52 | new pbr::GeneratedClrTypeInfo(null, null, new pbr::GeneratedClrTypeInfo[] {
53 | new pbr::GeneratedClrTypeInfo(typeof(global::Dwango.Nicolive.Chat.Data.NicoliveState), global::Dwango.Nicolive.Chat.Data.NicoliveState.Parser, new[]{ "Statistics", "Enquete", "MoveOrder", "Marquee", "CommentLock", "CommentMode", "TrialPanel", "FingerPrint", "ProgramStatus", "ModerationAnnouncement" }, new[]{ "Statistics", "Enquete", "MoveOrder", "Marquee", "CommentLock", "CommentMode", "TrialPanel", "FingerPrint", "ProgramStatus", "ModerationAnnouncement" }, null, null, null)
54 | }));
55 | }
56 | #endregion
57 |
58 | }
59 | #region Messages
60 | [global::System.Diagnostics.DebuggerDisplayAttribute("{ToString(),nq}")]
61 | public sealed partial class NicoliveState : pb::IMessage
62 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
63 | , pb::IBufferMessage
64 | #endif
65 | {
66 | private static readonly pb::MessageParser _parser = new pb::MessageParser(() => new NicoliveState());
67 | private pb::UnknownFieldSet _unknownFields;
68 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
69 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
70 | public static pb::MessageParser Parser { get { return _parser; } }
71 |
72 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
73 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
74 | public static pbr::MessageDescriptor Descriptor {
75 | get { return global::Dwango.Nicolive.Chat.Data.StateReflection.Descriptor.MessageTypes[0]; }
76 | }
77 |
78 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
79 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
80 | pbr::MessageDescriptor pb::IMessage.Descriptor {
81 | get { return Descriptor; }
82 | }
83 |
84 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
85 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
86 | public NicoliveState() {
87 | OnConstruction();
88 | }
89 |
90 | partial void OnConstruction();
91 |
92 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
93 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
94 | public NicoliveState(NicoliveState other) : this() {
95 | statistics_ = other.statistics_ != null ? other.statistics_.Clone() : null;
96 | enquete_ = other.enquete_ != null ? other.enquete_.Clone() : null;
97 | moveOrder_ = other.moveOrder_ != null ? other.moveOrder_.Clone() : null;
98 | marquee_ = other.marquee_ != null ? other.marquee_.Clone() : null;
99 | commentLock_ = other.commentLock_ != null ? other.commentLock_.Clone() : null;
100 | commentMode_ = other.commentMode_ != null ? other.commentMode_.Clone() : null;
101 | trialPanel_ = other.trialPanel_ != null ? other.trialPanel_.Clone() : null;
102 | fingerPrint_ = other.fingerPrint_ != null ? other.fingerPrint_.Clone() : null;
103 | programStatus_ = other.programStatus_ != null ? other.programStatus_.Clone() : null;
104 | moderationAnnouncement_ = other.moderationAnnouncement_ != null ? other.moderationAnnouncement_.Clone() : null;
105 | _unknownFields = pb::UnknownFieldSet.Clone(other._unknownFields);
106 | }
107 |
108 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
109 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
110 | public NicoliveState Clone() {
111 | return new NicoliveState(this);
112 | }
113 |
114 | /// Field number for the "statistics" field.
115 | public const int StatisticsFieldNumber = 1;
116 | private global::Dwango.Nicolive.Chat.Data.Statistics statistics_;
117 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
118 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
119 | public global::Dwango.Nicolive.Chat.Data.Statistics Statistics {
120 | get { return statistics_; }
121 | set {
122 | statistics_ = value;
123 | }
124 | }
125 |
126 | /// Field number for the "enquete" field.
127 | public const int EnqueteFieldNumber = 2;
128 | private global::Dwango.Nicolive.Chat.Data.Enquete enquete_;
129 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
130 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
131 | public global::Dwango.Nicolive.Chat.Data.Enquete Enquete {
132 | get { return enquete_; }
133 | set {
134 | enquete_ = value;
135 | }
136 | }
137 |
138 | /// Field number for the "move_order" field.
139 | public const int MoveOrderFieldNumber = 3;
140 | private global::Dwango.Nicolive.Chat.Data.MoveOrder moveOrder_;
141 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
142 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
143 | public global::Dwango.Nicolive.Chat.Data.MoveOrder MoveOrder {
144 | get { return moveOrder_; }
145 | set {
146 | moveOrder_ = value;
147 | }
148 | }
149 |
150 | /// Field number for the "marquee" field.
151 | public const int MarqueeFieldNumber = 4;
152 | private global::Dwango.Nicolive.Chat.Data.Marquee marquee_;
153 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
154 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
155 | public global::Dwango.Nicolive.Chat.Data.Marquee Marquee {
156 | get { return marquee_; }
157 | set {
158 | marquee_ = value;
159 | }
160 | }
161 |
162 | /// Field number for the "comment_lock" field.
163 | public const int CommentLockFieldNumber = 5;
164 | private global::Dwango.Nicolive.Chat.Data.CommentLock commentLock_;
165 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
166 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
167 | public global::Dwango.Nicolive.Chat.Data.CommentLock CommentLock {
168 | get { return commentLock_; }
169 | set {
170 | commentLock_ = value;
171 | }
172 | }
173 |
174 | /// Field number for the "comment_mode" field.
175 | public const int CommentModeFieldNumber = 6;
176 | private global::Dwango.Nicolive.Chat.Data.CommentMode commentMode_;
177 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
178 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
179 | public global::Dwango.Nicolive.Chat.Data.CommentMode CommentMode {
180 | get { return commentMode_; }
181 | set {
182 | commentMode_ = value;
183 | }
184 | }
185 |
186 | /// Field number for the "trial_panel" field.
187 | public const int TrialPanelFieldNumber = 7;
188 | private global::Dwango.Nicolive.Chat.Data.TrialPanel trialPanel_;
189 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
190 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
191 | public global::Dwango.Nicolive.Chat.Data.TrialPanel TrialPanel {
192 | get { return trialPanel_; }
193 | set {
194 | trialPanel_ = value;
195 | }
196 | }
197 |
198 | /// Field number for the "finger_print" field.
199 | public const int FingerPrintFieldNumber = 8;
200 | private global::Dwango.Nicolive.Chat.Data.FingerPrint fingerPrint_;
201 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
202 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
203 | public global::Dwango.Nicolive.Chat.Data.FingerPrint FingerPrint {
204 | get { return fingerPrint_; }
205 | set {
206 | fingerPrint_ = value;
207 | }
208 | }
209 |
210 | /// Field number for the "program_status" field.
211 | public const int ProgramStatusFieldNumber = 9;
212 | private global::Dwango.Nicolive.Chat.Data.ProgramStatus programStatus_;
213 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
214 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
215 | public global::Dwango.Nicolive.Chat.Data.ProgramStatus ProgramStatus {
216 | get { return programStatus_; }
217 | set {
218 | programStatus_ = value;
219 | }
220 | }
221 |
222 | /// Field number for the "moderation_announcement" field.
223 | public const int ModerationAnnouncementFieldNumber = 10;
224 | private global::Dwango.Nicolive.Chat.Data.Atoms.ModerationAnnouncement moderationAnnouncement_;
225 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
226 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
227 | public global::Dwango.Nicolive.Chat.Data.Atoms.ModerationAnnouncement ModerationAnnouncement {
228 | get { return moderationAnnouncement_; }
229 | set {
230 | moderationAnnouncement_ = value;
231 | }
232 | }
233 |
234 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
235 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
236 | public override bool Equals(object other) {
237 | return Equals(other as NicoliveState);
238 | }
239 |
240 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
241 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
242 | public bool Equals(NicoliveState other) {
243 | if (ReferenceEquals(other, null)) {
244 | return false;
245 | }
246 | if (ReferenceEquals(other, this)) {
247 | return true;
248 | }
249 | if (!object.Equals(Statistics, other.Statistics)) return false;
250 | if (!object.Equals(Enquete, other.Enquete)) return false;
251 | if (!object.Equals(MoveOrder, other.MoveOrder)) return false;
252 | if (!object.Equals(Marquee, other.Marquee)) return false;
253 | if (!object.Equals(CommentLock, other.CommentLock)) return false;
254 | if (!object.Equals(CommentMode, other.CommentMode)) return false;
255 | if (!object.Equals(TrialPanel, other.TrialPanel)) return false;
256 | if (!object.Equals(FingerPrint, other.FingerPrint)) return false;
257 | if (!object.Equals(ProgramStatus, other.ProgramStatus)) return false;
258 | if (!object.Equals(ModerationAnnouncement, other.ModerationAnnouncement)) return false;
259 | return Equals(_unknownFields, other._unknownFields);
260 | }
261 |
262 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
263 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
264 | public override int GetHashCode() {
265 | int hash = 1;
266 | if (statistics_ != null) hash ^= Statistics.GetHashCode();
267 | if (enquete_ != null) hash ^= Enquete.GetHashCode();
268 | if (moveOrder_ != null) hash ^= MoveOrder.GetHashCode();
269 | if (marquee_ != null) hash ^= Marquee.GetHashCode();
270 | if (commentLock_ != null) hash ^= CommentLock.GetHashCode();
271 | if (commentMode_ != null) hash ^= CommentMode.GetHashCode();
272 | if (trialPanel_ != null) hash ^= TrialPanel.GetHashCode();
273 | if (fingerPrint_ != null) hash ^= FingerPrint.GetHashCode();
274 | if (programStatus_ != null) hash ^= ProgramStatus.GetHashCode();
275 | if (moderationAnnouncement_ != null) hash ^= ModerationAnnouncement.GetHashCode();
276 | if (_unknownFields != null) {
277 | hash ^= _unknownFields.GetHashCode();
278 | }
279 | return hash;
280 | }
281 |
282 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
283 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
284 | public override string ToString() {
285 | return pb::JsonFormatter.ToDiagnosticString(this);
286 | }
287 |
288 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
289 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
290 | public void WriteTo(pb::CodedOutputStream output) {
291 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
292 | output.WriteRawMessage(this);
293 | #else
294 | if (statistics_ != null) {
295 | output.WriteRawTag(10);
296 | output.WriteMessage(Statistics);
297 | }
298 | if (enquete_ != null) {
299 | output.WriteRawTag(18);
300 | output.WriteMessage(Enquete);
301 | }
302 | if (moveOrder_ != null) {
303 | output.WriteRawTag(26);
304 | output.WriteMessage(MoveOrder);
305 | }
306 | if (marquee_ != null) {
307 | output.WriteRawTag(34);
308 | output.WriteMessage(Marquee);
309 | }
310 | if (commentLock_ != null) {
311 | output.WriteRawTag(42);
312 | output.WriteMessage(CommentLock);
313 | }
314 | if (commentMode_ != null) {
315 | output.WriteRawTag(50);
316 | output.WriteMessage(CommentMode);
317 | }
318 | if (trialPanel_ != null) {
319 | output.WriteRawTag(58);
320 | output.WriteMessage(TrialPanel);
321 | }
322 | if (fingerPrint_ != null) {
323 | output.WriteRawTag(66);
324 | output.WriteMessage(FingerPrint);
325 | }
326 | if (programStatus_ != null) {
327 | output.WriteRawTag(74);
328 | output.WriteMessage(ProgramStatus);
329 | }
330 | if (moderationAnnouncement_ != null) {
331 | output.WriteRawTag(82);
332 | output.WriteMessage(ModerationAnnouncement);
333 | }
334 | if (_unknownFields != null) {
335 | _unknownFields.WriteTo(output);
336 | }
337 | #endif
338 | }
339 |
340 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
341 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
342 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
343 | void pb::IBufferMessage.InternalWriteTo(ref pb::WriteContext output) {
344 | if (statistics_ != null) {
345 | output.WriteRawTag(10);
346 | output.WriteMessage(Statistics);
347 | }
348 | if (enquete_ != null) {
349 | output.WriteRawTag(18);
350 | output.WriteMessage(Enquete);
351 | }
352 | if (moveOrder_ != null) {
353 | output.WriteRawTag(26);
354 | output.WriteMessage(MoveOrder);
355 | }
356 | if (marquee_ != null) {
357 | output.WriteRawTag(34);
358 | output.WriteMessage(Marquee);
359 | }
360 | if (commentLock_ != null) {
361 | output.WriteRawTag(42);
362 | output.WriteMessage(CommentLock);
363 | }
364 | if (commentMode_ != null) {
365 | output.WriteRawTag(50);
366 | output.WriteMessage(CommentMode);
367 | }
368 | if (trialPanel_ != null) {
369 | output.WriteRawTag(58);
370 | output.WriteMessage(TrialPanel);
371 | }
372 | if (fingerPrint_ != null) {
373 | output.WriteRawTag(66);
374 | output.WriteMessage(FingerPrint);
375 | }
376 | if (programStatus_ != null) {
377 | output.WriteRawTag(74);
378 | output.WriteMessage(ProgramStatus);
379 | }
380 | if (moderationAnnouncement_ != null) {
381 | output.WriteRawTag(82);
382 | output.WriteMessage(ModerationAnnouncement);
383 | }
384 | if (_unknownFields != null) {
385 | _unknownFields.WriteTo(ref output);
386 | }
387 | }
388 | #endif
389 |
390 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
391 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
392 | public int CalculateSize() {
393 | int size = 0;
394 | if (statistics_ != null) {
395 | size += 1 + pb::CodedOutputStream.ComputeMessageSize(Statistics);
396 | }
397 | if (enquete_ != null) {
398 | size += 1 + pb::CodedOutputStream.ComputeMessageSize(Enquete);
399 | }
400 | if (moveOrder_ != null) {
401 | size += 1 + pb::CodedOutputStream.ComputeMessageSize(MoveOrder);
402 | }
403 | if (marquee_ != null) {
404 | size += 1 + pb::CodedOutputStream.ComputeMessageSize(Marquee);
405 | }
406 | if (commentLock_ != null) {
407 | size += 1 + pb::CodedOutputStream.ComputeMessageSize(CommentLock);
408 | }
409 | if (commentMode_ != null) {
410 | size += 1 + pb::CodedOutputStream.ComputeMessageSize(CommentMode);
411 | }
412 | if (trialPanel_ != null) {
413 | size += 1 + pb::CodedOutputStream.ComputeMessageSize(TrialPanel);
414 | }
415 | if (fingerPrint_ != null) {
416 | size += 1 + pb::CodedOutputStream.ComputeMessageSize(FingerPrint);
417 | }
418 | if (programStatus_ != null) {
419 | size += 1 + pb::CodedOutputStream.ComputeMessageSize(ProgramStatus);
420 | }
421 | if (moderationAnnouncement_ != null) {
422 | size += 1 + pb::CodedOutputStream.ComputeMessageSize(ModerationAnnouncement);
423 | }
424 | if (_unknownFields != null) {
425 | size += _unknownFields.CalculateSize();
426 | }
427 | return size;
428 | }
429 |
430 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
431 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
432 | public void MergeFrom(NicoliveState other) {
433 | if (other == null) {
434 | return;
435 | }
436 | if (other.statistics_ != null) {
437 | if (statistics_ == null) {
438 | Statistics = new global::Dwango.Nicolive.Chat.Data.Statistics();
439 | }
440 | Statistics.MergeFrom(other.Statistics);
441 | }
442 | if (other.enquete_ != null) {
443 | if (enquete_ == null) {
444 | Enquete = new global::Dwango.Nicolive.Chat.Data.Enquete();
445 | }
446 | Enquete.MergeFrom(other.Enquete);
447 | }
448 | if (other.moveOrder_ != null) {
449 | if (moveOrder_ == null) {
450 | MoveOrder = new global::Dwango.Nicolive.Chat.Data.MoveOrder();
451 | }
452 | MoveOrder.MergeFrom(other.MoveOrder);
453 | }
454 | if (other.marquee_ != null) {
455 | if (marquee_ == null) {
456 | Marquee = new global::Dwango.Nicolive.Chat.Data.Marquee();
457 | }
458 | Marquee.MergeFrom(other.Marquee);
459 | }
460 | if (other.commentLock_ != null) {
461 | if (commentLock_ == null) {
462 | CommentLock = new global::Dwango.Nicolive.Chat.Data.CommentLock();
463 | }
464 | CommentLock.MergeFrom(other.CommentLock);
465 | }
466 | if (other.commentMode_ != null) {
467 | if (commentMode_ == null) {
468 | CommentMode = new global::Dwango.Nicolive.Chat.Data.CommentMode();
469 | }
470 | CommentMode.MergeFrom(other.CommentMode);
471 | }
472 | if (other.trialPanel_ != null) {
473 | if (trialPanel_ == null) {
474 | TrialPanel = new global::Dwango.Nicolive.Chat.Data.TrialPanel();
475 | }
476 | TrialPanel.MergeFrom(other.TrialPanel);
477 | }
478 | if (other.fingerPrint_ != null) {
479 | if (fingerPrint_ == null) {
480 | FingerPrint = new global::Dwango.Nicolive.Chat.Data.FingerPrint();
481 | }
482 | FingerPrint.MergeFrom(other.FingerPrint);
483 | }
484 | if (other.programStatus_ != null) {
485 | if (programStatus_ == null) {
486 | ProgramStatus = new global::Dwango.Nicolive.Chat.Data.ProgramStatus();
487 | }
488 | ProgramStatus.MergeFrom(other.ProgramStatus);
489 | }
490 | if (other.moderationAnnouncement_ != null) {
491 | if (moderationAnnouncement_ == null) {
492 | ModerationAnnouncement = new global::Dwango.Nicolive.Chat.Data.Atoms.ModerationAnnouncement();
493 | }
494 | ModerationAnnouncement.MergeFrom(other.ModerationAnnouncement);
495 | }
496 | _unknownFields = pb::UnknownFieldSet.MergeFrom(_unknownFields, other._unknownFields);
497 | }
498 |
499 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
500 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
501 | public void MergeFrom(pb::CodedInputStream input) {
502 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
503 | input.ReadRawMessage(this);
504 | #else
505 | uint tag;
506 | while ((tag = input.ReadTag()) != 0) {
507 | if ((tag & 7) == 4) {
508 | // Abort on any end group tag.
509 | return;
510 | }
511 | switch(tag) {
512 | default:
513 | _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, input);
514 | break;
515 | case 10: {
516 | if (statistics_ == null) {
517 | Statistics = new global::Dwango.Nicolive.Chat.Data.Statistics();
518 | }
519 | input.ReadMessage(Statistics);
520 | break;
521 | }
522 | case 18: {
523 | if (enquete_ == null) {
524 | Enquete = new global::Dwango.Nicolive.Chat.Data.Enquete();
525 | }
526 | input.ReadMessage(Enquete);
527 | break;
528 | }
529 | case 26: {
530 | if (moveOrder_ == null) {
531 | MoveOrder = new global::Dwango.Nicolive.Chat.Data.MoveOrder();
532 | }
533 | input.ReadMessage(MoveOrder);
534 | break;
535 | }
536 | case 34: {
537 | if (marquee_ == null) {
538 | Marquee = new global::Dwango.Nicolive.Chat.Data.Marquee();
539 | }
540 | input.ReadMessage(Marquee);
541 | break;
542 | }
543 | case 42: {
544 | if (commentLock_ == null) {
545 | CommentLock = new global::Dwango.Nicolive.Chat.Data.CommentLock();
546 | }
547 | input.ReadMessage(CommentLock);
548 | break;
549 | }
550 | case 50: {
551 | if (commentMode_ == null) {
552 | CommentMode = new global::Dwango.Nicolive.Chat.Data.CommentMode();
553 | }
554 | input.ReadMessage(CommentMode);
555 | break;
556 | }
557 | case 58: {
558 | if (trialPanel_ == null) {
559 | TrialPanel = new global::Dwango.Nicolive.Chat.Data.TrialPanel();
560 | }
561 | input.ReadMessage(TrialPanel);
562 | break;
563 | }
564 | case 66: {
565 | if (fingerPrint_ == null) {
566 | FingerPrint = new global::Dwango.Nicolive.Chat.Data.FingerPrint();
567 | }
568 | input.ReadMessage(FingerPrint);
569 | break;
570 | }
571 | case 74: {
572 | if (programStatus_ == null) {
573 | ProgramStatus = new global::Dwango.Nicolive.Chat.Data.ProgramStatus();
574 | }
575 | input.ReadMessage(ProgramStatus);
576 | break;
577 | }
578 | case 82: {
579 | if (moderationAnnouncement_ == null) {
580 | ModerationAnnouncement = new global::Dwango.Nicolive.Chat.Data.Atoms.ModerationAnnouncement();
581 | }
582 | input.ReadMessage(ModerationAnnouncement);
583 | break;
584 | }
585 | }
586 | }
587 | #endif
588 | }
589 |
590 | #if !GOOGLE_PROTOBUF_REFSTRUCT_COMPATIBILITY_MODE
591 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute]
592 | [global::System.CodeDom.Compiler.GeneratedCode("protoc", null)]
593 | void pb::IBufferMessage.InternalMergeFrom(ref pb::ParseContext input) {
594 | uint tag;
595 | while ((tag = input.ReadTag()) != 0) {
596 | if ((tag & 7) == 4) {
597 | // Abort on any end group tag.
598 | return;
599 | }
600 | switch(tag) {
601 | default:
602 | _unknownFields = pb::UnknownFieldSet.MergeFieldFrom(_unknownFields, ref input);
603 | break;
604 | case 10: {
605 | if (statistics_ == null) {
606 | Statistics = new global::Dwango.Nicolive.Chat.Data.Statistics();
607 | }
608 | input.ReadMessage(Statistics);
609 | break;
610 | }
611 | case 18: {
612 | if (enquete_ == null) {
613 | Enquete = new global::Dwango.Nicolive.Chat.Data.Enquete();
614 | }
615 | input.ReadMessage(Enquete);
616 | break;
617 | }
618 | case 26: {
619 | if (moveOrder_ == null) {
620 | MoveOrder = new global::Dwango.Nicolive.Chat.Data.MoveOrder();
621 | }
622 | input.ReadMessage(MoveOrder);
623 | break;
624 | }
625 | case 34: {
626 | if (marquee_ == null) {
627 | Marquee = new global::Dwango.Nicolive.Chat.Data.Marquee();
628 | }
629 | input.ReadMessage(Marquee);
630 | break;
631 | }
632 | case 42: {
633 | if (commentLock_ == null) {
634 | CommentLock = new global::Dwango.Nicolive.Chat.Data.CommentLock();
635 | }
636 | input.ReadMessage(CommentLock);
637 | break;
638 | }
639 | case 50: {
640 | if (commentMode_ == null) {
641 | CommentMode = new global::Dwango.Nicolive.Chat.Data.CommentMode();
642 | }
643 | input.ReadMessage(CommentMode);
644 | break;
645 | }
646 | case 58: {
647 | if (trialPanel_ == null) {
648 | TrialPanel = new global::Dwango.Nicolive.Chat.Data.TrialPanel();
649 | }
650 | input.ReadMessage(TrialPanel);
651 | break;
652 | }
653 | case 66: {
654 | if (fingerPrint_ == null) {
655 | FingerPrint = new global::Dwango.Nicolive.Chat.Data.FingerPrint();
656 | }
657 | input.ReadMessage(FingerPrint);
658 | break;
659 | }
660 | case 74: {
661 | if (programStatus_ == null) {
662 | ProgramStatus = new global::Dwango.Nicolive.Chat.Data.ProgramStatus();
663 | }
664 | input.ReadMessage(ProgramStatus);
665 | break;
666 | }
667 | case 82: {
668 | if (moderationAnnouncement_ == null) {
669 | ModerationAnnouncement = new global::Dwango.Nicolive.Chat.Data.Atoms.ModerationAnnouncement();
670 | }
671 | input.ReadMessage(ModerationAnnouncement);
672 | break;
673 | }
674 | }
675 | }
676 | }
677 | #endif
678 |
679 | }
680 |
681 | #endregion
682 |
683 | }
684 |
685 | #endregion Designer generated code
686 |
--------------------------------------------------------------------------------
/NdgrClientSharp.Test/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | global using NUnit.Framework;
--------------------------------------------------------------------------------
/NdgrClientSharp.Test/NdgrClientSharp.Test.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0
5 | enable
6 | enable
7 |
8 | false
9 | true
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/NdgrClientSharp.Test/NdgrLiveCommentFetcherSpec.cs:
--------------------------------------------------------------------------------
1 | using System.Net;
2 | using Dwango.Nicolive.Chat.Data;
3 | using Dwango.Nicolive.Chat.Service.Edge;
4 | using Google.Protobuf.WellKnownTypes;
5 | using Moq;
6 | using NdgrClientSharp.NdgrApi;
7 | using R3;
8 |
9 | namespace NdgrClientSharp.Test;
10 |
11 | public sealed class NdgrLiveCommentFetcherSpec
12 | {
13 | [Test, Timeout(5000)]
14 | public async Task メッセージを受信しきったらDisconnectする()
15 | {
16 | var apiClientMock = new Mock();
17 |
18 | apiClientMock
19 | .Setup(x =>
20 | x.FetchViewAtNowAsync(It.IsAny(), It.IsAny()))
21 | .Returns(new ValueTask(
22 | new ChunkedEntry.Types.ReadyForNext
23 | {
24 | At = 0
25 | }
26 | ));
27 |
28 | apiClientMock
29 | .Setup(x =>
30 | x.FetchChunkedMessagesAsync("segment_1", It.IsAny()))
31 | .Returns(CreateChunkedMessagesAsync(TimeSpan.FromMilliseconds(100), "1", "2", "3"));
32 |
33 | apiClientMock
34 | .Setup(x =>
35 | x.FetchChunkedMessagesAsync("segment_2", It.IsAny()))
36 | .Returns(CreateChunkedMessagesAsync(TimeSpan.FromMilliseconds(100), "4", "5", "6"));
37 |
38 | apiClientMock
39 | .Setup(x => x.FetchViewAtAsync(It.IsAny(), 0, It.IsAny()))
40 | .Returns(new[]
41 | {
42 | new ChunkedEntry()
43 | {
44 | Segment = new MessageSegment()
45 | {
46 | Uri = "segment_1",
47 | From = new Timestamp()
48 | {
49 | Seconds = 0 // 時間としてはめちゃくちゃだが処理は進むからOK
50 | }
51 | }
52 | },
53 | // 1回目はNextがある
54 | new ChunkedEntry()
55 | {
56 | Next = new ChunkedEntry.Types.ReadyForNext()
57 | {
58 | At = 1
59 | }
60 | }
61 | }.ToAsyncEnumerable());
62 |
63 | apiClientMock
64 | .Setup(x => x.FetchViewAtAsync(It.IsAny(), 1, It.IsAny()))
65 | .Returns(new[]
66 | {
67 | // 2回目はNextがない
68 | new ChunkedEntry()
69 | {
70 | Segment = new MessageSegment()
71 | {
72 | Uri = "segment_2",
73 | From = new Timestamp()
74 | {
75 | Seconds = 0 // 時間としてはめちゃくちゃだが処理は進むからOK
76 | }
77 | }
78 | }
79 | }.ToAsyncEnumerable());
80 |
81 | var fetcher = new NdgrLiveCommentFetcher(apiClientMock.Object);
82 |
83 | // 結果の保持用
84 | var list = fetcher.OnMessageReceived.Select(x => x.Message.Chat.Content).ToLiveList();
85 | var statusList = fetcher.ConnectionStatus.ToLiveList();
86 |
87 | // 取得開始
88 | fetcher.Connect("test");
89 |
90 | // Disconnectされるまで待つ
91 | await fetcher.ConnectionStatus
92 | .Where(x => x == ConnectionState.Disconnected)
93 | .FirstAsync();
94 |
95 | Assert.Multiple(() =>
96 | {
97 | // Segmentの時間が適当なのでメッセージは意図した順番ではこない
98 | // ただしここでは全メッセージが受信できていることさえ見れれば良い
99 | CollectionAssert.AreEqual(new[] { "1", "2", "3", "4", "5", "6" }, list.Order());
100 |
101 | // 切断まで進んでいるはず
102 | CollectionAssert.AreEqual(
103 | new[]
104 | {
105 | ConnectionState.Disconnected,
106 | ConnectionState.Connecting,
107 | ConnectionState.Connected,
108 | ConnectionState.Disconnected,
109 | }, statusList
110 | );
111 | });
112 | }
113 |
114 |
115 | [Test]
116 | public void 最初の取得時にServiceUnavailableのときは上限までリトライする()
117 | {
118 | var apiClientMock = new Mock();
119 |
120 | var ex = new NdgrApiClientHttpException(HttpStatusCode.ServiceUnavailable);
121 |
122 | // 呼び出されても例外をなげる
123 | apiClientMock
124 | .Setup(x =>
125 | x.FetchViewAtNowAsync(It.IsAny(), It.IsAny()))
126 | .Throws(ex);
127 |
128 | var fetcher = new NdgrLiveCommentFetcher(apiClientMock.Object);
129 |
130 | // 最大5回までリトライ(なので最初の一回と合わせて計6回通信する)
131 | fetcher.MaxRetryCount = 5;
132 | fetcher.RetryInterval = TimeSpan.FromMilliseconds(0);
133 |
134 | // 結果の保持用
135 | var list = fetcher.OnMessageReceived.Materialize().ToLiveList();
136 |
137 | // 取得開始
138 | fetcher.Connect("");
139 | Assert.Multiple(() =>
140 | {
141 | // OnErrorResumeが6回発火しているはず
142 | Assert.That(list.Count, Is.EqualTo(6));
143 | Assert.That(list.All(x => x.Kind == NotificationKind.OnErrorResume), Is.True);
144 |
145 | // 最終的に切断している
146 | Assert.That(fetcher.ConnectionStatus.CurrentValue, Is.EqualTo(ConnectionState.Disconnected));
147 | });
148 | }
149 |
150 | [Test, Timeout(1000)]
151 | public void 最初の取得時にメッセージ破損はリトライする()
152 | {
153 | var apiClientMock = new Mock();
154 |
155 | var ex = new NdgrApiClientByteReadException("Failed to read varint from the stream.");
156 |
157 | // 呼び出されても例外をなげる
158 | apiClientMock
159 | .Setup(x =>
160 | x.FetchViewAtNowAsync(It.IsAny(), It.IsAny()))
161 | .Throws(ex);
162 |
163 | var fetcher = new NdgrLiveCommentFetcher(apiClientMock.Object);
164 | fetcher.MaxRetryCount = 5;
165 | fetcher.RetryInterval = TimeSpan.FromMilliseconds(0);
166 |
167 | // 結果の保持用
168 | var list = fetcher.OnMessageReceived.Materialize().ToLiveList();
169 |
170 | // 取得開始
171 | fetcher.Connect("");
172 | Assert.Multiple(() =>
173 | {
174 | // OnErrorResumeが発火している
175 | Assert.That(list.Count, Is.GreaterThan(1));
176 | Assert.That(list.All(x => x.Kind == NotificationKind.OnErrorResume), Is.True);
177 |
178 | // 最終的に切断している
179 | Assert.That(fetcher.ConnectionStatus.CurrentValue, Is.EqualTo(ConnectionState.Disconnected));
180 | });
181 | }
182 |
183 | [Test]
184 | public void 最初の取得時にその他のエラー時はリトライせずに諦める()
185 | {
186 | var apiClientMock = new Mock();
187 |
188 | var ex = new NdgrApiClientHttpException(HttpStatusCode.NotFound);
189 |
190 | // 呼び出されても例外をなげる
191 | apiClientMock
192 | .Setup(x =>
193 | x.FetchViewAtNowAsync(It.IsAny(), It.IsAny()))
194 | .Throws(ex);
195 |
196 | var fetcher = new NdgrLiveCommentFetcher(apiClientMock.Object);
197 | fetcher.MaxRetryCount = 5;
198 | fetcher.RetryInterval = TimeSpan.FromMilliseconds(0);
199 |
200 | // 結果の保持用
201 | var list = fetcher.OnMessageReceived.Materialize().ToLiveList();
202 |
203 | // 取得開始
204 | fetcher.Connect("");
205 | Assert.Multiple(() =>
206 | {
207 | // OnErrorResumeが1回発火しているはず
208 | Assert.That(list.Count, Is.EqualTo(1));
209 | Assert.That(list.All(x => x.Kind == NotificationKind.OnErrorResume), Is.True);
210 |
211 | // 最終的に切断している
212 | Assert.That(fetcher.ConnectionStatus.CurrentValue, Is.EqualTo(ConnectionState.Disconnected));
213 | });
214 | }
215 |
216 | [Test]
217 | public void Viewを連続して取得中にServiceUnavailableが出た場合はRecconectする()
218 | {
219 | var apiClientMock = new Mock();
220 |
221 | var ex = new NdgrApiClientHttpException(HttpStatusCode.ServiceUnavailable);
222 |
223 | var count = 0;
224 |
225 |
226 | apiClientMock
227 | .Setup(x =>
228 | x.FetchViewAtNowAsync(It.IsAny(), It.IsAny()))
229 | .Returns(new ValueTask(
230 | new ChunkedEntry.Types.ReadyForNext
231 | {
232 | At = 0
233 | }
234 | ));
235 |
236 | // 最初のViewAtの取得で例外
237 | apiClientMock
238 | .SetupSequence(x => x.FetchViewAtAsync(It.IsAny(), 0, It.IsAny()))
239 | .Throws(ex)
240 | .Returns(Array.Empty().ToAsyncEnumerable);
241 |
242 | var fetcher = new NdgrLiveCommentFetcher(apiClientMock.Object);
243 |
244 | // 結果の保持用
245 | var list = fetcher.OnMessageReceived.Materialize().ToLiveList();
246 | var statusList = fetcher.ConnectionStatus.ToLiveList();
247 |
248 | // 取得開始
249 | fetcher.Connect("test");
250 |
251 | Assert.Multiple(() =>
252 | {
253 | // OnErrorResumeが1回発火しているはず
254 | Assert.That(list.Count, Is.EqualTo(1));
255 | Assert.That(list.All(x => x.Kind == NotificationKind.OnErrorResume), Is.True);
256 |
257 | // それぞれ呼び出されているはず
258 | apiClientMock
259 | .Verify(
260 | x => x.FetchViewAtNowAsync(It.IsAny(), It.IsAny()),
261 | Times.Exactly(2)
262 | );
263 |
264 | apiClientMock
265 | .Verify(
266 | x => x.FetchViewAtAsync(It.IsAny(), 0, It.IsAny()),
267 | Times.Exactly(2)
268 | );
269 |
270 |
271 | CollectionAssert.AreEqual(
272 | new[]
273 | {
274 | // 1回目の接続
275 | ConnectionState.Disconnected,
276 | ConnectionState.Connecting,
277 | ConnectionState.Connected,
278 | // 再接続
279 | ConnectionState.Disconnected,
280 | ConnectionState.Connecting,
281 | ConnectionState.Connected,
282 | ConnectionState.Disconnected
283 | }, statusList
284 | );
285 | });
286 | }
287 |
288 |
289 | [Test]
290 | public void Viewを連続して取得中にバイトが破損した出た場合はRecconectする()
291 | {
292 | var apiClientMock = new Mock();
293 |
294 | var ex = new NdgrApiClientByteReadException("Failed to read varint from the stream.");
295 |
296 | apiClientMock
297 | .Setup(x =>
298 | x.FetchViewAtNowAsync(It.IsAny(), It.IsAny()))
299 | .Returns(new ValueTask(
300 | new ChunkedEntry.Types.ReadyForNext
301 | {
302 | At = 0
303 | }
304 | ));
305 |
306 | // 最初のViewAtの取得で例外
307 | apiClientMock
308 | .SetupSequence(x => x.FetchViewAtAsync(It.IsAny(), 0, It.IsAny()))
309 | .Throws(ex)
310 | .Returns(Array.Empty().ToAsyncEnumerable);
311 |
312 | var fetcher = new NdgrLiveCommentFetcher(apiClientMock.Object);
313 |
314 | // 結果の保持用
315 | var list = fetcher.OnMessageReceived.Materialize().ToLiveList();
316 | var statusList = fetcher.ConnectionStatus.ToLiveList();
317 |
318 | // 取得開始
319 | fetcher.Connect("test");
320 |
321 | Assert.Multiple(() =>
322 | {
323 | // OnErrorResumeが1回発火しているはず
324 | Assert.That(list.Count, Is.EqualTo(1));
325 | Assert.That(list.All(x => x.Kind == NotificationKind.OnErrorResume), Is.True);
326 |
327 | // それぞれ呼び出されているはず
328 | apiClientMock
329 | .Verify(
330 | x => x.FetchViewAtNowAsync(It.IsAny(), It.IsAny()),
331 | Times.Exactly(2)
332 | );
333 |
334 | apiClientMock
335 | .Verify(
336 | x => x.FetchViewAtAsync(It.IsAny(), 0, It.IsAny()),
337 | Times.Exactly(2)
338 | );
339 |
340 |
341 | CollectionAssert.AreEqual(
342 | new[]
343 | {
344 | // 1回目の接続
345 | ConnectionState.Disconnected,
346 | ConnectionState.Connecting,
347 | ConnectionState.Connected,
348 | // 再接続
349 | ConnectionState.Disconnected,
350 | ConnectionState.Connecting,
351 | ConnectionState.Connected,
352 | ConnectionState.Disconnected
353 | }, statusList
354 | );
355 | });
356 | }
357 |
358 | [Test]
359 | public void 受信中のSegmentが正しく反映される()
360 | {
361 | var apiClientMock = new Mock();
362 |
363 | apiClientMock
364 | .Setup(x =>
365 | x.FetchViewAtNowAsync(It.IsAny(), It.IsAny()))
366 | .Returns(new ValueTask(
367 | new ChunkedEntry.Types.ReadyForNext
368 | {
369 | At = 0
370 | }
371 | ));
372 |
373 | apiClientMock
374 | .Setup(x =>
375 | x.FetchChunkedMessagesAsync("segment_1", It.IsAny()))
376 | .Returns(CreateChunkedMessagesAsync(TimeSpan.FromMilliseconds(100), "1", "2", "3"));
377 |
378 | apiClientMock
379 | .Setup(x =>
380 | x.FetchChunkedMessagesAsync("segment_2", It.IsAny()))
381 | .Returns(CreateChunkedMessagesAsync(TimeSpan.FromMilliseconds(100), "4", "5", "6"));
382 |
383 |
384 | apiClientMock
385 | .Setup(x => x.FetchViewAtAsync(It.IsAny(), 0, It.IsAny()))
386 | .Returns(new[]
387 | {
388 | new ChunkedEntry()
389 | {
390 | Segment = new MessageSegment()
391 | {
392 | Uri = "segment_1",
393 | From = new Timestamp()
394 | {
395 | Seconds = 0 // 時間としてはめちゃくちゃだが処理は進むからOK
396 | }
397 | }
398 | },
399 | new ChunkedEntry()
400 | {
401 | Next = new ChunkedEntry.Types.ReadyForNext()
402 | {
403 | At = 1
404 | }
405 | }
406 | }.ToAsyncEnumerable());
407 |
408 |
409 | async IAsyncEnumerable Create_1(Task waitTask)
410 | {
411 | await waitTask;
412 | yield return new ChunkedEntry()
413 | {
414 | Segment = new MessageSegment()
415 | {
416 | Uri = "segment_1",
417 | From = new Timestamp()
418 | {
419 | Seconds = 0
420 | }
421 | }
422 | };
423 |
424 | yield return new ChunkedEntry()
425 | {
426 | Segment = new MessageSegment()
427 | {
428 | Uri = "segment_2",
429 | From = new Timestamp()
430 | {
431 | Seconds = 0
432 | }
433 | }
434 | };
435 |
436 | yield return new ChunkedEntry()
437 | {
438 | Next = new ChunkedEntry.Types.ReadyForNext()
439 | {
440 | At = 2
441 | }
442 | };
443 | }
444 |
445 | async IAsyncEnumerable Create_2(Task waitTask)
446 | {
447 | await waitTask;
448 | yield return new ChunkedEntry()
449 | {
450 | Segment = new MessageSegment()
451 | {
452 | Uri = "segment_2",
453 | From = new Timestamp()
454 | {
455 | Seconds = 0
456 | }
457 | }
458 | };
459 | }
460 |
461 | var tcs1 = new TaskCompletionSource();
462 | var tcs2 = new TaskCompletionSource();
463 | apiClientMock
464 | .Setup(x => x.FetchViewAtAsync(It.IsAny(), 1, It.IsAny()))
465 | .Returns(Create_1(tcs1.Task));
466 |
467 | apiClientMock
468 | .Setup(x => x.FetchViewAtAsync(It.IsAny(), 2, It.IsAny()))
469 | .Returns(Create_2(tcs2.Task));
470 |
471 | var fetcher = new NdgrLiveCommentFetcher(apiClientMock.Object);
472 |
473 | // 取得開始
474 | fetcher.Connect("test");
475 |
476 | // 最初はSegment1の取得まで進んでいるはず
477 | CollectionAssert.AreEqual(fetcher.CurrentReceivingSegments, new[] { "segment_1" });
478 |
479 | // 次に進んでSegment1,2の取得まで進んでいるはず
480 | tcs1.SetResult();
481 |
482 | CollectionAssert.AreEqual(fetcher.CurrentReceivingSegments, new[] { "segment_1", "segment_2" });
483 |
484 | // 最後に進んでSegment2のみの取得に進んでいるはず
485 | tcs2.SetResult();
486 |
487 | CollectionAssert.AreEqual(fetcher.CurrentReceivingSegments, new[] { "segment_2" });
488 | }
489 |
490 | [Test]
491 | public void Reconnect時に重複したコメントは受信しない()
492 | {
493 | var apiClientMock = new Mock();
494 |
495 | apiClientMock
496 | .Setup(x =>
497 | x.FetchViewAtNowAsync(It.IsAny(), It.IsAny()))
498 | .Returns(new ValueTask(
499 | new ChunkedEntry.Types.ReadyForNext
500 | {
501 | At = 0
502 | }
503 | ));
504 |
505 | apiClientMock
506 | .Setup(x =>
507 | x.FetchChunkedMessagesAsync("segment_1", It.IsAny()))
508 | .Returns(CreateChunkedMessagesAsync(TimeSpan.Zero, "1", "2", "3"));
509 |
510 | async IAsyncEnumerable CreateSegment(Task waitTask)
511 | {
512 | yield return new ChunkedEntry()
513 | {
514 | Segment = new MessageSegment()
515 | {
516 | Uri = "segment_1",
517 | From = new Timestamp()
518 | {
519 | Seconds = 0
520 | }
521 | }
522 | };
523 |
524 | await waitTask;
525 |
526 | yield return new ChunkedEntry()
527 | {
528 | Next = new ChunkedEntry.Types.ReadyForNext()
529 | {
530 | At = 2
531 | }
532 | };
533 | }
534 |
535 | var tcs = new TaskCompletionSource();
536 | apiClientMock
537 | .Setup(x => x.FetchViewAtAsync(It.IsAny(), 0, It.IsAny()))
538 | .Returns(CreateSegment(tcs.Task));
539 |
540 | var fetcher = new NdgrLiveCommentFetcher(apiClientMock.Object);
541 |
542 | // 結果の保持用
543 | var list = fetcher.OnMessageReceived.Select(x => x.Message.Chat.Content).ToLiveList();
544 |
545 | // 取得開始
546 | fetcher.Connect("test");
547 |
548 | // コメントの受信ができている
549 | CollectionAssert.AreEqual(new[] { "1", "2", "3" }, list.Order());
550 |
551 | // 再接続する
552 | fetcher.Reconnect();
553 | tcs.SetResult();
554 |
555 | // 重複したコメントは受信されない
556 | CollectionAssert.AreEqual(new[] { "1", "2", "3" }, list.Order());
557 | }
558 |
559 |
560 | private async IAsyncEnumerable CreateChunkedMessagesAsync(TimeSpan delayTime, params string[] texts)
561 | {
562 | foreach (var text in texts)
563 | {
564 | await Task.Delay(delayTime);
565 | yield return new ChunkedMessage()
566 | {
567 | Message = new NicoliveMessage()
568 | {
569 | Chat = new Chat()
570 | {
571 | Content = text
572 | }
573 | },
574 | Meta = new ChunkedMessage.Types.Meta()
575 | {
576 | Id = text
577 | }
578 | };
579 | }
580 | }
581 | }
--------------------------------------------------------------------------------
/NdgrClientSharp.Test/NdgrPastCommentFetcherSpec.cs:
--------------------------------------------------------------------------------
1 | using Dwango.Nicolive.Chat.Data;
2 | using Dwango.Nicolive.Chat.Service.Edge;
3 | using Google.Protobuf.WellKnownTypes;
4 | using Moq;
5 | using NdgrClientSharp.NdgrApi;
6 | using R3;
7 | using ReadyForNext = Dwango.Nicolive.Chat.Service.Edge.ChunkedEntry.Types.ReadyForNext;
8 |
9 | namespace NdgrClientSharp.Test;
10 |
11 | public sealed class NdgrPastCommentFetcherSpec
12 | {
13 | [Test, Timeout(1000)]
14 | public async Task 新しい方のPreviousからコメントが取得される()
15 | {
16 | var apiClientMock = new Mock();
17 |
18 | apiClientMock
19 | .Setup(x =>
20 | x.FetchViewAtNowAsync(It.IsAny(), It.IsAny()))
21 | .Returns(new ValueTask(
22 | new ReadyForNext
23 | {
24 | At = 10
25 | }
26 | ));
27 |
28 | // 2つのPreviousが返される
29 | apiClientMock
30 | .Setup(x =>
31 | x.FetchViewAtAsync(It.IsAny(), 10, It.IsAny()))
32 | .Returns(new[]
33 | {
34 | new ChunkedEntry()
35 | {
36 | Previous = new MessageSegment()
37 | {
38 | From = new Timestamp() { Seconds = 2 },
39 | Until = new Timestamp() { Seconds = 3 },
40 | Uri = "first"
41 | },
42 | },
43 | new ChunkedEntry()
44 | {
45 | Previous = new MessageSegment()
46 | {
47 | From = new Timestamp() { Seconds = 1 },
48 | Until = new Timestamp() { Seconds = 2 },
49 | Uri = "seconds"
50 | },
51 | }
52 | }.ToAsyncEnumerable()
53 | );
54 |
55 | apiClientMock.Setup(x =>
56 | x.FetchChunkedMessagesAsync("first", It.IsAny()))
57 | .Returns(CreateChunkedMessages("one").ToAsyncEnumerable);
58 | apiClientMock.Setup(x =>
59 | x.FetchChunkedMessagesAsync("seconds", It.IsAny()))
60 | .Returns(CreateChunkedMessages("two").ToAsyncEnumerable);
61 |
62 | var pastCommentFetcher = new NdgrPastCommentFetcher(apiClientMock.Object);
63 |
64 | // 取得実行
65 | var list = await pastCommentFetcher.FetchPastComments("", 1).ToListAsync();
66 |
67 | // 時刻が新しいほうが呼び出されているはず
68 | apiClientMock.Verify(x =>
69 | x.FetchChunkedMessagesAsync("first", It.IsAny()), Times.Once);
70 | apiClientMock.Verify(x =>
71 | x.FetchChunkedMessagesAsync("seconds", It.IsAny()), Times.Never);
72 |
73 | CollectionAssert.AreEqual(CreateChunkedMessages("one"), list);
74 | }
75 |
76 | [Test, Timeout(1000)]
77 | public async Task 指定した個数に達するまでコメントを連続取得する()
78 | {
79 | var apiClientMock = new Mock();
80 |
81 | apiClientMock
82 | .Setup(x =>
83 | x.FetchViewAtNowAsync(It.IsAny(), It.IsAny()))
84 | .Returns(new ValueTask(
85 | new ReadyForNext
86 | {
87 | At = 10
88 | }
89 | ));
90 |
91 | // PreviousとBackwardを返す
92 | apiClientMock
93 | .Setup(x =>
94 | x.FetchViewAtAsync(It.IsAny(), 10, It.IsAny()))
95 | .Returns(new[]
96 | {
97 | new ChunkedEntry()
98 | {
99 | Backward = new BackwardSegment()
100 | {
101 | Until = new Timestamp() { Seconds = 2 },
102 | Segment = new PackedSegment.Types.Next()
103 | {
104 | Uri = "segment_0"
105 | }
106 | },
107 | },
108 | new ChunkedEntry()
109 | {
110 | Previous = new MessageSegment()
111 | {
112 | From = new Timestamp() { Seconds = 2 },
113 | Until = new Timestamp() { Seconds = 3 },
114 | Uri = "first"
115 | },
116 | }
117 | }.ToAsyncEnumerable()
118 | );
119 |
120 | // Previousがコメントを1個返す
121 | apiClientMock.Setup(x =>
122 | x.FetchChunkedMessagesAsync("first", It.IsAny()))
123 | .Returns(CreateChunkedMessages("0").ToAsyncEnumerable);
124 |
125 | // セットアップ
126 | SetupBackwardSegment(apiClientMock, "segment_0", "segment_1", CreateChunkedMessages("1", "2"));
127 | SetupBackwardSegment(apiClientMock, "segment_1", "segment_2", CreateChunkedMessages("3", "4"));
128 | SetupBackwardSegment(apiClientMock, "segment_2", "segment_3", CreateChunkedMessages("5", "6"));
129 |
130 |
131 | var pastCommentFetcher = new NdgrPastCommentFetcher(apiClientMock.Object);
132 |
133 | // 4個を目標に取得
134 | var list = await pastCommentFetcher.FetchPastComments("", 4).ToListAsync();
135 |
136 | // 実際は5個取得されているはず
137 | Assert.That(list.Count, Is.EqualTo(5));
138 |
139 | // コメントも期待通り取得されている
140 | var resultChat = list.Select(x => x.Message.Chat.Content).ToArray();
141 | CollectionAssert.AreEqual(new[] { "0", "1", "2", "3", "4" }, resultChat);
142 |
143 | // 呼び出し数の確認
144 | apiClientMock.Verify(x =>
145 | x.FetchChunkedMessagesAsync("first", It.IsAny()), Times.Once);
146 | apiClientMock.Verify(x =>
147 | x.FetchPackedSegmentAsync("segment_0", It.IsAny()), Times.Once);
148 | apiClientMock.Verify(x =>
149 | x.FetchPackedSegmentAsync("segment_1", It.IsAny()), Times.Once);
150 | // ここまでは達してない
151 | apiClientMock.Verify(x =>
152 | x.FetchPackedSegmentAsync("segment_2", It.IsAny()), Times.Never);
153 | }
154 |
155 | [Test, Timeout(1000)]
156 | public async Task 個数を指定しない場合は最後まで連続取得する()
157 | {
158 | var apiClientMock = new Mock();
159 |
160 | apiClientMock
161 | .Setup(x =>
162 | x.FetchViewAtNowAsync(It.IsAny(), It.IsAny()))
163 | .Returns(new ValueTask(
164 | new ReadyForNext
165 | {
166 | At = 10
167 | }
168 | ));
169 |
170 | // PreviousとBackwardを返す
171 | apiClientMock
172 | .Setup(x =>
173 | x.FetchViewAtAsync(It.IsAny(), 10, It.IsAny()))
174 | .Returns(new[]
175 | {
176 | new ChunkedEntry()
177 | {
178 | Backward = new BackwardSegment()
179 | {
180 | Until = new Timestamp() { Seconds = 2 },
181 | Segment = new PackedSegment.Types.Next()
182 | {
183 | Uri = "segment_0"
184 | }
185 | },
186 | },
187 | new ChunkedEntry()
188 | {
189 | Previous = new MessageSegment()
190 | {
191 | From = new Timestamp() { Seconds = 2 },
192 | Until = new Timestamp() { Seconds = 3 },
193 | Uri = "first"
194 | },
195 | }
196 | }.ToAsyncEnumerable()
197 | );
198 |
199 | // Previousがコメントを1個返す
200 | apiClientMock.Setup(x =>
201 | x.FetchChunkedMessagesAsync("first", It.IsAny()))
202 | .Returns(CreateChunkedMessages("0").ToAsyncEnumerable);
203 |
204 | // セットアップ
205 | SetupBackwardSegment(apiClientMock, "segment_0", "segment_1", CreateChunkedMessages("1", "2"));
206 | SetupBackwardSegment(apiClientMock, "segment_1", "segment_2", CreateChunkedMessages("3", "4"));
207 | SetupBackwardSegment(apiClientMock, "segment_2", null, CreateChunkedMessages("5", "6"));
208 |
209 | var pastCommentFetcher = new NdgrPastCommentFetcher(apiClientMock.Object);
210 |
211 | // 上限無しで取得
212 | var list = await pastCommentFetcher.FetchPastComments("").ToListAsync();
213 |
214 | // 7個取得されているはず
215 | Assert.That(list.Count, Is.EqualTo(7));
216 |
217 | // コメントも期待通り取得されている
218 | var resultChat = list.Select(x => x.Message.Chat.Content).ToArray();
219 | CollectionAssert.AreEqual(new[] { "0", "1", "2", "3", "4", "5", "6" }, resultChat);
220 |
221 | // 呼び出し数の確認
222 | apiClientMock.Verify(x =>
223 | x.FetchChunkedMessagesAsync("first", It.IsAny()), Times.Once);
224 | apiClientMock.Verify(x =>
225 | x.FetchPackedSegmentAsync("segment_0", It.IsAny()), Times.Once);
226 | apiClientMock.Verify(x =>
227 | x.FetchPackedSegmentAsync("segment_1", It.IsAny()), Times.Once);
228 | apiClientMock.Verify(x =>
229 | x.FetchPackedSegmentAsync("segment_2", It.IsAny()), Times.Once);
230 | }
231 |
232 |
233 | private ChunkedMessage[] CreateChunkedMessages(params string[] texts)
234 | {
235 | return texts.Select(x => new ChunkedMessage()
236 | {
237 | Message = new NicoliveMessage()
238 | {
239 | Chat = new Chat()
240 | {
241 | Content = x
242 | }
243 | }
244 | })
245 | .ToArray();
246 | }
247 |
248 | private void SetupBackwardSegment(Mock apiClientMock,
249 | string currentUir,
250 | string? nextUri,
251 | ChunkedMessage[] messages)
252 | {
253 | PackedSegment packedSegment;
254 | if (nextUri == null)
255 | {
256 | packedSegment = new PackedSegment()
257 | {
258 | Messages = { messages },
259 | };
260 | }
261 | else
262 | {
263 | packedSegment = new PackedSegment()
264 | {
265 | Messages = { messages },
266 | Next = new PackedSegment.Types.Next()
267 | {
268 | Uri = nextUri
269 | }
270 | };
271 | }
272 |
273 | apiClientMock
274 | .Setup(x =>
275 | x.FetchPackedSegmentAsync(currentUir, It.IsAny()))
276 | .Returns(new ValueTask(packedSegment));
277 | }
278 | }
--------------------------------------------------------------------------------
/NdgrClientSharp.Test/NdgrProtobufStreamReaderSpec.cs:
--------------------------------------------------------------------------------
1 | using NdgrClientSharp.Utilities;
2 |
3 | namespace NdgrClientSharp.Test;
4 |
5 | public class NdgrProtobufStreamReaderSpec
6 | {
7 | private byte[] EncodeVarint(ulong value)
8 | {
9 | using var ms = new MemoryStream();
10 | while (value > 0x7F)
11 | {
12 | ms.WriteByte((byte)((value & 0x7F) | 0x80));
13 | value >>= 7;
14 | }
15 |
16 | ms.WriteByte((byte)value);
17 | return ms.ToArray();
18 | }
19 |
20 | [TestCase(0u)]
21 | [TestCase(1u)]
22 | [TestCase(127u)]
23 | [TestCase(128u)]
24 | [TestCase(300u)]
25 | [TestCase(uint.MaxValue)]
26 | public void ReadVarint_uintの範囲内を正しく読める(uint expected)
27 | {
28 | var reader = new NdgrProtobufStreamReader();
29 |
30 | // encodeして戻す
31 | var encoded = EncodeVarint(expected);
32 |
33 | reader.AddNewChunk(encoded);
34 | var result = reader.ReadVarint();
35 |
36 | Assert.IsNotNull(result);
37 | Assert.That(result.Value.result, Is.EqualTo(expected));
38 | }
39 |
40 | [TestCase(((ulong)123456 << 32) + 0u, 0u)]
41 | [TestCase(((ulong)123456 << 32) + 1u, 1u)]
42 | [TestCase(((ulong)123456 << 32) + 127u, 127u)]
43 | [TestCase(((ulong)123456 << 32) + 128u, 128u)]
44 | [TestCase(((ulong)123456 << 32) + 300u, 300u)]
45 | [TestCase(((ulong)123456 << 32) + uint.MaxValue, uint.MaxValue)]
46 | public void ReadVarint_ulongの場合はuintの範囲のみ読み取られる(ulong input, uint expected)
47 | {
48 | var reader = new NdgrProtobufStreamReader();
49 |
50 | // encodeして戻す
51 | var encoded = EncodeVarint(input);
52 |
53 | reader.AddNewChunk(encoded);
54 | var result = reader.ReadVarint();
55 |
56 | Assert.IsNotNull(result);
57 | Assert.That(result.Value.result, Is.EqualTo(expected));
58 | }
59 |
60 | [Test]
61 | public void ReadVarint_不十分なVarintがある場合はnullを返す()
62 | {
63 | var reader = new NdgrProtobufStreamReader();
64 | reader.AddNewChunk([0x80]); // 0x80は不完全なVarint
65 |
66 | var result = reader.ReadVarint();
67 |
68 | Assert.IsNull(result);
69 | }
70 |
71 | [Test]
72 | public void ReadVarint_64bitを超えるVarintがある場合は例外()
73 | {
74 | var reader = new NdgrProtobufStreamReader();
75 | reader.AddNewChunk([
76 | 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 // over 64bit
77 | ]);
78 |
79 | Assert.Throws(() => reader.ReadVarint());
80 | }
81 |
82 | [Test]
83 | public void UnshiftChunk_Varintが不十分なうちはfalseを返す()
84 | {
85 | var reader = new NdgrProtobufStreamReader();
86 | reader.AddNewChunk([0x80]); // 0x80は不完全なVarint
87 |
88 | var (isValid, _) = reader.UnshiftChunk(new byte[1]);
89 |
90 | Assert.IsFalse(isValid);
91 | }
92 |
93 | [Test]
94 | public void UnshiftChunk_データが揃っていない場合はfalse()
95 | {
96 | var reader = new NdgrProtobufStreamReader();
97 | reader.AddNewChunk(EncodeVarint(123)); // varint分しかない
98 |
99 | var (isValid, _) = reader.UnshiftChunk(new byte[1024]);
100 |
101 | Assert.IsFalse(isValid);
102 | }
103 |
104 | [Test]
105 | public void UnshiftChunk_データが揃っている場合は正常に読み取る()
106 | {
107 | var reader = new NdgrProtobufStreamReader();
108 | reader.AddNewChunk(EncodeVarint(3));
109 |
110 | var expected = new byte[] { 1, 2, 3 };
111 | reader.AddNewChunk(expected);
112 |
113 | var resultBuffer = new byte[1024];
114 | var (isValid, size) = reader.UnshiftChunk(resultBuffer);
115 |
116 | Assert.IsTrue(isValid);
117 | Assert.That(size, Is.EqualTo(expected.Length));
118 | Assert.That(resultBuffer[..size], Is.EqualTo(expected));
119 | }
120 |
121 | [Test]
122 | public void UnshiftChunk_データが大きすぎる場合は例外()
123 | {
124 | var reader = new NdgrProtobufStreamReader();
125 | reader.AddNewChunk(EncodeVarint(1024));
126 |
127 | var expected = new byte[1024];
128 | reader.AddNewChunk(expected);
129 |
130 | var resultBuffer = new byte[100]; // 100バイトしか収容できない
131 | Assert.Throws(() => reader.UnshiftChunk(resultBuffer));
132 | }
133 |
134 | [Test]
135 | public void UnshiftChunk_データが足りない場合は追加することで読み取りができる()
136 | {
137 | var reader = new NdgrProtobufStreamReader();
138 | var resultBuffer = new byte[2048];
139 |
140 | // varintを2byte用意
141 | var varints = EncodeVarint(1024);
142 |
143 | var payload = new byte[1024];
144 | for (int i = 0; i < payload.Length; i++)
145 | {
146 | payload[i] = (byte)i;
147 | }
148 |
149 | // varintを一部書き込み
150 | reader.AddNewChunk(varints.Take(1).ToArray());
151 | var result = reader.UnshiftChunk(resultBuffer);
152 |
153 | // まだ足りない
154 | Assert.IsFalse(result.isValid);
155 |
156 | // 残りを書き込み
157 | reader.AddNewChunk(varints.Skip(1).ToArray());
158 | result = reader.UnshiftChunk(resultBuffer);
159 |
160 | // varintは揃ったが、payloadはまだ足りない
161 | Assert.IsFalse(result.isValid);
162 |
163 | // ------
164 |
165 | // payloadを部分的に書き込む
166 | reader.AddNewChunk(payload.Take(512).ToArray());
167 |
168 | // まだ足りない
169 | result = reader.UnshiftChunk(resultBuffer);
170 | Assert.IsFalse(result.isValid);
171 |
172 | // ----
173 |
174 | // payloadを残りを書き込む
175 | reader.AddNewChunk(payload.Skip(512).ToArray());
176 | result = reader.UnshiftChunk(resultBuffer);
177 |
178 | // すべて揃った
179 | Assert.IsTrue(result.isValid);
180 | Assert.That(result.messageSize, Is.EqualTo(payload.Length));
181 | Assert.That(resultBuffer[..result.messageSize], Is.EqualTo(payload));
182 | }
183 | }
--------------------------------------------------------------------------------
/NdgrClientSharp.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NdgrClientSharp", "NdgrClientSharp\NdgrClientSharp.csproj", "{3F9650EF-A7FC-4B8F-A970-7C623F5D5CDF}"
4 | EndProject
5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NdgrClientSharp.Protocol", "NdgrClientSharp.Protocol\NdgrClientSharp.Protocol.csproj", "{6EE27585-D2B6-4409-B4E4-A0F723F6B0D9}"
6 | EndProject
7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sandbox", "Sandbox\Sandbox.csproj", "{13DD8DFB-FFC7-4A99-982C-B3905DDCE741}"
8 | EndProject
9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NdgrClientSharp.Test", "NdgrClientSharp.Test\NdgrClientSharp.Test.csproj", "{8555AA7B-746B-4F3C-B391-4FD54965535C}"
10 | EndProject
11 | Global
12 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
13 | Debug|Any CPU = Debug|Any CPU
14 | Release|Any CPU = Release|Any CPU
15 | EndGlobalSection
16 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
17 | {3F9650EF-A7FC-4B8F-A970-7C623F5D5CDF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
18 | {3F9650EF-A7FC-4B8F-A970-7C623F5D5CDF}.Debug|Any CPU.Build.0 = Debug|Any CPU
19 | {3F9650EF-A7FC-4B8F-A970-7C623F5D5CDF}.Release|Any CPU.ActiveCfg = Release|Any CPU
20 | {3F9650EF-A7FC-4B8F-A970-7C623F5D5CDF}.Release|Any CPU.Build.0 = Release|Any CPU
21 | {6EE27585-D2B6-4409-B4E4-A0F723F6B0D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
22 | {6EE27585-D2B6-4409-B4E4-A0F723F6B0D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
23 | {6EE27585-D2B6-4409-B4E4-A0F723F6B0D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
24 | {6EE27585-D2B6-4409-B4E4-A0F723F6B0D9}.Release|Any CPU.Build.0 = Release|Any CPU
25 | {13DD8DFB-FFC7-4A99-982C-B3905DDCE741}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
26 | {13DD8DFB-FFC7-4A99-982C-B3905DDCE741}.Debug|Any CPU.Build.0 = Debug|Any CPU
27 | {13DD8DFB-FFC7-4A99-982C-B3905DDCE741}.Release|Any CPU.ActiveCfg = Release|Any CPU
28 | {8555AA7B-746B-4F3C-B391-4FD54965535C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
29 | {8555AA7B-746B-4F3C-B391-4FD54965535C}.Debug|Any CPU.Build.0 = Debug|Any CPU
30 | {8555AA7B-746B-4F3C-B391-4FD54965535C}.Release|Any CPU.ActiveCfg = Release|Any CPU
31 | EndGlobalSection
32 | EndGlobal
33 |
--------------------------------------------------------------------------------
/NdgrClientSharp/NdgrApi/INdgrApiClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Threading;
4 | using System.Threading.Tasks;
5 | using Dwango.Nicolive.Chat.Service.Edge;
6 | using ReadyForNext = Dwango.Nicolive.Chat.Service.Edge.ChunkedEntry.Types.ReadyForNext;
7 |
8 | namespace NdgrClientSharp.NdgrApi
9 | {
10 | public interface INdgrApiClient : IDisposable
11 | {
12 | ///
13 | /// GET /api/view/v4/:view?at=now
14 | ///
15 | ValueTask FetchViewAtNowAsync(string viewApiUri,
16 | CancellationToken token = default);
17 |
18 | ///
19 | /// GET /api/view/v4/:view?at=unixtime
20 | /// 指定時刻付近のコメント取得のための情報を取得する
21 | /// backward,previous,segment,nextが非同期的に返ってくる
22 | ///
23 | IAsyncEnumerable FetchViewAtAsync(string viewApiUri,
24 | long unixTime,
25 | CancellationToken token = default);
26 |
27 | ///
28 | /// ChunkedMessage(コメント)を取得する
29 | /// Segment, Previous, Snapshot APIが対応
30 | ///
31 | IAsyncEnumerable FetchChunkedMessagesAsync(
32 | string apiUri,
33 | CancellationToken token = default);
34 |
35 | ///
36 | /// PackedSegmentを取得する
37 | ///
38 | ValueTask FetchPackedSegmentAsync(string uri, CancellationToken token = default);
39 | }
40 |
41 |
42 | public abstract class NdgrApiClientException : Exception
43 | {
44 | protected NdgrApiClientException(string message) : base(message)
45 | {
46 | }
47 | }
48 | }
--------------------------------------------------------------------------------
/NdgrClientSharp/NdgrApi/NdgrApiClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Buffers;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Net;
6 | using System.Net.Http;
7 | using System.Runtime.CompilerServices;
8 | using System.Threading;
9 | using System.Threading.Tasks;
10 | using Dwango.Nicolive.Chat.Service.Edge;
11 | using NdgrClientSharp.Utilities;
12 | using ReadyForNext = Dwango.Nicolive.Chat.Service.Edge.ChunkedEntry.Types.ReadyForNext;
13 |
14 | namespace NdgrClientSharp.NdgrApi
15 | {
16 | public class NdgrApiClient : INdgrApiClient
17 | {
18 | private readonly HttpClient _httpClient;
19 | private readonly bool _needDisposeHttpClient;
20 | private readonly CancellationTokenSource _mainCts = new CancellationTokenSource();
21 |
22 | public NdgrApiClient()
23 | {
24 | _httpClient = new HttpClient();
25 | _httpClient.DefaultRequestHeaders.Add("User-Agent", "ndgr-client-sharp");
26 | _needDisposeHttpClient = true;
27 | }
28 |
29 | ///
30 | /// HttpClientを指定して初期化する
31 | /// このHttpClientのDisposeはNdgrApiClientでは行わない
32 | ///
33 | public NdgrApiClient(HttpClient httpClient)
34 | {
35 | _httpClient = httpClient;
36 | _needDisposeHttpClient = false;
37 | }
38 |
39 | private static string TrimQuery(string uri)
40 | {
41 | // URIからクエリパラメータを除外
42 | return uri.Split('?')[0];
43 | }
44 |
45 |
46 | #region view api
47 |
48 | ///
49 | /// コメント取得の起点
50 | /// GET /api/view/v4/:view?at=now を実行して、次のコメント取得のための情報を取得する
51 | /// クエリパラメータはTrimしてから実行するため含まれていても問題ない
52 | ///
53 | public async ValueTask FetchViewAtNowAsync(string viewApiUri, CancellationToken token = default)
54 | {
55 | var ct = CreateLinkedToken(token);
56 |
57 | // URIからクエリパラメータを除外
58 | var uri = TrimQuery(viewApiUri);
59 |
60 | var response = await _httpClient.GetAsync(
61 | $"{uri}?at=now",
62 | HttpCompletionOption.ResponseHeadersRead, ct);
63 |
64 | if (!response.IsSuccessStatusCode)
65 | {
66 | throw new NdgrApiClientHttpException(response.StatusCode);
67 | }
68 |
69 | await foreach (var chunk in ReadProtoBuffBytesAsync(await response.Content.ReadAsStreamAsync())
70 | .WithCancellation(ct))
71 | {
72 | var message = ParseChunkedEntry(chunk);
73 |
74 | // at=nowの場合はほぼ現在時刻のunixtimeが返ってくる
75 | return message.Next;
76 | }
77 |
78 | throw new NdgrApiClientByteReadException("Failed to read bytes from stream.");
79 | }
80 |
81 | private static ChunkedEntry ParseChunkedEntry(PooledBuffer message)
82 | {
83 | try
84 | {
85 | return ChunkedEntry.Parser.ParseFrom(message.Span);
86 | }
87 | finally
88 | {
89 | message.Dispose();
90 | }
91 | }
92 |
93 |
94 | ///
95 | /// 指定時刻付近のコメント取得のための情報を取得する
96 | /// backward,previous,segment,nextが非同期的に返ってくる
97 | ///
98 | public async IAsyncEnumerable FetchViewAtAsync(string viewApiUri,
99 | long unixTime,
100 | [EnumeratorCancellation] CancellationToken token = default)
101 | {
102 | var ct = CreateLinkedToken(token);
103 |
104 | var uri = TrimQuery(viewApiUri);
105 | var response =
106 | await _httpClient.GetAsync(
107 | $"{uri}?at={unixTime}",
108 | HttpCompletionOption.ResponseHeadersRead, ct);
109 |
110 | if (!response.IsSuccessStatusCode)
111 | {
112 | throw new NdgrApiClientHttpException(response.StatusCode);
113 | }
114 |
115 | var stream = await response.Content.ReadAsStreamAsync();
116 | await foreach (var chunk in ReadProtoBuffBytesAsync(stream, ct))
117 | {
118 | var message = ParseChunkedEntry(chunk);
119 | yield return message;
120 | }
121 | }
122 |
123 | #endregion
124 |
125 |
126 | ///
127 | /// ChunkedMessage(コメント)を取得する
128 | /// Segment, Previous APIが対応
129 | ///
130 | public async IAsyncEnumerable FetchChunkedMessagesAsync(
131 | string apiUri,
132 | [EnumeratorCancellation] CancellationToken token = default)
133 | {
134 | var ct = CreateLinkedToken(token);
135 |
136 | using var response = await _httpClient.GetAsync(apiUri, HttpCompletionOption.ResponseHeadersRead, ct);
137 |
138 | if (!response.IsSuccessStatusCode)
139 | {
140 | throw new NdgrApiClientHttpException(response.StatusCode);
141 | }
142 |
143 | await foreach (var chunk in ReadProtoBuffBytesAsync(await response.Content.ReadAsStreamAsync(), ct))
144 | {
145 | ct.ThrowIfCancellationRequested();
146 | ChunkedMessage message;
147 | try
148 | {
149 | message = ParseChunkedMessage(chunk);
150 | }
151 | catch
152 | {
153 | // ignore...
154 | continue;
155 | }
156 |
157 | yield return message;
158 | }
159 | }
160 |
161 | private static ChunkedMessage ParseChunkedMessage(PooledBuffer message)
162 | {
163 | try
164 | {
165 | return ChunkedMessage.Parser.ParseFrom(message.Span);
166 | }
167 | finally
168 | {
169 | message.Dispose();
170 | }
171 | }
172 |
173 |
174 | ///
175 | /// PackedSegmentを取得する
176 | ///
177 | public async ValueTask FetchPackedSegmentAsync(
178 | string uri,
179 | CancellationToken token = default)
180 | {
181 | var ct = CreateLinkedToken(token);
182 | var response = await _httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, ct);
183 | response.EnsureSuccessStatusCode();
184 | var message = PackedSegment.Parser.ParseFrom(await response.Content.ReadAsStreamAsync());
185 | return message;
186 | }
187 |
188 | #region read bytes utils
189 |
190 | ///
191 | /// Streamから非同期的に生のバイト配列を読み取る
192 | ///
193 | private static async IAsyncEnumerable ReadRawBytesAsync(
194 | Stream stream,
195 | [EnumeratorCancellation] CancellationToken ct = default)
196 | {
197 | var pool = ArrayPool.Shared;
198 |
199 | while (true)
200 | {
201 | var buffer = pool.Rent(2048);
202 | int read;
203 | try
204 | {
205 | read = await stream.ReadAsync(buffer, 0, buffer.Length, ct);
206 | }
207 | catch
208 | {
209 | pool.Return(buffer);
210 | throw;
211 | }
212 |
213 | if (read == 0)
214 | {
215 | pool.Return(buffer);
216 | yield break;
217 | }
218 |
219 | yield return new PooledBuffer(buffer, read, pool);
220 | }
221 | }
222 |
223 | ///
224 | /// Streamを「ProtoBuffのメッセージとして解釈可能なバイト配列単位」で読み取る
225 | ///
226 | private static async IAsyncEnumerable ReadProtoBuffBytesAsync(
227 | Stream stream,
228 | [EnumeratorCancellation] CancellationToken ct = default)
229 | {
230 | using var reader = new NdgrProtobufStreamReader();
231 | await foreach (var chunk in ReadRawBytesAsync(stream).WithCancellation(ct))
232 | {
233 | try
234 | {
235 | ct.ThrowIfCancellationRequested();
236 | reader.AddNewChunk(chunk.Array, chunk.Length);
237 | }
238 | finally
239 | {
240 | chunk.Dispose();
241 | }
242 |
243 | var pool = ArrayPool.Shared;
244 | while (true)
245 | {
246 | int size;
247 | var buffer = pool.Rent(1024);
248 | try
249 | {
250 | bool isValid;
251 | (isValid, size) = reader.UnshiftChunk(buffer);
252 | if (!isValid)
253 | {
254 | break;
255 | }
256 | }
257 | catch (NdgrProtobufStreamReaderException)
258 | {
259 | pool.Return(buffer);
260 | throw new NdgrApiClientByteReadException("Failed to read varint from the stream.");
261 | }
262 |
263 | yield return new PooledBuffer(buffer, size, pool);
264 | }
265 | }
266 | }
267 |
268 | #endregion
269 |
270 | private CancellationToken CreateLinkedToken(CancellationToken ct)
271 | {
272 | if (!ct.CanBeCanceled) return _mainCts.Token;
273 | return CancellationTokenSource.CreateLinkedTokenSource(_mainCts.Token, ct).Token;
274 | }
275 |
276 | public void Dispose()
277 | {
278 | if (_needDisposeHttpClient)
279 | {
280 | _httpClient.Dispose();
281 | }
282 |
283 | _mainCts.Cancel();
284 | _mainCts.Dispose();
285 | }
286 | }
287 |
288 |
289 | public sealed class NdgrApiClientByteReadException : NdgrApiClientException
290 | {
291 | public NdgrApiClientByteReadException(string message) : base(message)
292 | {
293 | }
294 | }
295 |
296 |
297 | public sealed class NdgrApiClientHttpException : Exception
298 | {
299 | public HttpStatusCode HttpStatusCode { get; }
300 |
301 | public NdgrApiClientHttpException(HttpStatusCode httpStatusCode)
302 | {
303 | HttpStatusCode = httpStatusCode;
304 | }
305 |
306 | public override string ToString()
307 | {
308 | return $"{base.ToString()}, {nameof(HttpStatusCode)}: {HttpStatusCode}";
309 | }
310 | }
311 | }
--------------------------------------------------------------------------------
/NdgrClientSharp/NdgrClientSharp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | enable
5 | 0.1.3
6 | 8
7 | netstandard2.0;netstandard2.1
8 | 0.1.7
9 |
10 |
11 |
12 |
13 | All
14 |
15 |
16 | <_Parameter1>NdgrClientSharp.Test
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/NdgrClientSharp/NdgrLiveCommentFetcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Threading;
6 | using System.Threading.Tasks;
7 | using Dwango.Nicolive.Chat.Data;
8 | using Dwango.Nicolive.Chat.Service.Edge;
9 | using NdgrClientSharp.NdgrApi;
10 | using R3;
11 |
12 | namespace NdgrClientSharp
13 | {
14 | ///
15 | /// ニコ生の生放送中のコメントを取得する
16 | ///
17 | public sealed class NdgrLiveCommentFetcher : IDisposable
18 | {
19 | ///
20 | /// ・受信したコメント情報が発信される
21 | /// ・実行中に発生したエラーはOnErrorResumeとして発行
22 | /// ・DisconnectしてもOnCompletedは発行されない
23 | /// ・DisposeするとOnCompletedが発行される
24 | ///
25 | public Observable OnMessageReceived => _messageSubject;
26 |
27 | ///
28 | /// 番組が終了したことを通知する
29 | /// すでに終了済みの番組については発火しない
30 | ///
31 | public Observable OnProgramEnded { get; }
32 |
33 | ///
34 | /// 接続状態
35 | ///
36 | public ReadOnlyReactiveProperty ConnectionStatus => _connectionStatus;
37 |
38 | ///
39 | /// コメント取得エラー発生時の最大リトライ回数
40 | /// 0の場合はリトライしない
41 | ///
42 | public int MaxRetryCount { get; set; } = 5;
43 |
44 | ///
45 | /// コメント取得エラー発生時のリトライ間隔
46 | ///
47 | public TimeSpan RetryInterval { get; set; } = TimeSpan.FromSeconds(2);
48 |
49 | ///
50 | /// 現在受信中のSegment一覧
51 | ///
52 | public IEnumerable CurrentReceivingSegments
53 | {
54 | get
55 | {
56 | lock (_receivedMessages)
57 | {
58 | return _receivedMessages.Keys.ToArray();
59 | }
60 | }
61 | }
62 |
63 | private readonly INdgrApiClient _ndgrApiClient;
64 | private readonly object _gate = new object();
65 | private readonly Subject _messageSubject = new Subject();
66 | private readonly bool _needDisposeNdgrApiClient;
67 |
68 | private readonly SynchronizedReactiveProperty
69 | _connectionStatus = new SynchronizedReactiveProperty(ConnectionState.Disconnected);
70 |
71 | private CancellationTokenSource? _mainCts;
72 | private bool _isDisposed;
73 | private string _latestViewApiUri = string.Empty;
74 | private uint _fetchingSegmentCount = 0;
75 |
76 | private readonly Dictionary> _receivedMessages =
77 | new Dictionary>();
78 |
79 | public NdgrLiveCommentFetcher(INdgrApiClient? ndgrApiClient = null)
80 | {
81 | if (ndgrApiClient == null)
82 | {
83 | _ndgrApiClient = new NdgrApiClient();
84 | _needDisposeNdgrApiClient = true;
85 | }
86 | else
87 | {
88 | _ndgrApiClient = ndgrApiClient;
89 | _needDisposeNdgrApiClient = false;
90 | }
91 |
92 | OnProgramEnded = _messageSubject
93 | .Where(x => x.State?.ProgramStatus?.State is ProgramStatus.Types.State.Ended)
94 | .AsUnitObservable()
95 | .Share();
96 | }
97 |
98 |
99 | ///
100 | /// ViewAPIに接続してコメント取得を開始する
101 | /// 接続中またはすでに接続済みの場合は何もしない
102 | ///
103 | public void Connect(string viewApiUri)
104 | {
105 | lock (_gate)
106 | {
107 | if (_isDisposed) throw new ObjectDisposedException(nameof(NdgrLiveCommentFetcher));
108 | if (ConnectionStatus.CurrentValue != ConnectionState.Disconnected)
109 | {
110 | return;
111 | }
112 |
113 | _mainCts = new CancellationTokenSource();
114 | _connectionStatus.Value = ConnectionState.Connecting;
115 | _latestViewApiUri = viewApiUri;
116 | _fetchingSegmentCount = 0;
117 |
118 | // 取得処理開始
119 | Forget(FetchStartAsync(viewApiUri, _mainCts.Token));
120 | }
121 | }
122 |
123 | ///
124 | /// コメント取得を停止する
125 | ///
126 | public void Disconnect()
127 | {
128 | lock (_gate)
129 | {
130 | if (_isDisposed) return;
131 |
132 | _mainCts?.Cancel();
133 | _mainCts?.Dispose();
134 | _mainCts = null;
135 | _connectionStatus.Value = ConnectionState.Disconnected;
136 | _fetchingSegmentCount = 0;
137 | }
138 | }
139 |
140 |
141 | ///
142 | /// 最後に接続していたViewAPIに再接続する
143 | /// すでに接続済みの場合は切断してから再接続する
144 | ///
145 | public void Reconnect()
146 | {
147 | lock (_gate)
148 | {
149 | if (_isDisposed) throw new ObjectDisposedException(nameof(NdgrLiveCommentFetcher));
150 | if (ConnectionStatus.CurrentValue == ConnectionState.Connected)
151 | {
152 | Disconnect();
153 | }
154 |
155 | if (_latestViewApiUri == string.Empty)
156 | {
157 | throw new InvalidOperationException("No latest ViewAPI URI. Cannot reconnect.");
158 | }
159 |
160 | Connect(_latestViewApiUri);
161 | }
162 | }
163 |
164 |
165 | public void Dispose()
166 | {
167 | lock (_gate)
168 | {
169 | if (_isDisposed) return;
170 | _isDisposed = true;
171 | _receivedMessages.Clear();
172 |
173 | Disconnect();
174 |
175 | if (_needDisposeNdgrApiClient)
176 | {
177 | _ndgrApiClient.Dispose();
178 | }
179 |
180 | _messageSubject.Dispose();
181 | _connectionStatus.Dispose();
182 | }
183 | }
184 |
185 | #region AsyncMainAction
186 |
187 | private async ValueTask FetchStartAsync(string viewApiUri, CancellationToken ct)
188 | {
189 | var tryCount = 0;
190 |
191 | do
192 | {
193 | try
194 | {
195 | // 次の取得するべきViewのunixtimeを取得
196 | var next = await _ndgrApiClient.FetchViewAtNowAsync(viewApiUri, ct);
197 |
198 | if (_connectionStatus.Value == ConnectionState.Connecting)
199 | {
200 | // 取得成功したら接続済にする
201 | _connectionStatus.Value = ConnectionState.Connected;
202 | }
203 |
204 | // コメント取得のためのSegmentおよびNextの取得処理
205 | Forget(FetchLoopAsync(viewApiUri, next.At, ct));
206 | return;
207 | }
208 | catch (OperationCanceledException)
209 | {
210 | // nothing
211 | return;
212 | }
213 | catch (WebException w) when (w.Status == WebExceptionStatus.RequestCanceled)
214 | {
215 | // nothing
216 | return;
217 | }
218 | catch (NdgrApiClientHttpException ex)
219 | {
220 | lock (_gate)
221 | {
222 | _messageSubject.OnErrorResume(ex);
223 | }
224 |
225 | // 503だったときはちょっとまってリトライしてみる
226 | if (ex.HttpStatusCode == HttpStatusCode.ServiceUnavailable)
227 | {
228 | await Task.Delay(RetryInterval, ct);
229 | }
230 | else
231 | {
232 | // 503以外のエラーはリトライしない
233 | Disconnect();
234 | return;
235 | }
236 | }
237 | catch (NdgrApiClientByteReadException ex)
238 | {
239 | lock (_gate)
240 | {
241 | _messageSubject.OnErrorResume(ex);
242 | }
243 |
244 | // 受信バイトがおかしい場合はリトライ
245 | await Task.Delay(RetryInterval, ct);
246 | }
247 | catch (Exception ex)
248 | {
249 | lock (_gate)
250 | {
251 | if (!_messageSubject.IsDisposed)
252 | {
253 | _messageSubject.OnErrorResume(ex);
254 | }
255 | }
256 |
257 | Disconnect();
258 | return;
259 | }
260 | } while (++tryCount <= MaxRetryCount);
261 |
262 | // リトライしてもダメだったらDisconnect
263 | Disconnect();
264 | }
265 |
266 |
267 | ///
268 | /// コメント取得のためのSegmentおよびNextの取得処理
269 | ///
270 | private async ValueTask FetchLoopAsync(string viewApiUri, long unixTime, CancellationToken ct)
271 | {
272 | var targetTime = unixTime;
273 | try
274 | {
275 | while (!ct.IsCancellationRequested)
276 | {
277 | var hasNext = false;
278 | var chunks = _ndgrApiClient.FetchViewAtAsync(viewApiUri, targetTime, ct);
279 | var receivedSegments = new HashSet();
280 |
281 | // SegmentおよびNextの監視
282 | await foreach (var chunk in chunks)
283 | {
284 | switch (chunk.EntryCase)
285 | {
286 | case ChunkedEntry.EntryOneofCase.Segment:
287 | // Segment(コメント本文を返すAPI情報)
288 | var segment = chunk.Segment;
289 | receivedSegments.Add(segment.Uri);
290 | Forget(FetchSegmentAsync(segment, ct));
291 |
292 | break;
293 | case ChunkedEntry.EntryOneofCase.Next:
294 |
295 | // 次のView取得情報
296 | targetTime = chunk.Next.At;
297 | hasNext = true;
298 | break;
299 |
300 | // 未使用
301 | case ChunkedEntry.EntryOneofCase.None:
302 | case ChunkedEntry.EntryOneofCase.Backward:
303 | case ChunkedEntry.EntryOneofCase.Previous:
304 | break;
305 |
306 | default:
307 | throw new ArgumentOutOfRangeException();
308 | }
309 | }
310 |
311 | ct.ThrowIfCancellationRequested();
312 |
313 | // ReceivedSegmentsに含まれないものはDictionaryから削除
314 | lock (_receivedMessages)
315 | {
316 | foreach (var key in _receivedMessages.Keys.ToArray())
317 | {
318 | if (!receivedSegments.Contains(key))
319 | {
320 | _receivedMessages[key]?.Clear();
321 | _receivedMessages.Remove(key);
322 | }
323 | }
324 | }
325 |
326 | if (!hasNext)
327 | {
328 | // Nextがない場合はSegmentの全受信を待ってから終了
329 | // だが実際はNextは無限に存在し続けるぽいからここが実行されることはないはず
330 | while (_fetchingSegmentCount > 0)
331 | {
332 | // 適当に終わりそうな時間だけ待つ
333 | await Task.Delay(TimeSpan.FromSeconds(3), ct);
334 | }
335 |
336 | Disconnect();
337 | }
338 | }
339 | }
340 | catch (OperationCanceledException)
341 | {
342 | // nothing
343 | }
344 | catch (WebException w) when (w.Status == WebExceptionStatus.RequestCanceled)
345 | {
346 | // nothing
347 | }
348 | catch (NdgrApiClientHttpException e)
349 | {
350 | lock (_gate)
351 | {
352 | if (!_messageSubject.IsDisposed)
353 | {
354 | _messageSubject.OnErrorResume(e);
355 | }
356 |
357 | switch (e.HttpStatusCode)
358 | {
359 | case HttpStatusCode.ServiceUnavailable:
360 | // 503の場合はリトライできる可能性あり
361 | Reconnect();
362 | break;
363 | default:
364 | // リカバリ不可能な場合は動作停止
365 | Disconnect();
366 | break;
367 | }
368 | }
369 | }
370 | catch (NdgrApiClientByteReadException e)
371 | {
372 | lock (_gate)
373 | {
374 | if (!_messageSubject.IsDisposed)
375 | {
376 | _messageSubject.OnErrorResume(e);
377 | }
378 |
379 | // バイト列の破損の可能性があるのでリトライ
380 | Reconnect();
381 | }
382 | }
383 | catch (Exception e)
384 | {
385 | // エラーを発行してDisconnect
386 | lock (_gate)
387 | {
388 | if (!_messageSubject.IsDisposed)
389 | {
390 | _messageSubject.OnErrorResume(e);
391 | }
392 | }
393 |
394 | Disconnect();
395 | }
396 | }
397 |
398 | private async ValueTask FetchSegmentAsync(MessageSegment segment, CancellationToken ct = default)
399 | {
400 | var tryCount = 0;
401 |
402 | while (++tryCount <= 2)
403 | {
404 | try
405 | {
406 | _fetchingSegmentCount++;
407 |
408 | HashSet hashSet;
409 |
410 | lock (_receivedMessages)
411 | {
412 | if (!_receivedMessages.TryGetValue(segment.Uri, out var set))
413 | {
414 | hashSet = new HashSet();
415 | _receivedMessages.Add(segment.Uri, hashSet);
416 | }
417 | else
418 | {
419 | hashSet = set;
420 | }
421 | }
422 |
423 | var uri = segment.Uri;
424 |
425 | await foreach (var chunkedMessage in _ndgrApiClient.FetchChunkedMessagesAsync(uri, ct))
426 | {
427 | lock (_gate)
428 | {
429 | var meta = chunkedMessage.Meta;
430 | if (meta != null)
431 | {
432 | // Metaが存在する場合は重複チェック
433 | if (hashSet.Add(chunkedMessage.Meta.Id))
434 | {
435 | _messageSubject.OnNext(chunkedMessage);
436 | }
437 | }
438 | else
439 | {
440 | // Metaが存在しない場合は素通し
441 | _messageSubject.OnNext(chunkedMessage);
442 | }
443 | }
444 | }
445 |
446 | return;
447 | }
448 | catch (OperationCanceledException)
449 | {
450 | throw;
451 | }
452 | catch (NdgrApiClientHttpException e)
453 | {
454 | lock (_gate)
455 | {
456 | if (!_messageSubject.IsDisposed)
457 | {
458 | _messageSubject.OnErrorResume(e);
459 | }
460 | }
461 |
462 | await Task.Delay(RetryInterval, ct);
463 | }
464 | catch (NdgrApiClientByteReadException e)
465 | {
466 | lock (_gate)
467 | {
468 | if (!_messageSubject.IsDisposed)
469 | {
470 | _messageSubject.OnErrorResume(e);
471 | }
472 | }
473 |
474 | // Segmentのバイト列が破損している可能性があるので少し待ってリトライ
475 | await Task.Delay(RetryInterval, ct);
476 | }
477 | catch (WebException w) when (w.Status == WebExceptionStatus.RequestCanceled)
478 | {
479 | return;
480 | }
481 | catch (Exception e)
482 | {
483 | lock (_gate)
484 | {
485 | if (!_messageSubject.IsDisposed)
486 | {
487 | _messageSubject.OnErrorResume(e);
488 | }
489 | }
490 |
491 | // その他のエラーはリトライしない
492 | return;
493 | }
494 | finally
495 | {
496 | _fetchingSegmentCount--;
497 | }
498 | }
499 | }
500 |
501 |
502 | private async void Forget(ValueTask task)
503 | {
504 | try
505 | {
506 | await task;
507 | }
508 | catch (OperationCanceledException)
509 | {
510 | // ignore
511 | }
512 | catch (WebException w) when (w.Status == WebExceptionStatus.RequestCanceled)
513 | {
514 | // nothing
515 | }
516 | catch (Exception e)
517 | {
518 | lock (_gate)
519 | {
520 | if (!_messageSubject.IsDisposed)
521 | {
522 | _messageSubject.OnErrorResume(e);
523 | }
524 | }
525 | }
526 | }
527 |
528 | #endregion
529 | }
530 |
531 | public enum ConnectionState
532 | {
533 | Disconnected,
534 | Connecting,
535 | Connected
536 | }
537 | }
--------------------------------------------------------------------------------
/NdgrClientSharp/NdgrPastCommentFetcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Threading;
5 | using Dwango.Nicolive.Chat.Service.Edge;
6 | using NdgrClientSharp.NdgrApi;
7 | using R3;
8 |
9 | namespace NdgrClientSharp
10 | {
11 | ///
12 | /// 過去のコメントを取得する
13 | ///
14 | public sealed class NdgrPastCommentFetcher : IDisposable
15 | {
16 | private readonly INdgrApiClient _ndgrApiClient;
17 | private readonly bool _needDisposeNdgrApiClient;
18 | private bool _isDisposed;
19 | private readonly object _gate = new object();
20 | private readonly CancellationTokenSource _mainCts = new CancellationTokenSource();
21 |
22 | public NdgrPastCommentFetcher(INdgrApiClient? ndgrApiClient = null)
23 | {
24 | if (ndgrApiClient == null)
25 | {
26 | _ndgrApiClient = new NdgrApiClient();
27 | _needDisposeNdgrApiClient = true;
28 | }
29 | else
30 | {
31 | _ndgrApiClient = ndgrApiClient;
32 | _needDisposeNdgrApiClient = false;
33 | }
34 | }
35 |
36 | ///
37 | /// 過去のコメントを取得する
38 | ///
39 | /// ViewURI
40 | ///
41 | /// 取得数の目安。コメントはまとまった単位で取得するため、この数値を超える数のうちの最小回数まで取得する。
42 | /// nullを指定した場合はすべてのコメントを取得する。
43 | ///
44 | public Observable FetchPastComments(string viewApiUri, int? inaccurateLimit = 100)
45 | {
46 | lock (_gate)
47 | {
48 | if (_isDisposed) throw new ObjectDisposedException(nameof(NdgrPastCommentFetcher));
49 | }
50 |
51 | return Observable.Create(async (o, ct) =>
52 | {
53 | var token = CancellationTokenSource.CreateLinkedTokenSource(_mainCts.Token, ct).Token;
54 |
55 | try
56 | {
57 | var sendCount = 0;
58 |
59 | var now = await _ndgrApiClient.FetchViewAtNowAsync(viewApiUri, token);
60 | var previous = new List();
61 | BackwardSegment? backward = null;
62 |
63 | await foreach (var chunkedEntry in _ndgrApiClient.FetchViewAtAsync(viewApiUri, now.At, token))
64 | {
65 | if (chunkedEntry.EntryCase == ChunkedEntry.EntryOneofCase.Previous)
66 | {
67 | previous.Add(chunkedEntry.Previous);
68 | }
69 | else if (chunkedEntry.EntryCase == ChunkedEntry.EntryOneofCase.Backward)
70 | {
71 | backward = chunkedEntry.Backward;
72 | }
73 | else if (chunkedEntry.EntryCase == ChunkedEntry.EntryOneofCase.Segment)
74 | {
75 | // 通常のSegmentが来た時点で止める
76 | break;
77 | }
78 | }
79 |
80 |
81 | // PreviousSegmentの新しい側から取得
82 | foreach (var p in previous.OrderByDescending(x => x.Until))
83 | {
84 | await foreach (var chunkedMessage in _ndgrApiClient.FetchChunkedMessagesAsync(p.Uri, token))
85 | {
86 | o.OnNext(chunkedMessage);
87 | sendCount++;
88 | }
89 |
90 | if (sendCount >= inaccurateLimit)
91 | {
92 | o.OnCompleted();
93 | return;
94 | }
95 | }
96 |
97 | if (backward != null)
98 | {
99 | var uri = backward.Segment.Uri;
100 | // Previousよりさらに過去のコメントの取得
101 | while (!token.IsCancellationRequested)
102 | {
103 | var packedSegment = await _ndgrApiClient.FetchPackedSegmentAsync(uri, token);
104 | foreach (var message in packedSegment.Messages)
105 | {
106 | o.OnNext(message);
107 | sendCount++;
108 | }
109 |
110 | if (sendCount >= inaccurateLimit)
111 | {
112 | o.OnCompleted();
113 | return;
114 | }
115 |
116 | if (packedSegment.Next != null)
117 | {
118 | uri = packedSegment.Next.Uri;
119 | continue;
120 | }
121 |
122 | break;
123 | }
124 | }
125 |
126 | o.OnCompleted();
127 | }
128 | catch (Exception ex)
129 | {
130 | if (ex is OperationCanceledException)
131 | {
132 | o.OnCompleted();
133 | }
134 | else
135 | {
136 | o.OnCompleted(ex);
137 | }
138 | }
139 | });
140 | }
141 |
142 |
143 | public void Dispose()
144 | {
145 | lock (_gate)
146 | {
147 | if (_isDisposed) return;
148 | _isDisposed = true;
149 | _mainCts.Cancel();
150 | _mainCts.Dispose();
151 |
152 | if (_needDisposeNdgrApiClient)
153 | {
154 | _ndgrApiClient.Dispose();
155 | }
156 | }
157 | }
158 | }
159 | }
--------------------------------------------------------------------------------
/NdgrClientSharp/NdgrSnapshotFetcher.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Runtime.CompilerServices;
4 | using System.Threading;
5 | using Dwango.Nicolive.Chat.Service.Edge;
6 | using NdgrClientSharp.NdgrApi;
7 |
8 | namespace NdgrClientSharp
9 | {
10 | public sealed class NdgrSnapshotFetcher : IDisposable
11 | {
12 | private readonly INdgrApiClient _ndgrApiClient;
13 | private readonly bool _needDisposeNdgrApiClient;
14 | private bool _isDisposed;
15 | private readonly object _gate = new object();
16 | private readonly CancellationTokenSource _mainCts = new CancellationTokenSource();
17 |
18 | public NdgrSnapshotFetcher(INdgrApiClient? ndgrApiClient = null)
19 | {
20 | if (ndgrApiClient == null)
21 | {
22 | _ndgrApiClient = new NdgrApiClient();
23 | _needDisposeNdgrApiClient = true;
24 | }
25 | else
26 | {
27 | _ndgrApiClient = ndgrApiClient;
28 | _needDisposeNdgrApiClient = false;
29 | }
30 | }
31 |
32 | ///
33 | /// 現在のスナップショットを取得する
34 | ///
35 | public async IAsyncEnumerable FetchCurrentSnapshotAsync(
36 | string viewUri,
37 | [EnumeratorCancellation] CancellationToken token =default)
38 | {
39 | lock (_gate)
40 | {
41 | if (_isDisposed) throw new ObjectDisposedException(nameof(NdgrSnapshotFetcher));
42 | }
43 |
44 | var ct = CancellationTokenSource.CreateLinkedTokenSource(_mainCts.Token, token).Token;
45 |
46 | var now = await _ndgrApiClient.FetchViewAtNowAsync(viewUri, ct);
47 |
48 | await foreach (var ce in _ndgrApiClient.FetchViewAtAsync(viewUri, now.At, ct))
49 | {
50 | if (ce.EntryCase != ChunkedEntry.EntryOneofCase.Backward) continue;
51 |
52 | var backward = ce.Backward;
53 | await foreach (var s in _ndgrApiClient.FetchChunkedMessagesAsync(backward.Snapshot.Uri, ct))
54 | {
55 | yield return s;
56 | }
57 | }
58 | }
59 |
60 | public void Dispose()
61 | {
62 | lock (_gate)
63 | {
64 | if (_isDisposed) return;
65 | _isDisposed = true;
66 |
67 | _mainCts.Cancel();
68 | _mainCts.Dispose();
69 |
70 | if (_needDisposeNdgrApiClient)
71 | {
72 | _ndgrApiClient.Dispose();
73 | }
74 | }
75 | }
76 | }
77 | }
--------------------------------------------------------------------------------
/NdgrClientSharp/Utilities/NdgrProtobufStreamReader.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Buffers;
3 | using System.IO;
4 |
5 | namespace NdgrClientSharp.Utilities
6 | {
7 | internal sealed class NdgrProtobufStreamReader : IDisposable
8 | {
9 | private readonly MemoryStream _bufferStream = new MemoryStream();
10 |
11 | public void AddNewChunk(byte[] chunk, int? length = null)
12 | {
13 | // 末尾に書き込む
14 | _bufferStream.Position = _bufferStream.Length;
15 | _bufferStream.Write(chunk, 0, length ?? chunk.Length);
16 | }
17 |
18 | ///
19 | /// Varintの読み取り
20 | /// バイト列の先頭をVarint(Protocol Buffersの1データサイズの長さ)として読み取る
21 | ///
22 | /// 参考
23 | /// https://protobuf.dev/programming-guides/encoding/
24 | /// https://github.com/protocolbuffers/protobuf/blob/384fabf35a9b5d1f1502edaf9a139b1f51551a01/csharp/src/Google.Protobuf/ParsingPrimitives.cs#L720-L73
25 | /// This implementation is inspired by the Protocol Buffers library by Google.
26 | ///
27 | ///
28 | ///
29 | internal (int shift, uint result)? ReadVarint()
30 | {
31 | var result = 0;
32 | var offset = 0;
33 | var shift = 0;
34 |
35 | _bufferStream.Position = 0;
36 |
37 | for (; offset < 32; offset += 7)
38 | {
39 | int b = _bufferStream.ReadByte();
40 | if (b == -1)
41 | {
42 | return null;
43 | }
44 |
45 | shift++;
46 | result |= (b & 0x7f) << offset;
47 | if ((b & 0x80) == 0)
48 | {
49 | return (shift, (uint)result);
50 | }
51 | }
52 |
53 | for (; offset < 64; offset += 7)
54 | {
55 | var b = _bufferStream.ReadByte();
56 | if (b == -1)
57 | {
58 | return null;
59 | }
60 |
61 | shift++;
62 |
63 | if ((b & 0x80) == 0)
64 | {
65 | return (shift, (uint)result);
66 | }
67 | }
68 |
69 | // Varintの読み取りに失敗した
70 | throw new NdgrProtobufStreamReaderException("Failed to read varint from the stream.");
71 | }
72 |
73 | ///
74 | /// Bufferの先頭からVarintを読み取り、その値に基づいてバッファからメッセージを取り出す
75 | /// falseが返却された場合は、まだ十分なデータを受信仕切っていないことを示す
76 | ///
77 | /// 戻り値がtrueの場合はProtoBuffのバイナリ列が格納されている
78 | /// 正常に読み取れたか、読み取れた場合はバイトサイズも返す
79 | ///
80 | public (bool isValid, int messageSize) UnshiftChunk(byte[] messageBuffer)
81 | {
82 | var readVarint = ReadVarint();
83 | if (readVarint == null) return (false, 0);
84 |
85 | var (offset, varint) = readVarint.Value;
86 |
87 | // offset : Varint分のバイト数
88 | // varint : 実際のProtoBuffのメッセージサイズ
89 | // offset + varint : この長さ分のバイト列がProtoBuffとして解釈するために必要
90 | if (offset + varint > _bufferStream.Length)
91 | {
92 | // まだ十分なデータを受信していない
93 | return (false, 0);
94 | }
95 |
96 | if (offset + varint > messageBuffer.Length)
97 | {
98 | // messageBufferのサイズが足りない
99 | throw new NdgrProtobufStreamReaderException("Message size is too large.");
100 | }
101 |
102 |
103 | // Read the message bytes directly into the array
104 | _bufferStream.Position = offset;
105 | _bufferStream.Read(messageBuffer, 0, (int)varint);
106 |
107 | // Shift the buffer content
108 | var remainingLength = (int)(_bufferStream.Length - offset - varint);
109 | using var rentedBuffer = new PooledArray(remainingLength);
110 |
111 | _bufferStream.Position = offset + varint;
112 | _bufferStream.Read(rentedBuffer.Array, 0, remainingLength);
113 |
114 | // Reset and refill the stream with the remaining data
115 | _bufferStream.SetLength(0);
116 | _bufferStream.Write(rentedBuffer.Array, 0, remainingLength);
117 |
118 | return (true, (int)varint);
119 | }
120 |
121 | public void Dispose()
122 | {
123 | _bufferStream.Dispose();
124 | }
125 | }
126 |
127 | public class NdgrProtobufStreamReaderException : Exception
128 | {
129 | public NdgrProtobufStreamReaderException(string message) : base(message)
130 | {
131 | }
132 | }
133 | }
--------------------------------------------------------------------------------
/NdgrClientSharp/Utilities/PooledArray.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Buffers;
3 | using System.Threading;
4 |
5 | namespace NdgrClientSharp.Utilities
6 | {
7 | public struct PooledArray : IDisposable
8 | {
9 | public T[] Array => _array ?? throw new ObjectDisposedException(nameof(PooledArray));
10 | private readonly ArrayPool _pool;
11 | private readonly int _length;
12 |
13 | private T[]? _array;
14 | private int _disposed;
15 |
16 | public int Length => _length;
17 | public Span Span => Array.AsSpan(0, _length);
18 |
19 | public PooledArray(int length, ArrayPool? pool = null)
20 | {
21 | _pool = pool ?? ArrayPool.Shared;
22 | _array = _pool.Rent(length);
23 | _length = length;
24 | _disposed = 0;
25 | }
26 |
27 | public void Dispose()
28 | {
29 | if (Interlocked.Exchange(ref _disposed, 1) == 0)
30 | {
31 | if (_array != null)
32 | {
33 | _pool.Return(_array);
34 | _array = null;
35 | }
36 | }
37 | }
38 | }
39 | }
--------------------------------------------------------------------------------
/NdgrClientSharp/Utilities/PooledBuffer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Buffers;
3 | using System.Threading;
4 |
5 | namespace NdgrClientSharp.Utilities
6 | {
7 | public struct PooledBuffer : IDisposable
8 | {
9 | public byte[] Array { get; }
10 | public int Length { get; }
11 | private readonly ArrayPool _pool;
12 | private int _disposed;
13 |
14 | public PooledBuffer(byte[] array, int length, ArrayPool pool)
15 | {
16 | Array = array;
17 | Length = length;
18 | _pool = pool;
19 | _disposed = 0;
20 | }
21 |
22 | public ReadOnlySpan Span => Array.AsSpan(0, Length);
23 |
24 | public void Dispose()
25 | {
26 | if (Interlocked.Exchange(ref _disposed, 1) == 0)
27 | {
28 | _pool.Return(Array);
29 | }
30 | }
31 | }
32 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NdgrClientSharp
2 |
3 | ニコニコ生放送の新コメントサーバー「NDGR(のどぐろ)」用のC#クライアントです。
4 | 次の機能を使うことができます。
5 |
6 | * 各APIと通信できるSimpleなクライアント
7 | * `/api/view/v4/:view?at=now`
8 | * `/api/view/v4/:view?at=:at`
9 | * `/data/segment/v4/:segment`
10 | * `/data/backward/v4/:backward`
11 | * `/data/snapshot/v4/:snapshot`
12 | * 通信レイヤーをラップしたEasyなクライアント
13 | * 生放送のリアルタイムなコメント受信
14 | * 生放送の過去のコメント取得
15 | * 生放送の現在の状態(スナップショット)の取得
16 |
17 | ## 動作環境
18 |
19 | `C# 8.0`以上 & `.NET Standard 2.0`以上向けです。
20 |
21 | また、動作には次のライブラリが必要です。
22 |
23 | * [Google.Protobuf](https://www.nuget.org/packages/Google.Protobuf/)
24 | * [R3](https://www.nuget.org/packages/R3)
25 |
26 | ## プロジェクト構成
27 |
28 | * `NdgrClientSharp` : クライアント本体
29 | * `NdgrClientSharp.Protocol` : 通信及びクライアントが利用するProtoBuffから生成されたデータ構造置き場
30 | * `NdgrClientSharp.Test` : テスト
31 | * `Sandbox` : 実験用のコード置き場
32 |
33 | ## ProtocolBufferの定義について
34 |
35 | 本ライブラリが使用するProtoBuffの定義は[N Airが定義するprotoファイル(v2024.801.103927)](https://github.com/n-air-app/nicolive-comment-protobuf/releases/tag/v2024.801.103927)を元にしています。
36 |
37 |
38 | # 使用方法
39 |
40 | ## 導入
41 |
42 | Releaseよりzipでダウンロードして、次のdllをプロジェクトに入れて下さい。
43 |
44 | * `NdgrClientSharp.dll`
45 | * `NdgrClientSharp.Protocol.dll`
46 |
47 | また次のライブラリも導入している必要があります。
48 |
49 | * [Google.Protobuf](https://www.nuget.org/packages/Google.Protobuf/)
50 | * [R3](https://www.nuget.org/packages/R3)
51 |
52 | 加えてUnityで動作させる場合は[R3.Unity](https://github.com/Cysharp/R3?tab=readme-ov-file#unity)も導入してください。
53 |
54 | ### Unityで使用する場合
55 |
56 | Unityで使用する場合はHttpHandlerを[YetAnotherHttpHandler](https://github.com/Cysharp/YetAnotherHttpHandler)に差し替えて使用してください。
57 |
58 | ```cs
59 | var handler = new YetAnotherHttpHandler(); // here
60 | var httpClient = new HttpClient(handler);
61 |
62 | var ndgrApiClient = new NdgrApiClient(httpClient);
63 | var ndgrLiveCommentFetcher = new NdgrLiveCommentFetcher(ndgrApiClient);
64 | ```
65 |
66 | ## 使い方
67 |
68 | ### NdgrLiveCommentFetcher
69 |
70 | ニコニコ生放送の放送中のコメントをリアルタイムに取得するクライアントです。
71 |
72 | `OnMessageReceived`から`Observable`としてコメントを取得できます。
73 |
74 | ```cs
75 | // 生放送コメント取得用のクライアントを生成
76 | var liveCommentFetcher = new NdgrLiveCommentFetcher();
77 |
78 | // コメントの受信準備
79 | liveCommentFetcher
80 | .OnMessageReceived
81 | .Subscribe(chukedMessage =>
82 | {
83 | switch (chukedMessage.PayloadCase)
84 | {
85 | case ChunkedMessage.PayloadOneofCase.Message:
86 | // コメントやギフトの情報などはMessage
87 | Console.WriteLine(chukedMessage.Message);
88 | break;
89 | case ChunkedMessage.PayloadOneofCase.State:
90 | // 番組他状態の変更などはStateから取得可能
91 | Console.WriteLine(chukedMessage.State);
92 | break;
93 |
94 | default:
95 | break;
96 | }
97 | });
98 |
99 | // コメントの受信開始
100 | liveCommentFetcher.Connect(viewApiUri);
101 |
102 | // ---
103 |
104 | // コメントの受信停止
105 | liveCommentFetcher.Disconnect();
106 |
107 | // リソースの解放
108 | liveCommentFetcher.Dispose();
109 | ```
110 |
111 | #### 補足1:メッセージの発行スレッド
112 |
113 | `ConfigureAwait(true)`として動作します。そのため`Connect()`したときの同じスレッドでメッセージ発行される**はず**です。
114 |
115 | ただもしスレッドを確実に指定したい場合、たとえばUnityなどでは`ObserveOnMainThread`を保険として挟んでおくのはありかもしれません。
116 |
117 | ```cs
118 | // Unityで使う場合
119 | // R3.UnityのObserveOnMainThreadを使うことで
120 | // 確実にUnityメインスレッドで受信できる
121 | liveCommentFetcher
122 | .OnMessageReceived
123 | // これ
124 | .ObserveOnMainThread()
125 | .Subscribe(chukedMessage =>
126 | {
127 | switch (chukedMessage.PayloadCase)
128 | {
129 | case ChunkedMessage.PayloadOneofCase.Message:
130 | // コメントやギフトの情報などはMessage
131 | Console.WriteLine(chukedMessage.Message);
132 | break;
133 | case ChunkedMessage.PayloadOneofCase.State:
134 | // 番組他状態の変更などはStateから取得可能
135 | Console.WriteLine(chukedMessage.State);
136 | break;
137 |
138 | default:
139 | break;
140 | }
141 | });
142 | ```
143 |
144 |
145 | #### 補足2:NdgrApiClientの指定
146 |
147 | `NdgrApiClient` を生成してコンストラクタで指定することができます。
148 | `HttpClient`の挙動をカスタマイズしたい場合は、カスタマイズした`HttpClient`で`NdgrApiClient`を初期化し、それを`NdgrLiveCommentFetcher`に渡してください。
149 |
150 | ```cs
151 | var httpClient = new HttpClient();
152 | var negrApiClient = new NdgrApiClient(httpClient);
153 |
154 | // NdgrApiClientを指定することが可能
155 | var ndgrLiveCommentFetcher = new NdgrLiveCommentFetcher(negrApiClient);
156 |
157 | // Disposeは手動で
158 | ndgrLiveCommentFetcher.Dispose();
159 | negrApiClient.Dispose();
160 | httpClient.Dispose();
161 | ```
162 |
163 | #### 補足3:発生したエラーの検知
164 |
165 | エラーは`Observable`の`OnErrorResume`として通知されます。
166 |
167 | ```cs
168 | liveCommentFetcher
169 | .OnMessageReceived
170 | .Subscribe(
171 | onNext: chukedMessage => Console.WriteLine(chukedMessage),
172 | onErrorResume: ex => Console.WriteLine(ex),
173 | onCompleted: result => Console.WriteLine(result));
174 | ```
175 |
176 | R3のObservableについては詳しくは[こちらの記事](https://qiita.com/toRisouP/items/e7be5a5a43058556db8f)などを参照。
177 |
178 | #### 補足4: エラー発生時の再接続
179 |
180 | `NdgrLiveCommentFetcher`は通信時、ステータスコード「`503 Service Unavailable`」が返ってきた場合のみ自動で再接続を試みます。
181 | 再接続時のリトライ数やバックオフタイムはプロパティから設定可能です。
182 |
183 | ```cs
184 | var liveCommentFetcher = new NdgrLiveCommentFetcher();
185 |
186 | liveCommentFetcher.MaxRetryCount = 5;
187 | liveCommentFetcher.RetryInterval = TimeSpan.FromSeconds(5);
188 | ```
189 |
190 | #### 補足5: 接続状態の確認
191 |
192 | クライアントの接続状況は`ConnectionStatus`より`ReadOnlyReactiveProperty`として取得可能です。
193 |
194 |
195 | ```cs
196 | // 接続状態の通知
197 | liveCommentFetcher.ConnectionStatus
198 | .Subscribe(state =>
199 | {
200 | switch (state)
201 | {
202 | case ConnectionState.Disconnected:
203 | break;
204 | case ConnectionState.Connecting:
205 | break;
206 | case ConnectionState.Connected:
207 | break;
208 | default:
209 | break;
210 | }
211 | });
212 |
213 | // 同期的に接続状態を取得
214 | Console.WriteLine(liveCommentFetcher.ConnectionStatus.CurrentValue);
215 | ```
216 |
217 | #### 補足6: 番組終了時の挙動
218 |
219 | 放送中の番組に対して`Connect()`していた場合、**番組が終了しても自動でDisconnectはしません。**
220 | もし番組が終了を検知して`Disconnect()`したい場合は`OnProgramEnded`を購読して発行されるイベントを利用してください。
221 |
222 | ```cs
223 | using var ndgrLiveCommentFetcher = new NdgrLiveCommentFetcher();
224 | ndgrLiveCommentFetcher.Connect(viewApiUri);
225 |
226 | // 番組が終了時にイベントが発行される
227 | // それのイベントを受けてDisconnectする
228 | ndgrLiveCommentFetcher
229 | .OnProgramEnded
230 | .Subscribe(_ => ndgrLiveCommentFetcher.Disconnect());
231 | ```
232 |
233 |
234 | なお、すでに終了済みの番組に対して`Connect()`をした場合、`OnProgramEnded`は発行されません。
235 | 番組状態を別のAPIで取得し、放送中であることを確認してから`Connect()`してください。
236 |
237 | ### NdgrPastCommentFetcher
238 |
239 | 放送中番組の過去のコメント(`ChunckedMessage`)やタイムシフトからコメントを取得するクライントです。
240 | 現在時刻から遡って指定件数くらいのコメントを`Observable`として取得できます。
241 |
242 |
243 | ```cs
244 | var pastCommentFetcher = new NdgrPastCommentFetcher();
245 |
246 | // 最低100件取得する
247 | pastCommentFetcher
248 | .FetchPastComments(viewApiUri, 100)
249 | .Subscribe(x => Console.WriteLine(x.Message));
250 |
251 | pastCommentFetcher.Dispose();
252 | ```
253 |
254 | `FetchPastComments()`の引数としてコメントの件数が指定できます。ただしこの引数は**最低でもこの件数を取得する**という挙動をします(コメント取得はある程度まとまった単位で実行されるため、指定した個数ピッタリ取得することができない。)
255 |
256 | またこの引数にnullを指定した場合は過去のコメントをすべて取得します。
257 |
258 |
259 | #### 補足:コメントの順序
260 |
261 | 件数を指定した場合、現在時刻から遡る方向で指定件数分はコメントを取得します。
262 | ただし、`Observable`から発行されるメッセージの順序は時刻順であることを**保証しません**。
263 |
264 | 順序が重要な場合は受信後に自身でソートしてください。
265 |
266 | ```cs
267 | using var pastCommentFetcher = new NdgrPastCommentFetcher();
268 |
269 | // 最低100件取得する
270 | var list = await pastCommentFetcher
271 | .FetchPastComments(viewApiUri, 100)
272 | .ToListAsync();
273 |
274 | // 取得したコメントを並び替えて表示
275 | foreach (var c in list
276 | .Where(x => x.Meta?.At != null)
277 | .OrderBy(x => x.Meta.At))
278 | {
279 | Console.WriteLine(c);
280 | }
281 | ```
282 |
283 | ### NdgrSnapshotFetcher
284 |
285 | 生放送の現在の状態(Snapshot)を取得するクライアントです。
286 |
287 | ```cs
288 | using var ndgrSnapshotFetcher = new NdgrSnapshotFetcher();
289 |
290 | // 現在の状態(運営コメントの設定やアンケートの状態)を取得する
291 | await foreach (var chunkedMessage in ndgrSnapshotFetcher.FetchCurrentSnapshotAsync(viewApiUri))
292 | {
293 | Console.WriteLine(chunkedMessage);
294 | }
295 | ```
296 |
297 |
298 | ### NdgrApiClient
299 |
300 | **「NdgrApiClient」は上級者向けのクライアントです。**
301 | より簡単にコメント受信だけがしたい場合は**NdgrLiveCommentFetcher**か**NdgrPastCommentFetcher**を使って下さい。
302 |
303 | NDGRと通信するためのAPIクライアントです。NDGRのAPIとの通信をほぼラップせずに提供します。
304 | 結果はすべてProtoBuffから自動生成されたデータ構造をそのまま返します。
305 |
306 | また`NdgrLiveCommentFetcher`/`NdgrPastCommentFetcher`/`NdgrSnapshotFetcher`が内部的に依存するクライアントでもあります。
307 |
308 | 自身でNDGRとの通信を細かく制御したい場合に使用してください。
309 |
310 | ```cs
311 | // 初期化
312 | using (var ndgrApiClient = new NdgrApiClient())
313 | {
314 | // /api/view/v4/:view?at=now の取得
315 | var next = await ndgrApiClient.FetchViewAtNowAsync(viewApiUri);
316 |
317 | // /api/view/v4/:view?at=unixtime の取得
318 | await foreach (var chunkedEntry in ndgrApiClient.FetchViewAtAsync(viewApiUri, next.At))
319 | {
320 | switch (chunkedEntry.EntryCase)
321 | {
322 | case ChunkedEntry.EntryOneofCase.None:
323 | break;
324 | case ChunkedEntry.EntryOneofCase.Backward:
325 | break;
326 | case ChunkedEntry.EntryOneofCase.Previous:
327 | break;
328 | case ChunkedEntry.EntryOneofCase.Segment:
329 | break;
330 | case ChunkedEntry.EntryOneofCase.Next:
331 | break;
332 | default:
333 | throw new ArgumentOutOfRangeException();
334 | }
335 | }
336 | }
337 | ```
338 |
339 | また通信の挙動をより細かく制御したい場合はコンストラクタに`HttpClient`を渡すことができます。
340 | ただし**コンストラクタでHttpClientを指定した場合、このHttpClientのDisposeは自身で管理してください**
341 |
342 | ```cs
343 | // HttpClientの作成
344 | var httpClient = new HttpClient(new HttpClientHandler());
345 |
346 | // UAを設定したり
347 | httpClient.DefaultRequestHeaders.Add("User-Agent", "my-user-agent");
348 |
349 | // ndgrApiClientの作成
350 | var ndgrApiClient = new NdgrApiClient(httpClient);
351 |
352 | // NdgrApiClientをDisposeしただけではHttpClientはDisposeされない
353 | // 自身でHttpClientのDisposeを行う必要あり
354 | ndgrApiClient.Dispose();
355 | httpClient.Dispose();
356 | ```
357 |
358 | #### 補足: 例外
359 |
360 | 次の例外が定義されており、`NdgrApiClient`の動作に応じて例外が発行されます。
361 |
362 | ```cs
363 | // 基底クラス
364 | public abstract class NdgrApiClientException : Exception
365 | {
366 | protected NdgrApiClientException(string message) : base(message)
367 | {
368 | }
369 | }
370 |
371 | // バイトの読み出し、ProtocolBuffer周りで例外が発生した場合に発行
372 | public sealed class NdgrApiClientByteReadException : NdgrApiClientException
373 | {
374 | public NdgrApiClientByteReadException(string message) : base(message)
375 | {
376 | }
377 | }
378 |
379 | // 通信に失敗した場合に発行
380 | public sealed class NdgrApiClientHttpException : Exception
381 | {
382 | public HttpStatusCode HttpStatusCode { get; }
383 |
384 | public NdgrApiClientHttpException(HttpStatusCode httpStatusCode)
385 | {
386 | HttpStatusCode = httpStatusCode;
387 | }
388 |
389 | public override string ToString()
390 | {
391 | return $"{base.ToString()}, {nameof(HttpStatusCode)}: {HttpStatusCode}";
392 | }
393 | }
394 | ```
395 |
396 |
397 | # ライセンスについて
398 |
399 | MITライセンスです。
400 |
401 | # 権利表記
402 |
403 | ### R3
404 |
405 | MIT License
406 |
407 | Copyright (c) 2024 Cysharp, Inc.
408 |
409 | Permission is hereby granted, free of charge, to any person obtaining a copy
410 | of this software and associated documentation files (the "Software"), to deal
411 | in the Software without restriction, including without limitation the rights
412 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
413 | copies of the Software, and to permit persons to whom the Software is
414 | furnished to do so, subject to the following conditions:
415 |
416 | The above copyright notice and this permission notice shall be included in all
417 | copies or substantial portions of the Software.
418 |
419 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
420 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
421 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
422 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
423 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
424 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
425 | SOFTWARE.
426 |
427 |
428 | ### Protocol Buffers
429 |
430 | ```
431 | Copyright 2008 Google Inc. All rights reserved.
432 |
433 | Redistribution and use in source and binary forms, with or without
434 | modification, are permitted provided that the following conditions are
435 | met:
436 |
437 | * Redistributions of source code must retain the above copyright
438 | notice, this list of conditions and the following disclaimer.
439 | * Redistributions in binary form must reproduce the above
440 | copyright notice, this list of conditions and the following disclaimer
441 | in the documentation and/or other materials provided with the
442 | distribution.
443 | * Neither the name of Google Inc. nor the names of its
444 | contributors may be used to endorse or promote products derived from
445 | this software without specific prior written permission.
446 |
447 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
448 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
449 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
450 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
451 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
452 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
453 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
454 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
455 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
456 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
457 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
458 |
459 | Code generated by the Protocol Buffer compiler is owned by the owner
460 | of the input file used when generating it. This code is not
461 | standalone and requires a support library to be linked with it. This
462 | support library is itself covered by the above license.
463 | ```
464 |
--------------------------------------------------------------------------------
/Sandbox/Sandbox.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Net.Http;
4 | using System.Threading;
5 | using System.Threading.Tasks;
6 | using Dwango.Nicolive.Chat.Data;
7 | using Dwango.Nicolive.Chat.Service.Edge;
8 | using NdgrClientSharp;
9 | using NdgrClientSharp.NdgrApi;
10 | using R3;
11 |
12 | var viewApiUri = "";
13 | using var live = new NdgrLiveCommentFetcher();
14 |
15 |
16 | live.ConnectionStatus.Subscribe(x => Console.WriteLine(x));
17 | live.OnMessageReceived.Subscribe(x => Console.WriteLine(x));
18 | live.Connect(viewApiUri);
19 | live.OnProgramEnded.Subscribe(_ => live.Disconnect());
20 |
21 |
22 | Console.ReadLine();
--------------------------------------------------------------------------------
/Sandbox/Sandbox.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | enable
5 | Exe
6 | netcoreapp3.1
7 | 12
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------