1. 모바일 아프리카TV 접속
2. F12 개발자툴을 통해 분석
3. 웹소켓 주소 확보 및 분석
4. 암호화값 해독
5. JS분석 [http://m.afreecatv.com/assets/app.6646ce82ca46e1bb64f6.bundle.js]
채팅방 접속에 필요한 각종 값 찾기
1. 소켓 주소찾기
모바일 아프리카에서 F12 Network 창에서 아무 방이나 접속하시면 getList라는 부분을 요청하고 받아옵니다.
풀링크는 http://api.m.afreecatv.com/broad/chat/bridge/a/getList 인데요.
이 부분은 사용할 수 있는 소켓의 서버목록을 받아옵니다.
GET으로 요청하며 Json으로 반환을 받으며 이 중에서 랜덤으로 사용하시면 됩니다.
2. BJ정보 가지고오기
모바일 아프리카에서 F12 Network 창에서 아무 방이나 접속하시면 watch라는 부분을 요청하고 받아옵니다.
풀링크는 http://api.m.afreecatv.com/broad/a/watch 인데요.
이 부분은 BJ의 각종 방정보를 반환해줍니다.
POST로 요청하며 Json으로 반환을 해줍니다.
파라미터는 bj_id=비제이아이디&broad_no=비제이고유번호&agent=web 인데요.
BJ아이디만 입력하고 고유번호는 비워놔도 정상적으로 가지고오니 무시하셔도 됩니다.
3. 채팅방 로그인에 필요한 계정 암호화정보 가지고오기
https://login.afreecatv.com/app/LoginAction.php 로 로그인을 요청하면 계정 쿠키를 반환해줍니다.
그 쿠키를 분석해보면 PdboxTicket 라는 부분이 있습니다.
이 부분이 계정의 고유 암호화정보이며 이 부분은 절대 변하지않습니다.
4. 채팅방 암호화 정보 복호화
var n = new Uint8Array(e.data);
t = new TextDecoder("utf-8").decode(n)
위는 아프리카 JS에서의 암호화된 정보 Decode 코드입니다.
위처럼 찾아낸 후 코드를 찾아 만들면됩니다.
먼저 암호화 정보는 12-41-12-62-43-71-28 이런식으로 넘어옵니다.
이 부분은 UInt8 형식이며 C#에서의 uint8은 Byte 입니다.
암호화Hex를 Byte Array화 해준 후 UTF8.GetString으로 문자를 반환받으면됩니다.
코드 작성
먼저 위에서 정보들을 가지고 와야하며, WebSocketSharp을 이용해야합니다.
먼저 nuget 패키지에서 WebSocketSharp 을 설치해줍니다.
그 후 Json.NET 도 설치해줍니다.
namespace ConsoleApplication1
{
class Program
{
static CookieContainer logincookie = new CookieContainer();
static string PdboxTicket = "undefined"; //기본값 undefined 은 비회원
[STAThread]
static void Main(string[] args)
{
AfreecaChat("비제이아이디");
Console.ReadLine();
}
static async void AfreecaChat(string BJID)
{
bool loginstatus = await LoginAfreecaTV("아프리카TV아이디", "아프리카TV비밀번호");
if (loginstatus)
Console.WriteLine("로그인 성공");
string socketURL = await curl_get("http://api.m.afreecatv.com/broad/chat/bridge/a/getList");
var Socketjsondata = JsonConvert.DeserializeObject<JsonParser.Socket.MainSer>(socketURL);
socketURL = Socketjsondata.data.list[new Random().Next(0, Socketjsondata.data.list.Count)];
Console.WriteLine("소켓 아이피\t: " + socketURL);
string bjInfo = await curl_post("http://api.m.afreecatv.com/broad/a/watch", "bj_id=" + BJID + "&broad_no=&agent=web");
var BJInfojsondata = JsonConvert.DeserializeObject<JsonParser.BjDetail.MainSer>(bjInfo);
string allowed_view_cnt = BJInfojsondata.data.allowed_view_cnt.ToString();
string title = BJInfojsondata.data.broad_title;
string user_nick = BJInfojsondata.data.user_nick;
Console.WriteLine("제목\t\t: " + title);
Console.WriteLine("닉네임\t\t: " + user_nick);
Console.WriteLine("최대 시청자\t: " + allowed_view_cnt);
string chat_no = BJInfojsondata.data.chat_no.ToString();
string channel_info = BJInfojsondata.data.channel_info;
string fan_ticket = BJInfojsondata.data.fan_ticket;
string origianl_broad_no = BJInfojsondata.data.origianl_broad_no.ToString();
string country_code = BJInfojsondata.data.country_code;
string lang = BJInfojsondata.data.lang;
string region_code = BJInfojsondata.data.region_code;
string relay_ipport = BJInfojsondata.data.relay_ip + ":" + BJInfojsondata.data.relay_port;
bool issend = false;
using (var ws = new WebSocket("ws://" + socketURL + "/Websocket", "chat"))
{
ws.OnMessage += (sender, e) =>
{
string encData = e.Data;
string decodeData = DecodeAfreecaChat(encData);
//Console.WriteLine("암호화 : " + encData);
Console.WriteLine("받음 : " + decodeData);
/*
* - 받은채팅 명령 목록
LOGIN = 로그인
JOIN = 입장
ALERTMSG = 공지메세지
GETITEMADDR = 아이템
QUITCH = 채널퇴장메세지
CHATMESG = 체팅 메세지
BJNOTICE = BJ공지
DIRECTCHAT = 귓속말
SENDADMINNOTICE = 운영자공지
SETCHNAME = ??
SETBJSTAT = 방송종료
CLOSECH = ??
SETDUMB = 채팅금지메세지
BLINDEXIT = 블라인드 상태에서 탈출시도메세지
KICK = 강제퇴장
KICKCANCEL = 강제퇴장취소
SENDBALLOON = 별풍선메세지
SENDBALLOONSUB = 중계방별풍선메세지
VODBALLOON = VOD별풍선메세지
VIDEOBALLOON = 중계방 비디오풍선메세지
ICEMODE = 채팅 얼리기 메세지
SENDFANLETTER = 스티커메세지
SENDFANLETTERSUB = 중계방스티커메세지
CHOCOLATE = 초콜렛메세지
CHOCOLATESUB = 중계방초콜렛메세지
SENDQUICKVIEW = 퀵뷰선물메세지
SETCHINFO = ??
GETUSERCNT = 유저 총 수
GETUSERCNTEX = ??
CHATERROR = 채팅오류
GETCHINFO = ??
MOBBROADPAUSE = ??
MULTIBROAD = ??
*/
if (decodeData.Contains("LOGIN"))
{
ws.Send("JOIN" + chat_no + ":" + fan_ticket + ":: & geo_cc = " + country_code + " & geo_rc = " + region_code + " & acpt_lang = " + lang + "& svc_lang = ko-KR");
ws.Send("CONNECTCENTER" + relay_ipport + ":" + origianl_broad_no + ":undefined: & geo_cc = " + country_code + " & geo_rc = " + region_code + " & acpt_lang = " + lang + " & svc_lang = ko-KR");
ws.Send("GETITEMADDR");
}
//ws.Send("CHAT보낼채팅내용");
};
ws.Connect();
//mweb_ios = IOS
//mweb_aos = ANDROID
ws.Send("LOGIN" + channel_info + ":" + PdboxTicket + ":0:mweb_aos"); //로그인
Console.ReadKey(true);
}
}
static async Task<bool> LoginAfreecaTV(string id, string pw)
{
bool result = false;
CookieContainer tempcookie = new CookieContainer();
byte[] data = Encoding.ASCII.GetBytes("szWork=login&szType=json&szUid=" + id + "&szPassword=" + pw + "&isSaveId=false&isSavePw=false&isSaveJoin=false");
HttpWebRequest postreq = (HttpWebRequest)HttpWebRequest.Create("https://login.afreecatv.com/app/LoginAction.php");
postreq.Method = "POST";
postreq.UserAgent = "Mozilla/5.0 (Linux; Android 5.0; Nexus 5 Build/WHALE) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Whale/0.10.36.20 Mobile Safari/537.36)";
postreq.ContentType = "application/x-www-form-urlencoded;charset=UTF-8";
postreq.CookieContainer = tempcookie;
postreq.ContentLength = data.Length;
Stream requestStream = await postreq.GetRequestStreamAsync();
requestStream.Write(data, 0, data.Length);
requestStream.Close();
HttpWebResponse webResponse = (HttpWebResponse)await postreq.GetResponseAsync();
if (webResponse.StatusCode == HttpStatusCode.OK)
{
tempcookie.Add(webResponse.Cookies);
logincookie = tempcookie;
try
{
PdboxTicket = logincookie.GetCookies(new Uri("https://afreecatv.com"))["PdboxTicket"].Value.ToString();
}
catch { }
Stream responseStream = webResponse.GetResponseStream();
StreamReader streamReader = new StreamReader(responseStream, Encoding.UTF8);
string strResult = await streamReader.ReadToEndAsync();
streamReader.Close();
responseStream.Close();
webResponse.Close();
if (strResult.Contains("RESULT\":1"))
result = true;
}
return result;
}
static async Task<string> curl_get(string url)
{
string strResult = "";
HttpWebRequest getreq = (HttpWebRequest)HttpWebRequest.Create(url);
getreq.Method = "GET";
getreq.UserAgent = "Mozilla/5.0 (Linux; Android 5.0; Nexus 5 Build/WHALE) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Whale/0.10.36.20 Mobile Safari/537.36";
getreq.ContentType = "application/x-www-form-urlencoded; charset=UTF-8";
HttpWebResponse webResponse = (HttpWebResponse)await getreq.GetResponseAsync();
if (webResponse.StatusCode == HttpStatusCode.OK)
{
Stream responseStream = webResponse.GetResponseStream();
StreamReader streamReader = new StreamReader(responseStream, Encoding.Default);
strResult = await streamReader.ReadToEndAsync();
streamReader.Close();
responseStream.Close();
webResponse.Close();
}
return strResult;
}
static async Task<string> curl_post(string url, string postdata)
{
string strResult = "";
byte[] data = Encoding.ASCII.GetBytes(postdata);
HttpWebRequest postreq = (HttpWebRequest)HttpWebRequest.Create(url);
postreq.Method = "POST";
postreq.UserAgent = "Mozilla/5.0 (Linux; Android 5.0; Nexus 5 Build/WHALE) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.113 Whale/0.10.36.20 Mobile Safari/537.36)";
postreq.ContentType = "application/x-www-form-urlencoded";
postreq.ContentLength = data.Length;
Stream requestStream = await postreq.GetRequestStreamAsync();
requestStream.Write(data, 0, data.Length);
requestStream.Close();
HttpWebResponse webResponse = (HttpWebResponse)await postreq.GetResponseAsync();
if (webResponse.StatusCode == HttpStatusCode.OK)
{
Stream responseStream = webResponse.GetResponseStream();
StreamReader streamReader = new StreamReader(responseStream, Encoding.UTF8);
strResult = await streamReader.ReadToEndAsync();
streamReader.Close();
responseStream.Close();
webResponse.Close();
}
return strResult;
}
static string DecodeAfreecaChat(string str)
{
str = str.Replace("-", string.Empty);
byte[] buffer = HexToByte(str);
return Encoding.UTF8.GetString(buffer);
}
static byte[] HexToByte(string hexString)
{
byte[] returnBytes = new byte[hexString.Length / 2];
for (int i = 0; i < returnBytes.Length; i++)
{
returnBytes[i] = Convert.ToByte(hexString.Substring(i * 2, 2), 16);
}
return returnBytes;
}
}
}
'자료' 카테고리의 다른 글
[VB.NET/C#] 아프리카 TV 라이브 방송링크 받아오기 (0) | 2020.09.03 |
---|---|
[VB.net/C#] 아프리카티비 여러개 시청 (0) | 2020.09.03 |
실제 키보드 입력과 가상 키보드 이벤트를 구분하는법 (0) | 2020.09.03 |
[C#/VB.NET] Dictionary Examples (0) | 2020.08.31 |
VM웨어 가상윈도우 사용시 설정 팁 (0) | 2020.08.31 |