GithubHelp home page GithubHelp logo

mirai-net-shelter / mirai.net Goto Github PK

View Code? Open in Web Editor NEW
179.0 2.0 29.0 803 KB

Mirai.Net是基于mirai-api-http实现的轻量级mirai社区sdk。

License: GNU Affero General Public License v3.0

C# 100.00%
bot dotnet-core qqbot qq mirai mirai-bot mirai-api-http

mirai.net's People

Contributors

cyanray avatar cyl18 avatar easyworld avatar ksharperd avatar kyocius avatar lightquanta avatar mliybs avatar natsukage avatar sinoahpx avatar sunnieshine avatar yukuyoulei avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

mirai.net's Issues

[FEATURE]异步处理消息

背景:
机器人同时收到多条消息时,似乎是按照先后顺序逐条响应的。如果前一个消息的没有处理完,会一直处于等待状态
我实现的功能中有些比较耗时,需要等待网络请求和下载图片,容易阻碍后面消息

问题:
不知道有没有开启异步处理的设置选项,可以提高消息处理的效率?
谢谢!

版本:
mirai-console 2.12.1
mirai-api-http 2.6.2
Mirai.Net 2.4.6

初始化:

MiraiBot bot = new MiraiBot()
{
    Address = new ConnectConfig()
    {
        HttpAddress = "127.0.0.1:9908",
        WebsocketAddress = "127.0.0.1:9908",
    },
    VerifyKey = "1234567890",
    QQ = $"{BotId}",
};
await bot.LaunchAsync();
//监听事件
bot.MessageReceived.OfType<GroupMessageReceiver>().Subscribe(BotMessageHandler.HandleGroupMessage);
bot.MessageReceived.OfType<FriendMessageReceiver>().Subscribe(BotMessageHandler.HandleFriendMessage);
bot.MessageReceived.OfType<TempMessageReceiver>().Subscribe(BotMessageHandler.HandleTempMessage);
bot.EventReceived.OfType<NewFriendRequestedEvent>().Subscribe(BotRequestHandler.HandleNewFriendRequest);
bot.EventReceived.OfType<NewInvitationRequestedEvent>().Subscribe(BotRequestHandler.HandleNewInvitationRequest);

[建议] 请将单例改为对象

https://github.com/SinoAHpx/Mirai.Net/blob/35814dc6d3ef69d952f96568c5ba4c2a0860b4b6/Mirai.Net/Sessions/MiraiBot.cs#L46

在Http下,该项目使用了大量的MiraiBot单例进行HTTP请求,对于多个MiraiBot对象没办法单独使用,只能使用最新的一个Bot实例。

大部分在 Mirai.Net.Sessions.Http.Managers 、Mirai.Net.Utils.Internal.MiraiHttpUtils 下

拿MiraiHttpUtils中的一个举例

https://github.com/SinoAHpx/Mirai.Net/blob/35814dc6d3ef69d952f96568c5ba4c2a0860b4b6/Mirai.Net/Utils/Internal/MiraiHttpUtils.cs#L52-L64

该段引用了静态单例实例,MiraiBot.Instance.HttpSessionKey,从而导致无法对不同的MiraiBot对象进行操作。

所以我觉得应该改为以下格式,应该传入MiraiBot实例,改为可以单独使用的静态方法,然后添加this关键字弄成扩展方法,这样Mirai对象可以直接点出该方法进行使用

    internal static async Task<string> GetAsync(this MiraiBot bot, string url, bool withSessionKey = true)
    {
        var result = withSessionKey
            ? await url
                .WithHeader("Authorization", $"session {bot.HttpSessionKey}")
                .GetAsync()
            : await url.GetAsync();

        var response = await result.GetStringAsync();
        bot.EnsureSuccess(response, $"url={url}");

        return response;
    }

这只是一个建议,我是无所谓的,只是觉得不合理。静态拓展方法不应该使用单例Bot,应该使用参数进行传参,否则就没必要封装一个静态拓展方法。

调用MuteAsync时出现异常

调用的代码是
await GroupManager.MuteAsync((string)target, e.Sender.Group.Id, (TimeSpan)ts);

异常的message为
原因: 上传文件错误
备注: url=localhost:端口/memberInfo?target=group&memberId=“需要禁言的qqID”

不知道是我调用错误还是确实有问题(

在重启 mirai-console 的情况下,无法自动重连

复现步骤:

  1. 正常开启 mirai-console,程序成功建立连接,可以正常处理消息;
  2. 关闭并再次开启 mirai-console;
  3. 发现程序无法再接收到任何消息

我看了这个 WebSocket 库默认启用自动重连的,但是不知道为什么没起作用。(是否需要额外的设置?)

我目前的解决方案是处理 WebSocketClient 的 DisconnectionHappened 事件,在断线后调用 LaunchAsync 重新建立连接。

Update instruction

Internal structure modifications

  • Composing extension methods for Data entity classes
  • Enhance the experience of constructing MessageChain

External improvements

  • Update developing document & README.md for the repository

[FEATURE] 建议在GroupManager类中提供指向AccountManager中对应方法的映射

如题,目前例如获取群员列表等操作全都放在了AccountManager中,在实际使用过程中感觉很反直觉(这部分也没有在文档中列出,我还是翻源码才发现它是在AccountManager里的…)
希望将AccountManager.GetGroupMembersAsync()获取群成员列表,.GetGroupsAsync()获取群列表等方法在GroupManager中建立映射。
其实我是觉得应该直接把这些方法放在GroupManager里会更好一些的,尤其是目前获取群成员Member对象的方法位于GroupManager.GetMemberAsync(),而获取群成员Profile的方法位于AccountManager.GetMemberProfileAsync(),这部分感觉有些混乱了。

[BUG] 上传群文件不支持中文文件名

发生了什么事?
使用FileManager.UploadFileAsync()可以正常上传英文数字文件名的文件,但是当文件名包含中文日文等字符时MAH就会反馈500错误,上传失败。提示信息类似于

2023-03-14 08:57:21 E/MAH Access: java.lang.IllegalArgumentException: Chars ':*?"<>|' are not allowed in path. RemoteFile path contains illegal char: '?'. path='=?utf-8?B?5rWLMTg4NDY1OTguemlw?='
java.lang.IllegalArgumentException: Chars ':*?"<>|' are not allowed in path. RemoteFile path contains illegal char: '?'. path='=?utf-8?B?5rWLMTg4NDY1OTguemlw?='
        at net.mamoe.mirai.internal.utils.FileSystem.checkLegitimacy(FileSystem.kt:17)
        at net.mamoe.mirai.internal.utils.FileSystem.normalize(FileSystem.kt:26)
        at net.mamoe.mirai.internal.contact.file.RemoteFilesImpl$Companion.findFileByPath(RemoteFilesImpl.kt:34)
        at net.mamoe.mirai.internal.contact.file.CommonAbsoluteFolderImpl.uploadNewFile$suspendImpl(AbsoluteFolderImpl.kt:400)
        at net.mamoe.mirai.internal.contact.file.CommonAbsoluteFolderImpl.uploadNewFile(AbsoluteFolderImpl.kt)
        at net.mamoe.mirai.contact.file.AbsoluteFolder.uploadNewFile$default(AbsoluteFolder.kt:194)
        at mirai-api-http-2.9.1.mirai2.jar//net.mamoe.mirai.api.http.adapter.internal.action.FileKt.onUploadFile(file.kt:62)
        at mirai-api-http-2.9.1.mirai2.jar//net.mamoe.mirai.api.http.adapter.http.router.FileKt$fileRouter$1$invoke$$inlined$httpAuthedMultiPart$1$1.invokeSuspend(dsl.kt:228)
        at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
        at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
        at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42)
        at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
        at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:750)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
        at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)

怎么复现?
直接使用FileManager.UploadFileAsync()上传文件名包含中文的文件就可以复现,除了http返回外,Mirai窗口也会有错误提示。

截图
TMFJ(210R)IW4ARQYH5R{T

环境

  • Mirai版本: 2.14.0
  • MAH版本: 2.9.1
  • Mirai.Net版本: 2.4.8

补充信息
在MAH处提出了issue #698
似乎这是.NET Standard 2.0的MultipartFormDataContent方法本身有问题,而使用了它的FlUrl等也自然有了同样的问题。
.net 6.0的MultipartFormDataContent将file=项进行了错误的编码,而MAH并不支持这样的编码。
例如当编码新建文本文档.txt时,MultipartFormDataContent会将filename项编码为filename="=?utf-8?B?5paw5bu65paH5pys5paH5qGjLnR4dA==?="。根据上面issue中其他人的解答,这部分编码的方式是错误的。MAH接收到这串字符串后,会直接将其作为文件名,导致出错。正常情况下,应该直接发送UTF-8编码的filename="新建文本文档.txt"

此问题目前暂且只能通过手搓轮子来解决。希望框架能原生支持。

var hackedFileName = new string(Encoding.UTF8.GetBytes(fileName).Select(b => (char)b).ToArray());
streamContent.Headers.Add("Content-Disposition", $@"form-data; name=file; filename=""{hackedFileName}""; filename*=""{hackedFileName}""");
content.Add(streamContent);

通过以上方式而非FlUrl的.AddFile()方法添加文件

我目前搓的轮子完整代码如下,供参考

public static async Task<Mirai.Net.Data.Shared.File> UploadFile(
            string groupId,
            string filePath,
            string uploadPath = "", string fileName = null)
{
    string url = string.Format("http://{0}/file/upload", MiraiBot.Instance.Address.HttpAddress);
    fileName ??= Path.GetFileName(filePath);
    using var httpClient = new HttpClient();
    using var content = new MultipartFormDataContent
    {
        { new StringContent(MiraiBot.Instance.HttpSessionKey), "sessionKey" },
        { new StringContent("group"), "type" },
        { new StringContent(groupId), "target" },
        { new StringContent(uploadPath), "path" }
    };

    // 从文件加载数据
    var streamContent = new StreamContent(File.Open(filePath, FileMode.Open));

    var hackedFileName = new string(Encoding.UTF8.GetBytes(fileName).Select(b => (char)b).ToArray());
    streamContent.Headers.Add("Content-Disposition", $@"form-data; name=file; filename=""{hackedFileName}""; filename*=""{hackedFileName}""");
    content.Add(streamContent);
    
    // 发送POST请求

    var responseBody = await (await httpClient.PostAsync(url, content)).Content.ReadAsStringAsync();
    //Console.WriteLine(responseBody);
    JObject re = responseBody.ToJObject();
    Mirai.Net.Data.Shared.File file = !re.ContainsKey("name") ? null : re.ToObject<Mirai.Net.Data.Shared.File>();
    
    return file;
}

Add missing fields in File.FileDownloadInfo

Expectation

{
  "code":0,
  "msg":"",
  "data": [
    {
      "name":"setu.png",
      "id":"/12314d-1wf13-a98ffa",
      "path":"/setu.png",
      "parent":null,
      "contact":{
        "id":123123,
        "name":"setu qun",
        "permission":"OWNER"
      },
      "isFile":true,
      "isDictionary":false,
      "isDirectory":false,
      "downloadInfo":{
        "sha1":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "md5":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
        "downloadTimes":10,
        "uploaderId":123456789,
        "uploadTime":1631153749,
        "lastModifyTime":1631153749,
        "url":"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
      }
    }
  ]
}

Exist implement

public class FileDownloadInfo
{
    [JsonProperty("sha1")] public string Sha1 { get; set; }
    [JsonProperty("md5")] public string Md5 { get; set; }
    [JsonProperty("url")] public string Url { get; set; }
}

[FEATURE] 希望提供访问HttpSessionKey的方法。

目前框架只有VerifyKey是public类型,而HttpSessionKey是internal并且没有提供外部读取方法。
同时,PostJsonAsync()等较为底层的方法也没有公开,导致使用者无法直接自行向MAH发送非标准的请求。

例如MAH的插件Mirai Hibernate Http,此插件提供了多个新的框架自身未支持的Route,例如GET /message/group?bot={}&group={}&start={}&end={}获取指定群的群聊记录。
或者比如 #72 中遇到的情况,需要自己构建特殊的MAH请求时。

以上情况都需要当前bot实例的SessionKey才可以进行。所以希望框架能够提供访问当前SessionKey的方法。
(或者是直接开放允许用户自定义Route与请求的PostJsonAsync()方法也可以实现上面提到的需求场景)
多谢!

[BUG]无法获取NudgeEvent

发生了什么事?
无法获取NudgeEvent

怎么复现?

bot.MessageReceived
                .OfType<NudgeEvent>()
                .Subscribe(Received =>
                {
                    Console.WriteLine($"[{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}][INFO]收到来自{Received.FromId}的戳一戳。");
                    if (Received.Target != "114514" || Received.FromId == "114514")
                    {
                        return;
                    }
                    else
                    {
                        MessageManager.SendNudgeAsync(Received.FromId, Received.Subject.Id, MessageReceivers.Group);
                    }
                });

你以为它会做什么?
本应在收到戳一戳消息后在控制台输出消息来源,并且戳发出戳一戳的人,但似乎并无法收到NudgeEvent消息
Mirai控制台可以看见Mirai是有收到戳一戳的。

截图
Cache_258beb8effc446a1
image

环境

  • 操作系统:Windows 10 22H1/Windows Server 2022
  • Mirai版本:v2.12.0
  • Mirai http Api版本:v2.5.2
  • Mirai.Net 版本:2.4.2

[FEATURE] 希望支持Mirai的消息链序列化方法

希望支持Mirai中的消息序列化方法,serializeToMiraiCode()

目前Mirai.Net只有GetPlainMessage()。没有办法直接对比2条消息内容是否相同。
由于消息链中包含有消息的源数据,因此即使是2条复读的消息,其中的Source部分也有差异。当使用==直接对比两条消息链时总是会得到False的结果。

除了消息链外,Image等单个元素也会有类似的问题,即使是同一张图片连续发送,ImageID相同,但是因为每一次的图片地址url都有变化,所以导致即使是同一个消息里的3张ID相同的表情包,判断是否相等时候结果也是False。

目前我能想到的实现方法是逐个元素对比两个消息链并直接跳过Source类型,但是对于图片等类型的元素,依然需要每个元素单独判断两者是否相等(通过imageId),非常不美观。
类似于长这样

        private static bool Equals(MessageChain a,MessageChain b)
        {
            if (a.Count!=b.Count) return false;
            for (int i = 0; i < a.Count; i++)
            {
                if (a[i].Type==Messages.Source || b[i].Type == Messages.Source)
                    continue;
                if (a[i].Type == Messages.Image && b[i].Type == Messages.Image && ((ImageMessage)a[i]).ImageId == ((ImageMessage)b[i]).ImageId) continue;
                if (a[i] != b[i]) return false;
            }
            return true;
        }

如果对比两条消息的Mirai码或者酷Q码是可以简单地判断两条消息的内容是否相同的。并且一整条字符串也方便对消息本身进行整体处理(比如说,我想把消息的简体文字替换成繁体)的同时不影响消息中的表情图片、at等元素。希望能实现此特性。

非常感谢!

[BUG]WebSocket无法收到消息

发生了什么事?
WebSocket无法收到消息

怎么复现?
基本和demo一样的代码
image

你以为它会做什么?
全程没有触发异常,MessageManager.SendFriendMessageAsync可以发消息,但是FriendMessageReceiver等收不到任何东西,断点看了下StartWebsocketListenerAsync也是完全没任何消息收到.

截图
服务启动正常
image

环境

  • 操作系统:win10
  • Mirai版本:2.10.0
  • mirai-api-http:2.5.0
  • 版本:master分支最新

补充信息
原因不明,不知道是否是mirai-api-http的问题

[BUG] Constantly fail to upload file

怎么复现?

bot.MessageReceived
    .OfType<GroupMessageReceiver>()
    .Subscribe(async r =>
{
    if (r.MessageChain.GetPlainMessage() == "/send")
    {
        var localPath = @"C:\Users\ahpx\Desktop\RandomChoiceGenerator.exe";
        var file = await FileManager.UploadFileAsync(r.GroupId, localPath);
        await r.SendMessageAsync($"The file has been uploaded. \r\n{file.ToJsonString()}");
    }
});
  • mirai-api-http 2.5.0
  • mirai & mirai-console 2.11.0-M1
  • mcl 1.2.2

Existing issue: project-mirai/mirai-api-http#553

Bot消息发送速度问题

我没有其他的机器人项目做比对(我看过别群的机器人速度为1s左右),为什么发送消息的时间需要2-3s,这是正常现象还是框架所限还是我自己程序上的问题呢?

[FEATURE]可以增加获取群公告和发布群公告吗?

Mirai里的http api文档里有anno/list(获取群公告) 和anno/publish(发布群公告)
但是在框架里没有找到这两个功能,目前在框架中只看到一个入群公告改变事件。
能否增加这两个新功能?
万谢感谢!

[BUG]升级2.4.4之后无法订阅到事件

发生了什么事?
升级2.4.4之后无法订阅到事件,回退2.4.3后正常

怎么复现?
bot.MessageReceived
.OfType()
.Subscribe(async r =>
{
if (r.Sender.Id != bot.QQ)
{
var msg = r.MessageChain.GetPlainMessage().Trim();
var repMsg = await robotService.GeneralMessageProcess(msg, r.Sender.Id);
await r.SendMessageAsync(repMsg);
_logger.LogInformation($"回复好友消息:{repMsg}");
}
});

环境

  • 操作系统:
  • Mirai版本:
  • Mirai版本:
  • 版本:

建议征集

如果你觉得Mirai.NET现阶段做得还不够好,请回复此issue来说明你的需求是怎么样的,以及你的预期是什么。

Bugs of Sending quote

Incorrect send target specified

Location Mirai.Net.Utils.Scaffolds.MiraiScaffold.QuoteGroupMessageAsync()

public static async Task<string> QuoteGroupMessageAsync(this GroupMessageReceiver receiver, string message)
{
    var id = receiver.MessageChain.ToList().OfType<SourceMessage>().First().MessageId;
    return await MessageManager
        .QuoteGroupMessageAsync(receiver.Sender.Group.Id, id, message);
}

[Enhancement] Include original MessageChain while sending quote message

  • Losing child class detail in QuoteMessage

[FEATURE]希望在ImageMessage中添加bool IsEmoji字段

在mirai控制台上能够看到图片类型的消息带有IsEmoji=true/false的输出,这个可以识别电脑端消息中表情显示的大小,如果IsEmoji是false那么图片会尽可能的根据原尺寸显示,否则就是一个较小的尺寸显示。希望Mirai.Net中也可以支持获取这个字段
谢谢!

[BUG]订阅无效,无消息!WebSocket原因

描述

视乎是WebSocket接收不到数据导致。其次我通过调试发现WebSocket其实是连接上了,但接收不到数据自然没有数据

截图

Mirai.Net 断点与接收

%GSEX 5RXBI9PSD%%_IICQV

这是我自己用C#原生WebSocket尝试链接可以获取到数据

QQ截图20221216074544

环境

Net 7.0

[BUG]AccountManager.GetMemberProfileAsync()无法获取到nickname

发生了什么事?
AccountManager.GetMemberProfileAsync无法获取到nickname

怎么复现?
nickname = AccountManager.GetMemberProfileAsync(ms.num.ToString(), gs.num.ToString()).Result.NickName;

你以为它会做什么?
我认为他会得到nickname,但实际上nickname的值是""(为空)
我试图把他改成
Profile pf = await AccountManager.GetMemberProfileAsync(ms.num.ToString(), gs.num.ToString());
但是IDE告诉我await只能用于异步方法
请原谅我较低的编程水平
如果有错误请指出,不甚感激

环境

  • 操作系统:windows server 2019
  • Mirai版本:2.11.0
  • Mirai版本:2.11.0

[BUG] EnsureSuccess 状态码500对应的错误原因有误以及缺乏关键提示信息

发生了什么事?
目前EnsureSuccess()方法中的json.OfErrorMessage()部分在code=500时会直接返回文件上传错误。并且抛出的错误中,只包含引发错误时Mirai.Net发送的数据,而没有包括MAH端的回应。因此无法根据回应来判断错误的具体内容

举例来说,比如机器人短时间内群发太多内容触发了风控的话,会得到以下回应(其中***部分为群号)

{
  "code": 500,
  "msg": "Failed sending message to Group(**********), reason=LIMITED_MESSAGING. Tips: 问题原因可能是账号被多次举报或被服务器认为不安全. 若账号在官方客户端也无法发出消息, 可尝试用手机 QQ 登录后访问 https://accounts.qq.com/safe/message/unlock?lock_info=5_5 解冻."
}

此外,也包含以下等各种其他情况

500
Send message failed: MessageSvcPbSendMsg.Response.Failed(resultType=110, errorCode=0, errorMessage=发送失败,你已被移出该群,请重新加群。)
500
message is empty 

由于在发送消息的过程中,EnsureSuccess()抛出的异常并不包括返回的msg部分,导致无法在程序中判断错误的具体原因并进行处理。

例如一个应用场景:如果机器人发送消息时触发了上面的风控导致消息发不出去,那么就通过私聊告诉机器人的主人提醒手动上号解冻。

那么这里就需要自己在机器人中手动构筑一个自己的SendGroupMessage()方法封装MessageManager.SendGroupMessageAsync()来捕获异常,并根据异常的种类来判断是否需要发送私聊消息进行上报。

怎么复现?
如上面的例子,可以简单地直接发送空消息至群聊,或发送至已经被解散的群、已经退出的群号等,都可以得到MAH的500错误反馈。

修改建议
希望throw出包含更多内容的自定义异常类,而非直接将错误文字拼装成string并返回。
例如将code与msg封装入exception的自定义字段中,使得程序在catch异常ex后,可以通过ex.code\ex.msg\ex.payload等属性分别获取错误的各项具体内容

环境

  • Mirai版本: 2.14.0
  • Mirai版本: 2.9.1
  • 版本: 2.4.8

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.