Skip to content

Commit

Permalink
Change for the OAuth APIs from old APIs. #57
Browse files Browse the repository at this point in the history
  • Loading branch information
noriokun4649 committed May 31, 2022
1 parent 3f1f8eb commit 65ba443
Show file tree
Hide file tree
Showing 5 changed files with 127 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using TVTComment.Model.ChatCollectServiceEntry;
using TVTComment.Model.NiconicoUtils;
using static TVTComment.Model.NiconicoUtils.OAuthApiUtils;

namespace TVTComment.Model.ChatCollectService
{
Expand Down Expand Up @@ -56,6 +58,7 @@ public string GetInformationText()
private readonly NiconicoUtils.NicoLiveCommentReceiver commentReceiver;
private readonly NiconicoUtils.NicoLiveCommentSender commentSender;
private readonly ConcurrentQueue<NiconicoUtils.NiconicoCommentXmlTag> commentTagQueue = new ConcurrentQueue<NiconicoUtils.NiconicoCommentXmlTag>();
private readonly NiconicoLoginSession session;

private string originalLiveId = "";
private string liveId = "";
Expand All @@ -70,6 +73,7 @@ public NewNiconicoJikkyouChatCollectService(
NiconicoUtils.NiconicoLoginSession niconicoLoginSession
)
{
session = niconicoLoginSession;
ServiceEntry = serviceEntry;
this.liveIdResolver = liveIdResolver;

Expand All @@ -80,9 +84,16 @@ NiconicoUtils.NiconicoLoginSession niconicoLoginSession
handler.CookieContainer.Add(niconicoLoginSession.Cookie);
httpClient = new HttpClient(handler);
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", ua);

commentReceiver = new NiconicoUtils.NicoLiveCommentReceiver(niconicoLoginSession);
commentSender = new NiconicoUtils.NicoLiveCommentSender(niconicoLoginSession);
try
{
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", $"Bearer {niconicoLoginSession.Token}");
commentReceiver = new NiconicoUtils.NicoLiveCommentReceiver(niconicoLoginSession);
commentSender = new NiconicoUtils.NicoLiveCommentSender(niconicoLoginSession);
}
catch(InvalidOperationException e)
{
throw new ChatCollectServiceCreationException(e.Message);
}
}

public IEnumerable<Chat> GetChats(ChannelInfo channel, EventInfo _, DateTime time)
Expand Down Expand Up @@ -154,24 +165,20 @@ public IEnumerable<Chat> GetChats(ChannelInfo channel, DateTime time)

private async Task CollectChat(string originalLiveId, CancellationToken cancellationToken)
{
Stream playerStatusStr;
try
{
if (!originalLiveId.StartsWith("lv")) // 代替えAPIではコミュニティ・チャンネルにおけるコメント鯖取得ができないのでlvを取得しに行く
{
var getLiveId = await httpClient.GetStreamAsync($"https://live2.nicovideo.jp/unama/tool/v1/broadcasters/social_group/{originalLiveId}/program", cancellationToken).ConfigureAwait(false);
var liveIdJson = await JsonDocument.ParseAsync(getLiveId, cancellationToken: cancellationToken).ConfigureAwait(false);
var liveIdRoot = liveIdJson.RootElement;
if (!liveIdRoot.GetProperty("meta").GetProperty("errorCode").GetString().Equals("OK")) throw new ChatReceivingException("コミュニティ・チャンネルが見つかりませんでした");
originalLiveId = liveIdRoot.GetProperty("data").GetProperty("nicoliveProgramId").GetString(); // lvから始まるLiveIDに置き換え

originalLiveId = await OAuthApiUtils.GetProgramIdFromChAsync(httpClient, cancellationToken, originalLiveId).ConfigureAwait(false);
}
playerStatusStr = await httpClient.GetStreamAsync($"https://live2.nicovideo.jp/unama/watch/{originalLiveId}/programinfo", cancellationToken).ConfigureAwait(false);
liveId = await OAuthApiUtils.GetProgramInfoFromCommunityId(httpClient, cancellationToken, originalLiveId, session.UserId).ConfigureAwait(false);
}
catch (HttpRequestException e)
{
if (e.StatusCode == System.Net.HttpStatusCode.NotFound)
throw new LiveNotFoundChatReceivingException();
if (e.StatusCode == System.Net.HttpStatusCode.Unauthorized)
throw new ChatReceivingException("OAuth認可の有効期限が切れたか、アプリケーション連携から許可が解除された可能性があります");
if (e.StatusCode == System.Net.HttpStatusCode.ServiceUnavailable)
throw new ChatReceivingException("ニコニコのサーバーがメンテナンス中の可能性があります");
if (e.StatusCode == System.Net.HttpStatusCode.InternalServerError)
Expand All @@ -180,15 +187,6 @@ private async Task CollectChat(string originalLiveId, CancellationToken cancella
throw new ChatReceivingException("サーバーとの通信でエラーが発生しました", e);
}

var playerStatus = await JsonDocument.ParseAsync(playerStatusStr, cancellationToken: cancellationToken).ConfigureAwait(false);
var playerStatusRoot = playerStatus.RootElement;

if (playerStatusRoot.GetProperty("data").GetProperty("rooms").GetArrayLength() <= 0)
throw new LiveNotFoundChatReceivingException();

liveId = playerStatusRoot.GetProperty("data").GetProperty("socialGroup").GetProperty("id").GetString();


try
{
await foreach (NiconicoUtils.NiconicoCommentXmlTag tag in commentReceiver.Receive(originalLiveId, cancellationToken))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using TVTComment.Model.NiconicoUtils;

namespace TVTComment.Model.ChatCollectService
{
Expand Down Expand Up @@ -49,6 +50,7 @@ protected ChatReceivingException(
private readonly NiconicoUtils.NicoLiveCommentSender commentSender;
private DateTime lastHeartbeatTime = DateTime.MinValue;
private readonly CancellationTokenSource cancel = new CancellationTokenSource();
private readonly NiconicoLoginSession loginSession;

public NiconicoLiveChatCollectService(
ChatCollectServiceEntry.IChatCollectServiceEntry serviceEntry, string liveId,
Expand All @@ -57,6 +59,7 @@ NiconicoUtils.NiconicoLoginSession session
{
ServiceEntry = serviceEntry;
originalLiveId = liveId;
loginSession = session;

var assembly = Assembly.GetExecutingAssembly().GetName();
var ua = assembly.Name + "/" + assembly.Version.ToString(3);
Expand Down Expand Up @@ -126,14 +129,10 @@ private async Task CollectChat(CancellationToken cancel)
{
if (!originalLiveId.StartsWith("lv")) // 代替えAPIではコミュニティ・チャンネルにおけるコメント鯖取得ができないのでlvを取得しに行く
{
var getLiveId = await httpClient.GetStreamAsync($"https://live2.nicovideo.jp/unama/tool/v1/broadcasters/social_group/{originalLiveId}/program", cancel).ConfigureAwait(false);
var liveIdJson = await JsonDocument.ParseAsync(getLiveId, cancellationToken: cancel).ConfigureAwait(false);
var liveIdRoot = liveIdJson.RootElement;
if (!liveIdRoot.GetProperty("meta").GetProperty("errorCode").GetString().Equals("OK")) throw new ChatReceivingException("コミュニティ・チャンネルが見つかりませんでした");
originalLiveId = liveIdRoot.GetProperty("data").GetProperty("nicoliveProgramId").GetString(); // lvから始まるLiveIDに置き換え
originalLiveId = await OAuthApiUtils.GetProgramIdFromChAsync(httpClient, cancel, originalLiveId).ConfigureAwait(false);

}
playerStatusStr = await httpClient.GetStreamAsync($"https://live2.nicovideo.jp/unama/watch/{originalLiveId}/programinfo", cancel).ConfigureAwait(false);
liveId = await OAuthApiUtils.GetProgramInfoFromCommunityId(httpClient, cancel, originalLiveId, loginSession.UserId).ConfigureAwait(false);
}
catch (HttpRequestException e)
{
Expand All @@ -147,14 +146,6 @@ private async Task CollectChat(CancellationToken cancel)
throw new ChatReceivingException("サーバーとの通信でエラーが発生しました", e);
}

var playerStatus = await JsonDocument.ParseAsync(playerStatusStr, cancellationToken: cancel).ConfigureAwait(false);
var playerStatusRoot = playerStatus.RootElement;

if (playerStatusRoot.GetProperty("data").GetProperty("rooms").GetArrayLength() <= 0)
throw new ChatReceivingException("コメント取得できませんでした以下の原因が考えられます\n\n・放送されていない\n・視聴権がない\n・コミュニティフォロワー限定番組");

liveId = playerStatusRoot.GetProperty("data").GetProperty("socialGroup").GetProperty("id").GetString();

try
{
await foreach (NiconicoUtils.NiconicoCommentXmlTag tag in commentReceiver.Receive(originalLiveId, cancel))
Expand Down
84 changes: 84 additions & 0 deletions TVTComment/Model/NiconicoUtils/ApiUtils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace TVTComment.Model.NiconicoUtils
{
public static class OAuthApiUtils
{
public class Room
{
public readonly Uri webSocketUri;
public readonly string threadId;
public Room(Uri uri, string id)
{
webSocketUri = uri;
threadId = id;
}
}

private static async Task<JsonElement> GetProgramInfo(HttpClient client, CancellationToken cancellationToken, string programId, string userId,string fields)
{
var stream = await client.GetStreamAsync($"https://api.live2.nicovideo.jp/api/v1/watch/programs?nicoliveProgramId={programId}&userId={userId}&fields={fields}", cancellationToken).ConfigureAwait(false);
var respJson = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken).ConfigureAwait(false);
var watchData = respJson.RootElement.GetProperty("data");
return watchData;
}

public static async Task<string> GetProgramInfoFromCommunityId(HttpClient client, CancellationToken cancellationToken, string programId, string userId)
{
var field = "socialGroup";
var watchData = await GetProgramInfo(client, cancellationToken, programId, userId, field);
var social = watchData.GetProperty(field);
var id = social.GetProperty("socialGroupId").GetString();
return id;
}

public static async Task<long> GetProgramInfoFromVposBaseTime(HttpClient client, CancellationToken cancellationToken, string programId, string userId)
{
var field = "program";
var watchData = await GetProgramInfo(client, cancellationToken, programId, userId, field);
var program = watchData.GetProperty(field);
var vposBaseTime = program.GetProperty("schedule").GetProperty("vposBaseTime").GetString();
var vposBaseTimeUnix = new DateTimeOffset(DateTime.Parse(vposBaseTime)).ToUnixTimeSeconds();
return vposBaseTimeUnix;
}

public static async Task<string> GetProgramIdFromChAsync(HttpClient client, CancellationToken cancellationToken , string chId)
{
var resp = await client.GetStreamAsync($"https://api.live2.nicovideo.jp/api/v1/watch/channels/programs/onair?id={chId}", cancellationToken).ConfigureAwait(false);
var respJson = await JsonDocument.ParseAsync(resp, cancellationToken: cancellationToken).ConfigureAwait(false);
var programId = respJson.RootElement.GetProperty("data").GetProperty("programId").GetString();
return programId;
}

public static async Task<Room> GetRoomFromProgramId(HttpClient client, CancellationToken cancellationToken, string programId, string userId)
{
var stream = await client.GetStreamAsync($"https://api.live2.nicovideo.jp/api/v1/unama/programs/rooms?nicoliveProgramId={programId}&userId={userId}", cancellationToken).ConfigureAwait(false);
var respJson = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken).ConfigureAwait(false);

var roomsData = respJson.RootElement.GetProperty("data")[0]; // 0 なのでアリーナ固定
var webSocketUrl = roomsData.GetProperty("webSocketUri").GetString();
var threadId = roomsData.GetProperty("threadId").GetString();
var webSocketUri = new Uri(webSocketUrl);
var room = new Room(webSocketUri, threadId);
return room;
}

public static async Task<Uri> GetWatchWebSocketUri(HttpClient client, CancellationToken cancellationToken, string programId, string userId)
{
var stream = await client.GetStreamAsync($"https://api.live2.nicovideo.jp/api/v1/wsendpoint?nicoliveProgramId={programId}&userId={userId}", cancellationToken).ConfigureAwait(false);
var respJson = await JsonDocument.ParseAsync(stream, cancellationToken: cancellationToken).ConfigureAwait(false);

var watchData = respJson.RootElement.GetProperty("data");
var url = watchData.GetProperty("url").GetString();
var uri = new Uri(url);
return uri;
}
}
}
29 changes: 8 additions & 21 deletions TVTComment/Model/NiconicoUtils/NicoLiveCommentReceiver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using System.Threading;
using System.Threading.Tasks;
using System.Timers;
using static TVTComment.Model.NiconicoUtils.OAuthApiUtils;

namespace TVTComment.Model.NiconicoUtils
{
Expand Down Expand Up @@ -64,6 +65,7 @@ public NicoLiveCommentReceiver(NiconicoLoginSession niconicoLoginSession)
var assembly = Assembly.GetExecutingAssembly().GetName();
ua = assembly.Name + "/" + assembly.Version.ToString(3);
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("User-Agent", ua);
httpClient.DefaultRequestHeaders.TryAddWithoutValidation("Authorization", $"Bearer {niconicoLoginSession.Token}");
count = Environment.TickCount;
}

Expand All @@ -88,16 +90,14 @@ public async IAsyncEnumerable<NiconicoCommentXmlTag> Receive(string liveId, [Enu
{
var random = new Random();
await Task.Delay((disconnectedCount * 5000) + random.Next(0, 101)); //再試行時に立て続けのリクエストにならないようにする
Stream str;
Room room;
try
{
if (comId != "") { //コミュIDが取得済みであればlvを再取得 24時間放送のコミュニティ・チャンネル用
var getLiveId = await httpClient.GetStreamAsync($"https://live2.nicovideo.jp/unama/tool/v1/broadcasters/social_group/{comId}/program", cancellationToken).ConfigureAwait(false);
var liveIdJson = await JsonDocument.ParseAsync(getLiveId, cancellationToken: cancellationToken).ConfigureAwait(false);
if (!liveIdJson.RootElement.GetProperty("meta").GetProperty("errorCode").GetString().Equals("OK")) throw new InvalidPlayerStatusNicoLiveCommentReceiverException("コミュニティ・チャンネルが見つかりませんでした");
liveId = liveIdJson.RootElement.GetProperty("data").GetProperty("nicoliveProgramId").GetString();
liveId = await OAuthApiUtils.GetProgramIdFromChAsync(httpClient, cancellationToken, comId).ConfigureAwait(false);
}
str = await httpClient.GetStreamAsync($"https://live2.nicovideo.jp/unama/watch/{liveId}/programinfo", cancellationToken).ConfigureAwait(false);
room = await OAuthApiUtils.GetRoomFromProgramId(httpClient, cancellationToken, liveId, NiconicoLoginSession.UserId).ConfigureAwait(false);

}
// httpClient.CancelPendingRequestsが呼ばれた、もしくはタイムアウト
catch (TaskCanceledException e)
Expand All @@ -110,27 +110,14 @@ public async IAsyncEnumerable<NiconicoCommentXmlTag> Receive(string liveId, [Enu
{
throw new NetworkNicoLiveCommentReceiverException(e);
}
var playerStatus = await JsonDocument.ParseAsync(str, cancellationToken: cancellationToken).ConfigureAwait(false);
var playerStatusRoot = playerStatus.RootElement;
comId = playerStatusRoot.GetProperty("data").GetProperty("socialGroup").GetProperty("id").GetString(); //コメント受信ループ内でbreakされたあとにコミュからlvを取得するためにコミュを取得しておく
if (playerStatusRoot.GetProperty("data").GetProperty("rooms").GetArrayLength() <= 0) //roomsが無かったら放送終了扱い
throw new ConnectionDisconnectNicoLiveCommentReceiverException();
var msUriStr = playerStatusRoot.GetProperty("data").GetProperty("rooms")[0].GetProperty("webSocketUri").GetString();
var threadId = playerStatusRoot.GetProperty("data").GetProperty("rooms")[0].GetProperty("threadId").GetString();
if (threadId == null || msUriStr == null)
{
throw new InvalidPlayerStatusNicoLiveCommentReceiverException(str.ToString());
}
var msUri = new Uri(msUriStr);

using var ws = new ClientWebSocket();

ws.Options.SetRequestHeader("User-Agent", ua);
ws.Options.AddSubProtocol("msg.nicovideo.jp#json");

try
{
await ws.ConnectAsync(msUri, cancellationToken);
await ws.ConnectAsync(room.webSocketUri, cancellationToken);
}
catch (Exception e) when (e is ObjectDisposedException || e is WebSocketException || e is IOException)
{
Expand All @@ -146,7 +133,7 @@ public async IAsyncEnumerable<NiconicoCommentXmlTag> Receive(string liveId, [Enu
ws.Dispose();
});

var sendThread = "[{\"ping\":{\"content\":\"rs:0\"}},{\"ping\":{\"content\":\"ps:0\"}},{\"thread\":{\"thread\":\""+ threadId + "\",\"version\":\"20061206\",\"user_id\":\""+ NiconicoLoginSession.UserId + "\",\"res_from\":-10,\"with_global\":1,\"scores\":1,\"nicoru\":0}},{\"ping\":{\"content\":\"pf:0\"}},{\"ping\":{\"content\":\"rf:0\"}}]";
var sendThread = "[{\"ping\":{\"content\":\"rs:0\"}},{\"ping\":{\"content\":\"ps:0\"}},{\"thread\":{\"thread\":\""+ room.threadId + "\",\"version\":\"20061206\",\"user_id\":\""+ NiconicoLoginSession.UserId + "\",\"res_from\":-10,\"with_global\":1,\"scores\":1,\"nicoru\":0}},{\"ping\":{\"content\":\"pf:0\"}},{\"ping\":{\"content\":\"rf:0\"}}]";

try
{
Expand Down
Loading

0 comments on commit 65ba443

Please sign in to comment.