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);
        }
Exemplo n.º 2
0
        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));
        }
Exemplo n.º 4
0
        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);
Exemplo n.º 7
0
        /// <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)));
        }
Exemplo n.º 11
0
 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);