C#からPixivSketchLiveの配信枠を取る
概要とか
Pixiv(が提供しているSKETCH)の配信サービス PixivSketchLive にて、配信枠を取るだけのツールを作りたかったので いろいろ探った時とかの覚書。
そもそもAPI自体非公開だろうし、仕様がホイホイ変わるかもしれないので その辺りはご容赦
公式クライアントからの挙動メモ
ライブ配信の項目をクリックすると、入力フォームの表示と共に複数データが得られる
https://sketch.pixiv.net/api/lives/availability.json
{
"data":
{
"availability":true,
"closed_live_availability":true
},
"errors":[],
"_links":{},
"rand":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // 32文字の16進数ランダム文字列
}
よくわからんかった (゚ω゚)
https://sketch.pixiv.net/api/rewards/contract.json
(404)
こちらは多分リワード受け取りに関する情報だと思う。
自分は受け取り利用規約の同意を見送っているので、単純に蹴られているだけかも?
既に配信中の場合は
https://sketch.pixiv.net/api/lives/mine.json
ここに繋ぐとJSONが返る。その中身次第で、自身が既に配信中であるかどうか確認できる
配信中で無いのなら、それっぽい中身にならない(雑な表現)
{
"data":{}, // ← 配信中であれば、ここに沢山情報が入るっぽい
"errors":[],
"_links":{},
"rand":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // 32文字の16進数ランダム文字列
}
枠を取りたい場合、https://sketch.pixiv.net/api/lives.json
へ諸々をPOSTすれば良いと判断
投げつけるフォームデータの一例
name: 配信タイトル (20字)
description: 配信説明 (300字)
is_single: false
publicity: closed
adult_level: normal
source: web
enable_gifting: false
thumbnail : サムネデータ(C#コード中で後述)
配信枠のタイトルおよび配信説明文は クライアント側で文字数制限があるので、サーバーでもバリデート掛けられてると思う。試してないけど。
Request Headers で必要そうなもの
cookie の PHPSESSID
は言わずもがな、
accept: application/vnd.sketch-v4+json
origin: https://sketch.pixiv.net
referer: https://sketch.pixiv.net/
多分この3つは設定必須
本系Webページのクライアントjsにおけるサムネ画像管理箇所は?
それっぽいな?と思った箇所の引用メモ
Live.js
async updateLive({ context, payload }) {
const nextLive = payload.getIn(['entity', 'live']).toJS();
const body = new FormData();
nextLive.name && body.append('name', nextLive.name);
body.append('description', nextLive.description || '');
nextLive.adult_level && body.append('adult_level', nextLive.adult_level);
nextLive.thumbnail.file && body.append('thumbnail', nextLive.thumbnail.file);
await Promise.all([
context.fetch(`/api/lives/${nextLive.id}.json`, { method: 'PUT', body }),
...(nextLive.remove_thumbnail
? [context.fetch(`/api/lives/${nextLive.id}/thumbnail.json`, { method: 'DELETE' })]
: []),
]);
},
client.xxxxxx.js
updateLive: function(e) {
var n = e.context
, t = e.payload;
return w(regeneratorRuntime.mark(function e() {
var a, r;
return regeneratorRuntime.wrap(function(e) {
for (; ; )
switch (e.prev = e.next) {
case 0:
return a = t.getIn(["entity", "live"]).toJS(),
r = new FormData,
a.name && r.append("name", a.name),
r.append("description", a.description || ""),
a.adult_level && r.append("adult_level", a.adult_level),
a.thumbnail.file && r.append("thumbnail", a.thumbnail.file),
e.next = 8,
Promise.all([n.fetch("/api/lives/".concat(a.id, ".json"), {
method: "PUT",
body: r
})].concat(h(a.remove_thumbnail ? [n.fetch("/api/lives/".concat(a.id, "/thumbnail.json"), {
method: "DELETE"
})] : [])));
case 8:
case "end":
return e.stop()
}
}, e)
}))()
},
配信の終了
https://sketch.pixiv.net/api/lives/[チャンネルID].json
的な、チャンネルを対象にして DELETE メソッドで投げるっぽい?
自分は枠さえ取れればよかったので、ここは未検証
レスポンスは、
{
"data":{},
"errors":[],
"_links":{},
"rand":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" // 32文字の16進数ランダム文字列
}
のような JSONだった
C#から枠を取る
簡単な流れ
- Cookie から "PHPSESSID" を持ってきてセット
- POST 用に、複数のパラメタも用意しておく
- 必要ならサムネイル(画像)データを別途付与
- RequestHeader は多分3つ必要
accept : application/vnd.sketch-v4+json
Origin : https://sketch.pixiv.net
referer : https://sketch.pixiv.net/
- これらを投げつけると JSON が返ってくる
- 後は中身を見て、配信の成否判断とか、取れた場合の枠情報等をよしなに調理する
準備
記事の本質とはズレるので、Cookie読み取り処理等は省いてます
// サムネイルデータは別途用意してね
System.Drawing.Image Thumbnail = null;
// Cookieから持ってきてね
var _cookieSessionId = "1234567_xxxxxxxxxxxxxxxxxxxxxxxx";
MultipartFormDataContent form = new MultipartFormDataContent();
// cookieを使う為に前もって用意
var clientHandler = new HttpClientHandler();
clientHandler.UseCookies = true;
// アクセス時に引用するcookie情報を作成
var cookieContainer = new CookieContainer();
cookieContainer.Add(new Uri("https://sketch.pixiv.net"), new Cookie("PHPSESSID", _cookieSessionId));
// cookie を HttpClientHandler に仕込む
clientHandler.CookieContainer = cookieContainer;
// 仕込み済みの HttpClientHandler を引数に、HttpClient インスタンスを作成。あとヘッダ付与
var client = new HttpClient(clientHandler);
client.DefaultRequestHeaders.Add("accept", "application/vnd.sketch-v4+json");
client.DefaultRequestHeaders.Add("Origin", "https://sketch.pixiv.net");
client.DefaultRequestHeaders.Add("referer", "https://sketch.pixiv.net/");
// 構築
form.Add(new StringContent(Title), "name");
form.Add(new StringContent(Description), "description");
form.Add(new StringContent(Single.ToString()), "is_single");
form.Add(new StringContent(PublicityType), "publicity");
form.Add(new StringContent(AdultLevelType), "adult_level");
form.Add(new StringContent(SourceType), "source");
form.Add(new StringContent(Gifting.ToString()), "enable_gifting");
// サムネあれば開きつつ、バイト配列に変換
if (Thumbnail != null)
{
var img = Thumbnail;
var _converter = new System.Drawing.ImageConverter();
byte[] file_bytes = (byte[])_converter.ConvertTo(img, typeof(byte[]));
// ここのファイル名、何でも良いのかな?
form.Add(new ByteArrayContent(file_bytes, 0, file_bytes.Length), "thumbnail", "image.jpg");
}
// 接続・結果読み取り
var resAsyncTaskResult = client.PostAsync(SketchApiUrl, form).Result;
var resBodyTaskresult = resAsyncTaskResult.Content.ReadAsStringAsync().Result;
JSON応答を解析
Newtonsoft.Json ライブラリ使ってます。
jsに比べれば面倒だけど、C#でJSON弄るのはなんかもうコレ!って感じに自分はなっちゃってます。
上に記したコードの resBodyTaskresult
をそのままJSONであるものとしてパースをかけつつ諸々を判断していきます。
- JSONが返ってくる前提として進めて良さそう
"errors"
キーが返ってくるけど、エラーが無い場合でもこのキーは存在する模様- 不備があればJSONの
"errors"
に含まれる配列内に"message"
キーとその値にエラー内容が(英文で)入るっぽい - 不備が無ければ
"errors"
の配列は空[]
になるのかな?
- 不備があればJSONの
- 正常に枠が取れれば、JSONは それっぽい中身のオブジェクトになる
"data.live.id"
に配信番組のID"live.owner.user.unique_name"
に、配信オーナー(実質あなた)のユニーク名が入る- これらを汎用URLとして繋げれば、取ったばかりの配信枠URLが得られる
...という訳で、
↓ 以下は自分が使ってたコードの一部を雑に抜いただけなんですけど、雰囲気だけ伝われば...
try
{
// 例外前提でパース。data を最初にとって、
// エラー用メッセージ data.errors.message
// 次に data.live.id
var _resJson = JObject.Parse(resBodyTaskresult);
JToken _data = new JObject();
if (_resJson.TryGetValue("data", out _data) == false)
{
// "応答したJSONの data パース自体に失敗";
return false;
}
JToken _JT_Errors = _resJson.SelectToken("errors");
// サーバーからのエラーメッセージは、正常処理でも空のオブジェクトとして存在している。
// 大抵のケースでは「既に配信中である」旨
if (_JT_Errors != null && _JT_Errors.Type == JTokenType.Array)
{
// {"data":{},"errors":[{"message":"you cannot create open room","code":null}],"rand":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"}
var _ErrArray = (JArray)_JT_Errors;
string _rawErrMsg = String.Empty;
if (_ErrArray != null && _ErrArray.Count > 0)
{
foreach (var aa in _ErrArray)
{
var _emsg = aa.SelectToken("message");
if (_emsg != null)
{
_rawErrMsg += "\n" + (string)((JValue)_emsg).Value;
}
}
// "サーバーからのエラーメッセージ" + _rawErrMsg;
return false;
}
}
JToken _JT_ChannnelId = _data.SelectToken("live.id");
JToken _JT_OwnerUniqueName = _data.SelectToken("live.owner.user.unique_name");
// 配信IDかユニークネームの取得に失敗
if (_JT_ChannnelId == null)
{
// "応答したJSONの 配信ID パースに失敗";
return false;
}
else if (_JT_OwnerUniqueName == null)
{
// "応答したJSONの owner unique_name パースに失敗";
return false;
}
// 本来ならここに到達できているはず。
string _channelID = (string)((JValue)_JT_ChannnelId).Value;
string _ownerUniqueName = (string)((JValue)_JT_OwnerUniqueName).Value;
return true;
}
catch (Newtonsoft.Json.JsonReaderException exc)
{
// JObject.Parse に失敗した時
// "応答したJSONのパースに失敗\n" + exc.Message;
}
catch (Exception exc)
{
// その他例外あれば。
}