├── HttpWorker.cs ├── README.md └── worker_scene.unitypackage /HttpWorker.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System; 5 | using System.Net.Http; 6 | using Newtonsoft.Json; 7 | 8 | public class HttpWorker 9 | { 10 | #region Define 11 | 12 | [System.Serializable] 13 | public struct HttpWorkerResult 14 | { 15 | public string Text; 16 | public string NickName; 17 | public DateTime TimeLine; 18 | public int RND; 19 | } 20 | 21 | public class LimitedQueue : Queue 22 | { 23 | public int Limit { get; set; } 24 | 25 | public LimitedQueue(int limit) : base(limit) 26 | { 27 | Limit = limit; 28 | } 29 | 30 | public new void Enqueue(T item) 31 | { 32 | while (Count >= Limit) 33 | { 34 | Dequeue(); 35 | } 36 | base.Enqueue(item); 37 | } 38 | 39 | public bool IsFull 40 | { 41 | get 42 | { 43 | return Count == Limit; 44 | } 45 | } 46 | } 47 | 48 | #endregion 49 | 50 | private int m_httpMaxWorkCount = 128; 51 | private int m_httpMaxResultCount = 256; 52 | private LimitedQueue m_httpWorkerResults; 53 | 54 | private int m_roomID; 55 | private bool m_working; 56 | 57 | public void StartWorking(int roomID) 58 | { 59 | if (roomID <= 0) 60 | { 61 | return; 62 | } 63 | 64 | m_httpWorkDeses = new Queue(m_httpMaxWorkCount); 65 | m_stringWorkDeses = new Queue(m_httpMaxWorkCount); 66 | m_httpWorkerResults = new LimitedQueue(m_httpMaxResultCount); 67 | 68 | 69 | m_httpThread = new Thread(new ThreadStart(HttpWorkerThreadEntry)); 70 | m_httpThread.Start(); 71 | 72 | m_stringThread = new Thread(new ThreadStart(StringWorkerThreadEntry)); 73 | m_stringThread.Start(); 74 | 75 | m_roomID = roomID; 76 | m_working = true; 77 | } 78 | 79 | public void StopWorking() 80 | { 81 | if (m_httpThread != null) 82 | { 83 | m_httpThread.Abort(); 84 | m_httpThread = null; 85 | } 86 | if (m_stringThread != null) 87 | { 88 | m_stringThread.Abort(); 89 | m_stringThread = null; 90 | } 91 | m_httpWorkDeses = null; 92 | m_stringWorkDeses = null; 93 | m_httpWorkerResults = null; 94 | 95 | m_working = false; 96 | } 97 | 98 | public void GiveOneWork() 99 | { 100 | if (!m_working) 101 | { 102 | return; 103 | } 104 | 105 | HttpWorkDes work = new HttpWorkDes(); 106 | Dictionary asks = new Dictionary() { 107 | { "roomid", m_roomID.ToString() }, 108 | { "csrf_token", "" }, 109 | { "csrf", "" }, 110 | { "visit_id", "" }, 111 | }; 112 | work.RequestContent = new FormUrlEncodedContent(asks); 113 | 114 | lock (m_httpWorkDeses) 115 | { 116 | m_httpWorkDeses.Enqueue(work); 117 | } 118 | } 119 | 120 | public void EnqueueFakeResult(HttpWorkerResult result) 121 | { 122 | if (m_httpWorkerResults == null) 123 | { 124 | m_httpWorkerResults = new LimitedQueue(m_httpMaxResultCount); 125 | } 126 | 127 | m_httpWorkerResults.Enqueue(result); 128 | } 129 | 130 | public HttpWorkerResult[] ConsumeAll() 131 | { 132 | if (m_httpWorkerResults == null) 133 | { 134 | return null; 135 | } 136 | 137 | lock (m_httpWorkerResults) 138 | { 139 | HttpWorkerResult[] output = new HttpWorkerResult[m_httpWorkerResults.Count]; 140 | //foreach (var result in m_httpWorkerResults) 141 | //{ 142 | // print($"{result.NickName} at {result.TimeLine}: {result.Text}"); 143 | //} 144 | int counter = 0; 145 | foreach (var result in m_httpWorkerResults) 146 | { 147 | output[counter++] = result; 148 | } 149 | m_httpWorkerResults.Clear(); 150 | return output; 151 | } 152 | } 153 | 154 | #region Http Worker Thread 155 | 156 | private Thread m_httpThread; 157 | 158 | private const int c_HttpWorkerThreadSleepTime = 500; 159 | private struct HttpWorkDes 160 | { 161 | public FormUrlEncodedContent RequestContent; 162 | } 163 | 164 | private Queue m_httpWorkDeses; 165 | 166 | private void HttpWorkerThreadEntry() 167 | { 168 | HttpClient client = new HttpClient(); 169 | List works = new List(); 170 | List results = new List(); 171 | 172 | while (true) 173 | { 174 | // See if there is any job 175 | lock (m_httpWorkDeses) 176 | { 177 | foreach (var work in m_httpWorkDeses) 178 | { 179 | works.Add(work); 180 | } 181 | m_httpWorkDeses.Clear(); 182 | } 183 | 184 | if (works.Count == 0) 185 | { 186 | Thread.Sleep(c_HttpWorkerThreadSleepTime); 187 | } 188 | else 189 | { 190 | foreach (var work in works) 191 | { 192 | HttpResponseMessage httpMsg = client.PostAsync("https://api.live.bilibili.com/xlive/web-room/v1/dM/gethistory", work.RequestContent).Result; 193 | StringWorkDes result = new StringWorkDes(); 194 | result.HttpResponse = httpMsg; 195 | results.Add(result); 196 | } 197 | works.Clear(); 198 | 199 | lock (m_stringWorkDeses) 200 | { 201 | foreach (var result in results) 202 | { 203 | m_stringWorkDeses.Enqueue(result); 204 | } 205 | } 206 | results.Clear(); 207 | } 208 | } 209 | } 210 | 211 | #endregion 212 | 213 | #region String Analysis 214 | 215 | private Thread m_stringThread; 216 | 217 | private const int c_StringWorkerThreadSleepTime = 500; 218 | 219 | private HashSet m_proccessRndIDs = new HashSet(); 220 | private HashSet m_cacheRndTb = new HashSet(); 221 | 222 | private struct CustomRndStruct 223 | { 224 | public string Name; 225 | public string SendTime; 226 | } 227 | 228 | private struct StringWorkDes 229 | { 230 | public HttpResponseMessage HttpResponse; 231 | } 232 | 233 | [System.Serializable] 234 | private struct ResponseMessageJsonOneMsg 235 | { 236 | public string text; 237 | public int uid; 238 | public string nickname; 239 | public string timeline; 240 | public string rnd; 241 | } 242 | 243 | [System.Serializable] 244 | private struct ResponseMessageJsonData 245 | { 246 | public List admin; 247 | public List room; 248 | } 249 | 250 | [System.Serializable] 251 | private struct ResponseMessageJson 252 | { 253 | public int code; 254 | public ResponseMessageJsonData data; 255 | } 256 | 257 | private Queue m_stringWorkDeses; 258 | private DateTime m_lastResultDateTime; 259 | 260 | 261 | private void StringWorkerThreadEntry() 262 | { 263 | List works = new List(); 264 | List results = new List(); 265 | 266 | // Init matchers 267 | // Work out some string matches here. 268 | 269 | while (true) 270 | { 271 | // See if there is any job 272 | lock (m_stringWorkDeses) 273 | { 274 | foreach (var work in m_stringWorkDeses) 275 | { 276 | works.Add(work); 277 | } 278 | m_stringWorkDeses.Clear(); 279 | } 280 | 281 | if (works.Count == 0) 282 | { 283 | Thread.Sleep(c_StringWorkerThreadSleepTime); 284 | } 285 | else 286 | { 287 | Dictionary RNDOuter = new Dictionary(); 288 | foreach (var work in works) 289 | { 290 | string message = work.HttpResponse.Content.ReadAsStringAsync().Result; 291 | ResponseMessageJson finalJson; 292 | try 293 | { 294 | finalJson = JsonConvert.DeserializeObject(message); 295 | } 296 | catch (Exception ex) 297 | { 298 | continue; 299 | } 300 | 301 | DateTime lastestTimeInMsgBoxes = new DateTime(); 302 | List[] msgBoxes = new List[] { finalJson.data.admin, finalJson.data.room }; 303 | int msgBoxesLength = msgBoxes.Length; 304 | for (int x = 0; x < msgBoxesLength; x++) 305 | { 306 | List msgBox = msgBoxes[x]; 307 | if (msgBox != null) 308 | { 309 | foreach (var data in msgBox) 310 | { 311 | CustomRndStruct rndStruct; 312 | rndStruct.Name = data.nickname; 313 | rndStruct.SendTime = data.timeline; 314 | int customRND = rndStruct.GetHashCode(); 315 | 316 | m_cacheRndTb.Add(customRND); 317 | if (m_proccessRndIDs.Contains(customRND)) 318 | { 319 | // Already processed 320 | continue; 321 | } 322 | 323 | HttpWorkerResult result = new HttpWorkerResult(); 324 | 325 | bool success = DateTime.TryParse(data.timeline, out result.TimeLine); 326 | if (!success) 327 | { 328 | //Debug.LogWarning($"Time is invalid! {data.timeline} {message}"); 329 | continue; 330 | } 331 | 332 | result.Text = data.text; 333 | result.NickName = data.nickname; 334 | results.Add(result); 335 | } 336 | } 337 | } 338 | 339 | var tmp = m_cacheRndTb; 340 | m_cacheRndTb = m_proccessRndIDs; 341 | m_proccessRndIDs = tmp; 342 | m_cacheRndTb.Clear(); 343 | } 344 | works.Clear(); 345 | 346 | lock (m_httpWorkerResults) 347 | { 348 | foreach (var result in results) 349 | { 350 | if (m_httpWorkerResults.IsFull) 351 | { 352 | //Debug.LogWarning("Result is dequing! Consider consume more frequently!"); 353 | } 354 | //print($"{result.NickName} {result.Text} {m_httpWorkerResults.Count} {m_httpWorkerResults.Limit}"); 355 | m_httpWorkerResults.Enqueue(result); 356 | } 357 | } 358 | results.Clear(); 359 | } 360 | } 361 | } 362 | 363 | #endregion 364 | } 365 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HttpWorker 2 | 拉取B站弹幕的C#脚本,以及一个集成的UnityPackage 3 | 4 | HttpWorker是一个典型的生产者-消费者模型,需要由使用者每隔一段时间分配任务HttpWorker.GiveOneWork和拉取任务HttpWorker.ConsumeAll 5 | 6 | 直接使用UnityPackage的话,可能需要拉入一个NewtonSoft的Dll,然后直接开始游戏就可以啦 -------------------------------------------------------------------------------- /worker_scene.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DEATHART101/HttpWorker/3fb12b04662ffeeeff6eb37189f9fef9de1e33b7/worker_scene.unitypackage --------------------------------------------------------------------------------