Ejemplo n.º 1
0
        /// <inheritdoc/>
        /// <exception cref="InvalidAuthKeyException"/>
        public override Task RegisterCommandAsync(string name, string[]?alias = null, string?description = null, string?usage = null, CancellationToken token = default)
        {
            InternalSessionInfo session = SafeGetSession();

            CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token);
            return(RegisterCommandAsync(_client, _options, name, alias, description, usage, token).DisposeWhenCompleted(cts));
        }
Ejemplo n.º 2
0
        public override Task <long[]> GetManagersAsync(long qqNumber, CancellationToken token = default)
        {
            InternalSessionInfo session = SafeGetSession();

            CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token);
            return(GetManagersAsync(_client, _options, qqNumber, token).DisposeWhenCompleted(cts));
        }
Ejemplo n.º 3
0
        /// <summary>
        /// 异步获取群列表
        /// </summary>
        /// <exception cref="InvalidOperationException"/>
        public Task <IGroupInfo[]> GetGroupListAsync()
        {
            InternalSessionInfo session = SafeGetSession();

            return(session.Client.GetAsync($"{session.Options.BaseUrl}/groupList?sessionKey={session.SessionKey}", session.Token)
                   .AsApiRespAsync <IGroupInfo[], GroupInfo[]>(session.Token));
        }
Ejemplo n.º 4
0
        /// <summary>
        /// 异步获取群成员列表
        /// </summary>
        /// <exception cref="InvalidOperationException"/>
        /// <exception cref="TargetNotFoundException"/>
        /// <param name="groupNumber">将要进行查询的群号</param>
        public Task <IGroupMemberInfo[]> GetGroupMemberListAsync(long groupNumber)
        {
            InternalSessionInfo session = SafeGetSession();

            return(session.Client.GetAsync($"{session.Options.BaseUrl}/memberList?sessionKey={session.SessionKey}&target={groupNumber}", session.Token)
                   .AsApiRespAsync <IGroupMemberInfo[], GroupMemberInfo[]>(session.Token));
        }
        /// <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));
        }
Ejemplo n.º 6
0
        /// <summary>
        /// 异步获取给定群员的信息
        /// </summary>
        /// <exception cref="InvalidOperationException"/>
        /// <exception cref="TargetNotFoundException"/>
        /// <param name="memberId">要获取信息的QQ号</param>
        /// <param name="groupNumber">该用户所在群号</param>
        public Task <IGroupMemberCardInfo> GetGroupMemberInfoAsync(long memberId, long groupNumber)
        {
            InternalSessionInfo session = SafeGetSession();

            return(session.Client.GetAsync($"{session.Options.BaseUrl}/memberInfo?sessionKey={session.SessionKey}&target={groupNumber}&memberId={memberId}", session.Token)
                   .AsApiRespAsync <IGroupMemberCardInfo, GroupMemberCardInfo>(session.Token));
        }
Ejemplo n.º 7
0
        /// <inheritdoc/>
        public override Task PublishGroupAnnouncementAsync(long groupNumber, ISharedPublishGroupAnnouncementRequest request, CancellationToken token = default)
        {
            if (string.IsNullOrEmpty(request.Content))
            {
                throw new ArgumentException("群公告内容不能为空");
            }
            InternalSessionInfo session = SafeGetSession();

            CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token);
            if (request is not PublishGroupAnnouncementRequest payload)
            {
                payload = new PublishGroupAnnouncementRequest(
                    request.GroupNumber,
                    request.Content,
                    request.SendToNewMember,
                    request.Pinned,
                    request.ShowEditMemberCard,
                    request.AutoPopup,
                    request.RequireConfirmation,
                    request.ImageUrl,
                    request.ImagePath,
                    request.ImageBase64);
            }
            payload.SessionKey = session.SessionKey;
            return(_client.PostAsJsonAsync($"{_options.BaseUrl}/anno/publish", payload, token).AsApiRespAsync(token).DisposeWhenCompleted(cts));
        }
Ejemplo n.º 8
0
        /// <summary>
        /// 异步获取好友列表
        /// </summary>
        /// <exception cref="InvalidOperationException"/>
        public Task <IFriendInfo[]> GetFriendListAsync()
        {
            InternalSessionInfo session = SafeGetSession();

            return(session.Client.GetAsync($"{session.Options.BaseUrl}/friendList?sessionKey={session.SessionKey}", session.Token)
                   .AsNoSuccCodeApiRespAsync <IFriendInfo[], FriendInfo[]>(session.Token));
        }
Ejemplo n.º 9
0
        /// <inheritdoc/>
        public override Task ExecuteCommandAsync(string name, string[]?args, CancellationToken token)
        {
            InternalSessionInfo session = SafeGetSession();

            CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token);
            return(ExecuteCommandAsync(_client, _options, name, args, token).DisposeWhenCompleted(cts));
        }
 /// <inheritdoc/>
 public override Task<ISharedGroupMemberInfo[]> GetGroupMemberListAsync(long groupNumber, CancellationToken token = default)
 {
     InternalSessionInfo session = SafeGetSession();
     CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token);
     return _client.GetAsync($"{_options.BaseUrl}/memberList?sessionKey={session.SessionKey}&target={groupNumber}", token)
         .AsApiRespV2Async<ISharedGroupMemberInfo[], GroupMemberInfo[]>(token)
         .DisposeWhenCompleted(cts);
 }
 /// <inheritdoc/>
 public override Task<ISharedFriendInfo[]> GetFriendListAsync(CancellationToken token = default)
 {
     InternalSessionInfo session = SafeGetSession();
     CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token);
     return _client.GetAsync($"{_options.BaseUrl}/friendList?sessionKey={session.SessionKey}", token)
         .AsApiRespV2Async<ISharedFriendInfo[], FriendInfo[]>(token)
         .DisposeWhenCompleted(cts);
 }
Ejemplo n.º 12
0
        private async Task <IMiraiSessionConfig> GetConfigAsync(InternalSessionInfo session)
        {
            using JsonDocument j = await HttpHelper.HttpGetAsync($"{session.Options.BaseUrl}/config?sessionKey={WebUtility.UrlEncode(session.SessionKey)}", token : session.Canceller.Token);

            JsonElement root = j.RootElement;

            return(Utils.Deserialize <MiraiSessionConfig>(in root));
        }
        /// <inheritdoc/>
        public override Task <ISharedFriendProfile> GetFriendProfileAsync(long qqNumber, CancellationToken token = default)
        {
            InternalSessionInfo session = SafeGetSession();

            CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token);
            return(_client.GetAsync($"{_options.BaseUrl}/friendProfile?sessionKey={session.SessionKey}&target={qqNumber}", token)
                   .AsApiRespAsync <ISharedFriendProfile, FriendProfile>(token)
                   .DisposeWhenCompleted(cts));
        }
Ejemplo n.º 14
0
 private Task SetConfigAsync(InternalSessionInfo session, IMiraiSessionConfig config)
 {
     byte[] payload = JsonSerializer.SerializeToUtf8Bytes(new
     {
         sessionKey      = session.SessionKey,
         cacheSize       = config.CacheSize,
         enableWebsocket = config.EnableWebSocket
     }, JsonSerializeOptionsFactory.IgnoreNulls);
     return(InternalHttpPostAsync($"{session.Options.BaseUrl}/config", payload, session.Canceller.Token));
 }
        private async Task <int> CommonSendMessageAsync(string action, long?qqNumber, long?groupNumber, ISharedChatMessage[] chain, int?quoteMsgId, CancellationToken token = default)
        {
            InternalSessionInfo session = SafeGetSession();

            if (chain == null || chain.Length == 0)
            {
                throw new ArgumentException("消息链必须为非空且至少有1条消息。");
            }
            int emptyPlainCount = 0;

            for (int i = 0; i < chain.Length; i++)
            {
                ISharedChatMessage msg = chain[i];
                if (msg is not IChatMessage)
                {
                    throw new ArgumentException($"不允许发送未实现 {typeof(IChatMessage).FullName} 接口的消息。");
                }
                if (msg is ISourceMessage)
                {
                    throw new ArgumentException("无法发送基本信息(SourceMessage)。");
                }
                if (msg is IQuoteMessage)
                {
                    throw new ArgumentException("无法发送引用信息(QuoteMessage), 请使用quoteMsgId参数进行引用。");
                }
                if (msg is IPlainMessage pm && string.IsNullOrEmpty(pm.Message))
                {
                    emptyPlainCount++;
                }
            }
            if (emptyPlainCount == chain.Length)
            {
                throw new ArgumentException("消息链仅含文本消息且所有文本均为空。");
            }
            // 前边已经检查了所有元素均实现了 IChatMessage 接口, 但直接强制转换数组类型会引发 InvalidCastException, 所以玩一点 trick
            IEnumerable <IChatMessage> convertedChain = Unsafe.As <ISharedChatMessage[], IEnumerable <IChatMessage> >(ref chain);

            CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token);
            var payload = new
            {
                sessionKey   = session.SessionKey,
                qq           = qqNumber,
                group        = groupNumber,
                quote        = quoteMsgId,
                messageChain = convertedChain
            };

            using JsonDocument j = await _client.PostAsJsonAsync($"{_options.BaseUrl}/{action}", payload, _chatMessageSerializingOptions, token).GetJsonAsync(token).DisposeWhenCompleted(cts).ConfigureAwait(false);

            JsonElement root = j.RootElement;

            root.EnsureApiRespCode();
            return(root.GetProperty("messageId").GetInt32());
        }
        /// <summary>
        /// 异步撤回消息
        /// </summary>
        /// <param name="messageId">
        /// 请提供以下之一
        /// <list type="bullet">
        /// <item><see cref="SourceMessage.Id"/></item>
        /// <item><see cref="SendFriendMessageAsync(long, IMessageBase[], int?)"/> 的返回值</item>
        /// <item><see cref="SendTempMessageAsync(long, long, IMessageBase[], int?)"/> 的返回值</item>
        /// <item><see cref="SendGroupMessageAsync(long, IMessageBase[], int?)"/> 的返回值</item>
        /// </list>
        /// </param>
        /// <exception cref="InvalidOperationException"/>
        /// <exception cref="TargetNotFoundException"/>
        public Task RevokeMessageAsync(int messageId)
        {
            InternalSessionInfo session = SafeGetSession();

            byte[] payload = JsonSerializer.SerializeToUtf8Bytes(new
            {
                sessionKey = session.SessionKey,
                target     = messageId
            });
            return(InternalHttpPostAsync(session.Client, $"{session.Options.BaseUrl}/recall", payload, session.Token));
        }
        /// <summary>
        /// 内部使用
        /// </summary>
        /// <exception cref="InvalidOperationException"/>
        /// <exception cref="PermissionDeniedException"/>
        /// <exception cref="TargetNotFoundException"/>
        /// <param name="action">禁言为 <see langword="true"/>, 解禁为 <see langword="false"/></param>
        /// <param name="groupNumber">将要操作的群号</param>
        private Task InternalToggleMuteAllAsync(bool action, long groupNumber)
        {
            InternalSessionInfo session = SafeGetSession();

            byte[] payload = JsonSerializer.SerializeToUtf8Bytes(new
            {
                sessionKey = session.SessionKey,
                target     = groupNumber
            });
            return(InternalHttpPostAsync(session.Client, $"{session.Options.BaseUrl}/{(action ? "muteAll" : "unmuteAll")}", payload, session.Token));
        }
        /// <summary>
        /// 异步使当前机器人退出给定的群
        /// </summary>
        /// <exception cref="InvalidOperationException"/>
        /// <exception cref="TargetNotFoundException"/>
        /// <param name="groupNumber">将要退出的群号</param>
        public Task LeaveGroupAsync(long groupNumber)
        {
            InternalSessionInfo session = SafeGetSession();

            byte[] payload = JsonSerializer.SerializeToUtf8Bytes(new
            {
                sessionKey = session.SessionKey,
                target     = groupNumber,
            });
            return(InternalHttpPostAsync(session.Client, $"{session.Options.BaseUrl}/quit", payload, session.Token));
        }
Ejemplo n.º 19
0
        /// <summary>
        /// 异步撤回消息
        /// </summary>
        /// <param name="messageId">
        /// 请提供以下之一
        /// <list type="bullet">
        /// <item><see cref="SourceMessage.Id"/></item>
        /// <item><see cref="SendFriendMessageAsync(long, IMessageBase[], int?)"/> 的返回值</item>
        /// <item><see cref="SendTempMessageAsync(long, long, IMessageBase[], int?)"/> 的返回值</item>
        /// <item><see cref="SendGroupMessageAsync(long, IMessageBase[], int?)"/> 的返回值</item>
        /// </list>
        /// </param>
        /// <exception cref="InvalidOperationException"/>
        /// <exception cref="TargetNotFoundException"/>
        public Task RevokeMessageAsync(int messageId)
        {
            InternalSessionInfo session = SafeGetSession();
            var payload = new
            {
                sessionKey = session.SessionKey,
                target     = messageId
            };

            return(session.Client.PostAsJsonAsync($"{session.Options.BaseUrl}/recall", payload, session.Token).AsApiRespAsync(session.Token));
        }
Ejemplo n.º 20
0
        /// <inheritdoc/>
        public Task SetConfigAsync(IMiraiSessionConfig config, CancellationToken token = default)
        {
            InternalSessionInfo session = SafeGetSession();

            if (session.ApiVersion.Major >= 2)
            {
                throw new NotSupportedException("自 mirai-api-http v2.0.0 起, 本接口不受支持");
            }
            CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token);
            return(SetConfigAsync(session, config, token).DisposeWhenCompleted(cts));
        }
Ejemplo n.º 21
0
        private Task SetConfigAsync(InternalSessionInfo session, IMiraiSessionConfig config, CancellationToken token = default)
        {
            var payload = new
            {
                sessionKey      = session.SessionKey,
                cacheSize       = config.CacheSize,
                enableWebsocket = config.EnableWebSocket
            };

            return(_client.PostAsJsonAsync($"{_options.BaseUrl}/config", payload, JsonSerializeOptionsFactory.IgnoreNulls, session.Token).AsApiRespAsync(session.Token));
        }
Ejemplo n.º 22
0
        /// <summary>
        /// 异步使当前机器人退出给定的群
        /// </summary>
        /// <exception cref="InvalidOperationException"/>
        /// <exception cref="TargetNotFoundException"/>
        /// <param name="groupNumber">将要退出的群号</param>
        public Task LeaveGroupAsync(long groupNumber)
        {
            InternalSessionInfo session = SafeGetSession();
            var payload = new
            {
                sessionKey = session.SessionKey,
                target     = groupNumber,
            };

            return(session.Client.PostAsJsonAsync($"{session.Options.BaseUrl}/quit", payload, session.Token)
                   .AsApiRespAsync(session.Token));
        }
Ejemplo n.º 23
0
        /// <summary>
        /// 内部使用
        /// </summary>
        /// <exception cref="InvalidOperationException"/>
        /// <exception cref="PermissionDeniedException"/>
        /// <exception cref="TargetNotFoundException"/>
        /// <param name="action">禁言为 <see langword="true"/>, 解禁为 <see langword="false"/></param>
        /// <param name="groupNumber">将要操作的群号</param>
        private Task InternalToggleMuteAllAsync(bool action, long groupNumber)
        {
            InternalSessionInfo session = SafeGetSession();
            var payload = new
            {
                sessionKey = session.SessionKey,
                target     = groupNumber
            };

            return(session.Client.PostAsJsonAsync($"{session.Options.BaseUrl}/{(action ? "muteAll" : "unmuteAll")}", payload, session.Token)
                   .AsApiRespAsync(session.Token));
        }
        /// <summary>
        /// 异步修改群信息
        /// </summary>
        /// <exception cref="InvalidOperationException"/>
        /// <exception cref="PermissionDeniedException"/>
        /// <exception cref="TargetNotFoundException"/>
        /// <param name="groupNumber">要进行修改的群号</param>
        /// <param name="config">群信息。其中不进行修改的值请置为 <see langword="null"/></param>
        public Task ChangeGroupConfigAsync(long groupNumber, IGroupConfig config)
        {
            InternalSessionInfo session = SafeGetSession();

            byte[] payload = JsonSerializer.SerializeToUtf8Bytes(new
            {
                sessionKey = session.SessionKey,
                target     = groupNumber,
                config
            }, JsonSerializeOptionsFactory.IgnoreNulls);
            return(InternalHttpPostAsync(session.Client, $"{session.Options.BaseUrl}/groupConfig", payload, session.Token));
        }
        /// <inheritdoc/>
        public override Task <ISharedUserProfile> GetUserProfileAsync(long qqNumber, CancellationToken token = default)
        {
            InternalSessionInfo session = SafeGetSession();

            CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token);
            if (session.ApiVersion < new System.Version(2, 4))
            {
                throw new System.NotSupportedException("本接口仅在 mirai-api-http v2.4.0 及以上版本提供");
            }
            return(_client.GetAsync($"{_options.BaseUrl}/userProfile?sessionKey={session.SessionKey}&userId={qqNumber}", token)
                   .AsApiRespAsync <ISharedUserProfile, UserProfile>(token)
                   .DisposeWhenCompleted(cts));
        }
 /// <inheritdoc/>
 public override Task DeleteFriendAsync(long qqNumber, CancellationToken token = default)
 {
     InternalSessionInfo session = SafeGetSession();
     CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token);
     var payload = new
     {
         sessionKey = session.SessionKey,
         target = qqNumber,
     };
     return _client.PostAsJsonAsync($"{_options.BaseUrl}/deleteFriend", payload, token)
         .AsApiRespAsync(token)
         .DisposeWhenCompleted(cts);
 }
        /// <summary>
        /// 异步将给定用户踢出给定的群
        /// </summary>
        /// <exception cref="InvalidOperationException"/>
        /// <exception cref="PermissionDeniedException"/>
        /// <exception cref="TargetNotFoundException"/>
        /// <param name="memberId">将要被踢出的QQ号</param>
        /// <param name="groupNumber">该用户所在群号</param>
        /// <param name="msg">附加消息</param>
        public Task KickMemberAsync(long memberId, long groupNumber, string msg = "您已被移出群聊")
        {
            InternalSessionInfo session = SafeGetSession();

            byte[] payload = JsonSerializer.SerializeToUtf8Bytes(new
            {
                sessionKey = session.SessionKey,
                target     = groupNumber,
                memberId,
                msg
            });
            return(InternalHttpPostAsync(session.Client, $"{session.Options.BaseUrl}/kick", payload, session.Token));
        }
Ejemplo n.º 28
0
        private async void ReceiveCommandLoop(InternalSessionInfo session, CancellationToken token)
        {
            using ClientWebSocket ws = new ClientWebSocket();
            try
            {
                await ws.ConnectAsync(new Uri($"ws://{session.Options.Host}:{session.Options.Port}/command?authKey={session.Options.AuthKey}"), token);

                while (true)
                {
                    using MemoryStream ms = await ws.ReceiveFullyAsync(token);

                    ms.Seek(0, SeekOrigin.Begin);
                    using JsonDocument j = await JsonDocument.ParseAsync(ms, default, token);
 /// <inheritdoc/>
 public override Task SetEssenceMessageAsync(int id, CancellationToken token = default)
 {
     InternalSessionInfo session = SafeGetSession();
     CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token);
     var payload = new
     {
         sessionKey = session.SessionKey,
         target = id,
     };
     return _client.PostAsJsonAsync($"{_options.BaseUrl}/setEssence", payload, token)
         .AsApiRespAsync(token)
         .DisposeWhenCompleted(cts);
 }
 /// <summary>
 /// 内部使用
 /// </summary>
 private Task InternalToggleMuteAllAsync(bool action, long groupNumber, CancellationToken token = default)
 {
     InternalSessionInfo session = SafeGetSession();
     CreateLinkedUserSessionToken(session.Token, token, out CancellationTokenSource? cts, out token);
     var payload = new
     {
         sessionKey = session.SessionKey,
         target = groupNumber
     };
     return _client.PostAsJsonAsync($"{_options.BaseUrl}/{(action ? "muteAll" : "unmuteAll")}", payload, token)
         .AsApiRespAsync(token)
         .DisposeWhenCompleted(cts);
 }