private Dictionary <string, string> BuildQueryParamsUploadDoc(Guid projectId, UploadFileType uploadFileType, string filePath, Guid?folderId = null, string nameDocument = null, Guid?documentId = null, UploadTarget target = UploadTarget.Document, UploadAction action = UploadAction.Add) { Dictionary <string, string> queryParams = new Dictionary <string, string> { { "file", uploadFileType.ToString().ToLowerInvariant() }, { "action", action.ToString().ToLowerInvariant() }, { "target", target.ToString().ToLowerInvariant() }, { "projectid", projectId.ToString().ToLowerInvariant() } }; if (target == UploadTarget.Document && action == UploadAction.Add) { if (String.IsNullOrEmpty(nameDocument) && !string.IsNullOrEmpty(filePath)) { nameDocument = Path.GetFileNameWithoutExtension(filePath); } } if (!String.IsNullOrEmpty(nameDocument)) { queryParams.Add("name", nameDocument); } if (folderId.HasValue) { queryParams.Add("folderid", folderId.Value.ToString()); } if (documentId.HasValue) { queryParams.Add("parentdocid", documentId.Value.ToString()); } return(queryParams); }
Settings( string azMonWorkspaceId, string azMonWorkspaceKey, string azMonLogType, string endpoints, int scrapeFrequencySecs, UploadTarget uploadTarget) { this.AzMonWorkspaceId = Preconditions.CheckNonWhiteSpace(azMonWorkspaceId, nameof(azMonWorkspaceId)); this.AzMonWorkspaceKey = Preconditions.CheckNonWhiteSpace(azMonWorkspaceKey, nameof(azMonWorkspaceKey)); this.AzMonLogType = Preconditions.CheckNonWhiteSpace(azMonLogType, nameof(azMonLogType)); this.Endpoints = new List <string>(); foreach (string endpoint in endpoints.Split(",")) { if (!string.IsNullOrWhiteSpace(endpoint)) { this.Endpoints.Add(endpoint); } } if (this.Endpoints.Count == 0) { throw new ArgumentException("No endpoints specified to scrape metrics"); } this.ScrapeFrequencySecs = Preconditions.CheckRange(scrapeFrequencySecs, 1); this.UploadTarget = uploadTarget; }
private async Task <Document> UploadDocumentOrVersion(Guid projectId, UploadFileType uploadFileType, Stream stream, string contentType, Guid?folderId = null, string nameDocument = null, Guid?documentId = null, UploadTarget target = UploadTarget.Document, UploadAction action = UploadAction.Add) { Dictionary <string, string> queryParams = BuildQueryParamsUploadDoc(projectId, uploadFileType, string.Empty, folderId, nameDocument, documentId, target, action); string docJson = (await Requester.Request(Requester.ApiRootUrl + "uploaddocument", ApiMethod.Post, queryParams, stream, contentType)).Data; return(JsonConvert.DeserializeObject <Document>(docJson)); }
Settings( string logAnalyticsWorkspaceId, string logAnalyticsWorkspaceKey, string logAnalyticsLogType, string endpoints, int scrapeFrequencySecs, UploadTarget uploadTarget, string messageIdentifier) { if (uploadTarget == UploadTarget.AzureLogAnalytics) { this.LogAnalyticsWorkspaceId = Preconditions.CheckNonWhiteSpace(logAnalyticsWorkspaceId, nameof(logAnalyticsWorkspaceId)); this.LogAnalyticsWorkspaceKey = Preconditions.CheckNonWhiteSpace(logAnalyticsWorkspaceKey, nameof(logAnalyticsWorkspaceKey)); this.LogAnalyticsLogType = Preconditions.CheckNonWhiteSpace(logAnalyticsLogType, nameof(logAnalyticsLogType)); } else { this.MessageIdentifier = Preconditions.CheckNonWhiteSpace(messageIdentifier, nameof(messageIdentifier)); } this.Endpoints = new List <string>(); foreach (string endpoint in endpoints.Split(",")) { if (!string.IsNullOrWhiteSpace(endpoint)) { this.Endpoints.Add(endpoint); } } if (this.Endpoints.Count == 0) { throw new ArgumentException("No endpoints specified for which to scrape metrics"); } this.ScrapeFrequencySecs = Preconditions.CheckRange(scrapeFrequencySecs, 1); this.UploadTarget = uploadTarget; }
public abstract Task <IVoiceMessage> UploadVoiceAsync(UploadTarget type, string voicePath, CancellationToken token = default);
public abstract Task <IVoiceMessage> UploadVoiceAsync(UploadTarget type, Stream voice, CancellationToken token = default);
/// <summary> /// 内部使用 /// </summary> /// <exception cref="InvalidOperationException"/> /// <param name="type">目标类型</param> /// <param name="imgStream">图片流</param> /// <remarks> /// 当 mirai-api-http 的版本小于等于v1.7.0时, 本方法返回的将是一个只有 Url 有值的 <see cref="ImageMessage"/> /// <para/> /// <paramref name="imgStream"/> 会被读取至末尾 /// </remarks> /// <returns>一个 <see cref="ImageMessage"/> 实例, 可用于以后的消息发送</returns> private static async Task <ImageMessage> InternalUploadPictureAsync(InternalSessionInfo session, UploadTarget type, Stream imgStream) { if (session.ApiVersion <= new Version(1, 7, 0)) { Guid guid = Guid.NewGuid(); MemoryStream ms = new MemoryStream(8192); // 无论如何都做一份copy await imgStream.CopyToAsync(ms); ImageHttpListener.RegisterImage(guid, ms); return(new ImageMessage(null, $"http://127.0.0.1:{ImageHttpListener.Port}/fetch?guid={guid:n}", null)); } Stream?internalStream = null; bool internalCreated = false; long pervious = 0; if (!imgStream.CanSeek || imgStream.CanTimeout) // 对于 CanTimeOut 的 imgStream, 或者无法Seek的, 一律假定其读取行为是阻塞的 // 为其创建一个内部缓存先行异步读取 { internalStream = new MemoryStream(8192); internalCreated = true; await imgStream.CopyToAsync(internalStream); } else // 否则不创建副本, 避免多余的堆分配 { internalStream = imgStream; pervious = imgStream.Position; } HttpContent sessionKeyContent = new StringContent(session.SessionKey); sessionKeyContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "sessionKey" }; HttpContent typeContent = new StringContent(type.ToString().ToLower()); typeContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "type" }; string format; using (Image img = Image.FromStream(internalStream)) // 已经把数据读到非托管内存里边了, 就不用管input的死活了 { format = img.RawFormat.ToString(); switch (format) { case nameof(ImageFormat.Jpeg): case nameof(ImageFormat.Png): case nameof(ImageFormat.Gif): { format = format.ToLower(); break; } default: // 不是以上三种类型的图片就强转为Png { if (!internalCreated) { internalStream = new MemoryStream(8192); internalCreated = true; } else { internalStream.Seek(0, SeekOrigin.Begin); } img.Save(internalStream, ImageFormat.Png); format = "png"; break; } } } if (internalCreated) { internalStream.Seek(0, SeekOrigin.Begin); } else // internalStream == imgStream { internalStream.Seek(pervious - internalStream.Position, SeekOrigin.Current); } HttpContent imageContent = new StreamContent(internalStream); imageContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "img", FileName = $"{Guid.NewGuid():n}.{format}" }; imageContent.Headers.ContentType = new MediaTypeHeaderValue("image/" + format); HttpContent[] contents = new HttpContent[] { sessionKeyContent, typeContent, imageContent }; return(await session.Client.PostAsync($"{session.Options.BaseUrl}/uploadImage", contents, session.Token) .AsApiRespAsync <ImageMessage>(session.Token) .ContinueWith(t => t.IsFaulted && t.Exception !.InnerException is JsonException ? throw new NotSupportedException("当前版本的mirai-api-http无法发送图片。") : t, TaskContinuationOptions.ExecuteSynchronously).Unwrap()); // ^-- 处理 JsonException 到 NotSupportedException, https://github.com/mamoe/mirai-api-http/issues/85 // internalStream 是 MemoryStream, 内部为全托管字段不需要 Dispose }
/// <summary> /// 内部使用 /// </summary> private async Task <ISharedImageMessage> InternalUploadPictureAsync(InternalSessionInfo session, UploadTarget type, Stream imgStream, bool disposeStream, CancellationToken token = default) { // 使用 disposeStream 目的是不使上层 caller 创建多余的状态机 if (session.ApiVersion <= new Version(1, 7, 0)) { Guid guid = Guid.NewGuid(); MemoryStream ms = new MemoryStream(8192); // 无论如何都做一份copy await imgStream.CopyToAsync(ms, 81920, token).ConfigureAwait(false); ImageHttpListener.RegisterImage(guid, ms); return(new ImageMessage(null, $"http://127.0.0.1:{ImageHttpListener.Port}/fetch?guid={guid:n}", null)); } Stream?internalStream = null; bool internalCreated = false; long pervious = 0; if (!imgStream.CanSeek || imgStream.CanTimeout) // 对于 CanTimeOut 的 imgStream, 或者无法Seek的, 一律假定其读取行为是阻塞的 // 为其创建一个内部缓存先行异步读取 { internalStream = new MemoryStream(8192); internalCreated = true; await imgStream.CopyToAsync(internalStream, 81920, token); internalStream.Seek(0, SeekOrigin.Begin); } else // 否则不创建副本, 避免多余的堆分配 { internalStream = imgStream; pervious = imgStream.Position; } HttpContent sessionKeyContent = new StringContent(session.SessionKey); sessionKeyContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "sessionKey" }; HttpContent typeContent = new StringContent(type.ToString().ToLower()); typeContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "type" }; string format; using SKManagedStream skstream = new SKManagedStream(internalStream, false); using (SKCodec codec = SKCodec.Create(skstream)) // 已经把数据读到非托管内存里边了, 就不用管input的死活了 { var skformat = codec.EncodedFormat; format = skformat.ToString().ToLower(); switch (skformat) { case SKEncodedImageFormat.Gif: case SKEncodedImageFormat.Jpeg: case SKEncodedImageFormat.Png: break; default: { skstream.Seek(0); using (SKBitmap bitmap = SKBitmap.Decode(skstream)) { if (!internalCreated) { internalStream = new MemoryStream(8192); internalCreated = true; } else { internalStream.Seek(0, SeekOrigin.Begin); } bitmap.Encode(internalStream, SKEncodedImageFormat.Png, 100); } format = "png"; break; } } } if (internalCreated) { internalStream.Seek(0, SeekOrigin.Begin); } else // internalStream == imgStream { internalStream.Seek(pervious - internalStream.Position, SeekOrigin.Current); } HttpContent imageContent = new StreamContent(internalStream); imageContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "img", FileName = $"{Guid.NewGuid():n}.{format}" }; imageContent.Headers.ContentType = new MediaTypeHeaderValue("image/" + format); HttpContent[] contents = new HttpContent[] { sessionKeyContent, typeContent, imageContent }; try { CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token); return(await _client.PostAsync($"{_options.BaseUrl}/uploadImage", contents, token) .AsApiRespAsync <ISharedImageMessage, ImageMessage>(token) .DisposeWhenCompleted(cts) .ContinueWith(t => t.IsFaulted && t.Exception !.InnerException is JsonException ? throw new NotSupportedException("当前版本的mirai-api-http无法发送图片。") : t, TaskContinuationOptions.ExecuteSynchronously).Unwrap().ConfigureAwait(false)); // ^-- 处理 JsonException 到 NotSupportedException, https://github.com/mamoe/mirai-api-http/issues/85 // internalStream 是 MemoryStream, 内部为全托管字段不需要 Dispose } finally { if (disposeStream) { imgStream.Dispose(); } } }
/// <summary> /// 异步上传图片 /// </summary> /// <exception cref="ArgumentException"/> /// <param name="image">图片流</param> /// <inheritdoc cref="InternalUploadPictureAsync"/> public Task <ImageMessage> UploadPictureAsync(UploadTarget type, Stream image) { InternalSessionInfo session = SafeGetSession(); return(InternalUploadPictureAsync(session, type, image)); }
/// <summary> /// 异步上传图片 /// </summary> /// <exception cref="ArgumentException"/> /// <exception cref="FileNotFoundException"/> /// <param name="imagePath">图片路径</param> /// <inheritdoc cref="InternalUploadPictureAsync"/> public Task <ImageMessage> UploadPictureAsync(UploadTarget type, string imagePath) { InternalSessionInfo session = SafeGetSession(); return(InternalUploadPictureAsync(session, type, new FileStream(imagePath, FileMode.Open, FileAccess.Read, FileShare.Read))); }
public static async Task <VoiceMessage> UploadVoiceAsync(UploadTarget target, string path) { // Mirai-CSharp 的根据路径上传忘了释放创建的流 using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read); return(await session.UploadVoiceAsync(target, fs)); // 直接返回 Task 会导致流提前释放 }
/// <summary> /// 异步上传语音 /// </summary> /// <param name="voice">语音流</param> /// <inheritdoc cref="InternalUploadVoiceAsync"/> public Task <VoiceMessage> UploadVoiceAsync(UploadTarget type, Stream voice) { InternalSessionInfo session = SafeGetSession(); return(InternalUploadVoiceAsync(session, type, voice)); }
/// <summary> /// 内部使用 /// </summary> /// <exception cref="NotSupportedException"/> /// <param name="type">目标类型</param> /// <param name="voiceStream">语音流</param> /// <returns>一个 <see cref="VoiceMessage"/> 实例, 可用于以后的消息发送</returns> private static Task <VoiceMessage> InternalUploadVoiceAsync(InternalSessionInfo session, UploadTarget type, Stream voiceStream) { if (session.ApiVersion < new Version(1, 8, 0)) { throw new NotSupportedException($"当前版本的mirai-api-http不支持上传语音。({session.ApiVersion}, 必须>=1.8.0)"); } HttpContent sessionKeyContent = new StringContent(session.SessionKey); sessionKeyContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "sessionKey" }; HttpContent typeContent = new StringContent(type.ToString().ToLower()); typeContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "type" }; HttpContent voiceContent = new StreamContent(voiceStream); voiceContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "voice", FileName = $"{Guid.NewGuid():n}.amr" }; HttpContent[] contents = new HttpContent[] { sessionKeyContent, typeContent, voiceContent }; return(session.Client.PostAsync($"{session.Options.BaseUrl}/uploadVoice", contents, session.Token) .AsApiRespAsync <VoiceMessage>(session.Token)); }
/// <summary> /// 内部使用 /// </summary> private Task <ISharedVoiceMessage> InternalUploadVoiceAsync(InternalSessionInfo session, UploadTarget type, Stream voiceStream, CancellationToken token = default) { if (session.ApiVersion < new Version(1, 8, 0)) { throw new NotSupportedException($"当前版本的mirai-api-http不支持上传语音。({session.ApiVersion}, 必须>=1.8.0)"); } if (voiceStream is MemoryStream ms) { byte[] buffer = new byte[ms.Length - ms.Position]; ms.Read(buffer, 0, buffer.Length); return(InternalUploadVoiceAsync(session, type, buffer, token)); } async Task <ISharedVoiceMessage> Await(InternalSessionInfo session, UploadTarget type, Stream voiceStream, CancellationToken token = default)
/// <remarks> /// 当 mirai-api-http 的版本小于等于v1.7.0时, 本方法返回的将是一个只有 Url 有值的 <see cref="ImageMessage"/> /// <para/> /// <paramref name="imagePath"/> 会被读取至末尾 /// </remarks> /// <inheritdoc/> public override Task <ISharedImageMessage> UploadPictureAsync(UploadTarget type, string imagePath, CancellationToken token = default) { InternalSessionInfo session = SafeGetSession(); return(InternalUploadPictureAsync(session, type, new FileStream(imagePath, FileMode.Open, FileAccess.Read, FileShare.Read), true, token)); }
/// <remarks> /// 当 mirai-api-http 的版本小于等于v1.7.0时, 本方法返回的将是一个只有 Url 有值的 <see cref="ImageMessage"/> /// <para/> /// <paramref name="image"/> 会被读取至末尾 /// </remarks> /// <inheritdoc/> public override Task <ISharedImageMessage> UploadPictureAsync(UploadTarget type, Stream image, CancellationToken token = default) { InternalSessionInfo session = SafeGetSession(); return(InternalUploadPictureAsync(session, type, image, false, token)); }
/// <summary> /// 内部使用 /// </summary> /// <exception cref="InvalidOperationException"/> /// <param name="type">目标类型</param> /// <param name="imgStream">图片流</param> /// <remarks> /// 注意: 当 mirai-api-http 的版本小于等于v1.7.0时, 本方法返回的将是一个只有 Url 有值的 <see cref="ImageMessage"/> /// </remarks> /// <returns>一个 <see cref="ImageMessage"/> 实例, 可用于以后的消息发送</returns> private static Task <ImageMessage> InternalUploadPictureAsync(InternalSessionInfo session, UploadTarget type, Stream imgStream) { if (session.ApiVersion <= new Version(1, 7, 0)) { Guid guid = Guid.NewGuid(); ImageHttpListener.RegisterImage(guid, imgStream); return(Task.FromResult(new ImageMessage(null, $"http://127.0.0.1:{ImageHttpListener.Port}/fetch?guid={guid:n}", null))); } HttpContent sessionKeyContent = new StringContent(session.SessionKey); sessionKeyContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "sessionKey" }; HttpContent typeContent = new StringContent(type.ToString().ToLower()); typeContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "type" }; string format; using MemoryStream codecMemoryStream = new MemoryStream(); imgStream.CopyTo(codecMemoryStream); codecMemoryStream.Seek(0, SeekOrigin.Begin); using (SKCodec codec = SKCodec.Create(codecMemoryStream)) { var skformat = codec.EncodedFormat; format = skformat.ToString().ToLower(); switch (skformat) { case SKEncodedImageFormat.Gif: case SKEncodedImageFormat.Jpeg: case SKEncodedImageFormat.Png: break; default: imgStream.Seek(0, SeekOrigin.Begin); using (SKBitmap bitmap = SKBitmap.Decode(imgStream)) { MemoryStream ms = new MemoryStream(); bitmap.Encode(ms, SKEncodedImageFormat.Png, 100); imgStream = ms; format = SKEncodedImageFormat.Png.ToString().ToLower(); } break; } } imgStream.Seek(0, SeekOrigin.Begin); HttpContent imageContent = new StreamContent(imgStream); imageContent.Headers.ContentDisposition = new ContentDispositionHeaderValue("form-data") { Name = "img", FileName = $"{Guid.NewGuid():n}.{format}" }; imageContent.Headers.ContentType = new MediaTypeHeaderValue("image/" + format); HttpContent[] contents = new HttpContent[] { sessionKeyContent, typeContent, imageContent }; return(session.Client.PostAsync($"{session.Options.BaseUrl}/uploadImage", contents, session.Token) .AsNoSuccCodeApiRespAsync <ImageMessage>(session.Token) .ContinueWith(t => t.IsFaulted && t.Exception !.InnerException is JsonException ? throw new NotSupportedException("当前版本的mirai-api-http无法发送图片。") : t, TaskContinuationOptions.ExecuteSynchronously).Unwrap()); // ^-- 处理 JsonException 到 NotSupportedException, https://github.com/mamoe/mirai-api-http/issues/85 }
public static async Task <IChatMessage[]> ToMiraiApiHttpMessages(this IGreenOnionsMessages greenOnionsMessage, IMiraiHttpSession session, UploadTarget uploadTarget) { List <IChatMessage> miraiApiHttpMessages = new List <IChatMessage>(); List <Mirai.CSharp.HttpApi.Models.ChatMessages.ForwardMessageNode> nodes = new List <Mirai.CSharp.HttpApi.Models.ChatMessages.ForwardMessageNode>(); for (int i = 0; i < greenOnionsMessage.Count; i++) { if (greenOnionsMessage[i] is IGreenOnionsTextMessage txtMsg) { miraiApiHttpMessages.Add(new Mirai.CSharp.HttpApi.Models.ChatMessages.PlainMessage(txtMsg.Text)); } else if (greenOnionsMessage[i] is IGreenOnionsImageMessage imgMsg) { if (!string.IsNullOrEmpty(imgMsg.Url)) { string url = null; string path = null; if (File.Exists(imgMsg.Url)) { path = imgMsg.Url; } else { url = imgMsg.Url; } miraiApiHttpMessages.Add(new Mirai.CSharp.HttpApi.Models.ChatMessages.ImageMessage(null, url, path)); } else if (!string.IsNullOrEmpty(imgMsg.Base64Str)) { using (MemoryStream ms = imgMsg.MemoryStream) { miraiApiHttpMessages.Add(await session.UploadPictureAsync(uploadTarget, ms)); } } } else if (greenOnionsMessage[i] is IGreenOnionsAtMessage atMsg) { if (atMsg.AtId == -1) { miraiApiHttpMessages.Add(new Mirai.CSharp.HttpApi.Models.ChatMessages.AtAllMessage()); } else { miraiApiHttpMessages.Add(new Mirai.CSharp.HttpApi.Models.ChatMessages.AtMessage(atMsg.AtId)); } } else if (greenOnionsMessage[i] is IGreenOnionsForwardMessage forwardMsg) { for (int j = 0; j < forwardMsg.ItemMessages.Count; j++) { Mirai.CSharp.HttpApi.Models.ChatMessages.ForwardMessageNode node = new Mirai.CSharp.HttpApi.Models.ChatMessages.ForwardMessageNode() { Id = i * j + j, Name = forwardMsg.ItemMessages[j].NickName, QQNumber = forwardMsg.ItemMessages[j].QQid, Time = DateTime.Now, Chain = (await ToMiraiApiHttpMessages(forwardMsg.ItemMessages[j].itemMessage, session, uploadTarget)).Select(msg => msg as Mirai.CSharp.HttpApi.Models.ChatMessages.IChatMessage).ToArray(), }; nodes.Add(node); } } } if (nodes.Count > 0) { Mirai.CSharp.HttpApi.Models.ChatMessages.ForwardMessage forwardMessage = new Mirai.CSharp.HttpApi.Models.ChatMessages.ForwardMessage(nodes.ToArray()); miraiApiHttpMessages.Add(forwardMessage); } return(miraiApiHttpMessages.ToArray()); }
public abstract Task <IImageMessage> UploadPictureAsync(UploadTarget type, string imagePath, CancellationToken token = default);