{"id":37029906,"url":"https://github.com/dimchat/mkm-java","last_synced_at":"2026-01-14T03:37:16.361Z","repository":{"id":48914026,"uuid":"182232158","full_name":"dimchat/mkm-java","owner":"dimchat","description":"Ming Ke Ming (名可名) -- Account Module","archived":false,"fork":false,"pushed_at":"2026-01-03T22:43:02.000Z","size":770,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-01-08T22:16:01.016Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dimchat.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2019-04-19T08:38:31.000Z","updated_at":"2026-01-03T22:43:06.000Z","dependencies_parsed_at":"2023-02-12T10:31:45.185Z","dependency_job_id":"496da551-06e8-413f-aaaa-e70a7341f5d7","html_url":"https://github.com/dimchat/mkm-java","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"purl":"pkg:github/dimchat/mkm-java","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimchat%2Fmkm-java","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimchat%2Fmkm-java/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimchat%2Fmkm-java/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimchat%2Fmkm-java/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dimchat","download_url":"https://codeload.github.com/dimchat/mkm-java/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dimchat%2Fmkm-java/sbom","scorecard":{"id":24020,"data":{"date":"2025-08-11","repo":{"name":"github.com/dimchat/mkm-java","commit":"7475356fd41262cfed14ef6e94f3428ab1146624"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.2,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Maintained","score":10,"reason":"13 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Binary-Artifacts","score":9,"reason":"binaries present in source code","details":["Warn: binary detected: MingKeMing/gradle/wrapper/gradle-wrapper.jar:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}}]},"last_synced_at":"2025-08-14T17:26:22.042Z","repository_id":48914026,"created_at":"2025-08-14T17:26:22.042Z","updated_at":"2025-08-14T17:26:22.042Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28408850,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T01:52:23.358Z","status":"online","status_checked_at":"2026-01-14T02:00:06.678Z","response_time":107,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-14T03:37:15.694Z","updated_at":"2026-01-14T03:37:16.352Z","avatar_url":"https://github.com/dimchat.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ming Ke Ming (名可名) -- Account Module (Java)\n\n[![License](https://img.shields.io/github/license/dimchat/mkm-java)](https://github.com/dimchat/mkm-java/blob/master/LICENSE)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreeng)](https://github.com/dimchat/mkm-java/pulls)\n[![Platform](https://img.shields.io/badge/Platform-Java%208-brightgreen)](https://github.com/dimchat/mkm-java/wiki)\n[![Issues](https://img.shields.io/github/issues/dimchat/mkm-java)](https://github.com/dimchat/mkm-java/issues)\n[![Repo Size](https://img.shields.io/github/repo-size/dimchat/mkm-java)](https://github.com/dimchat/mkm-java/archive/refs/heads/master.zip)\n[![Tags](https://img.shields.io/github/tag/dimchat/mkm-java)](https://github.com/dimchat/mkm-java/tags)\n[![Version](https://img.shields.io/maven-central/v/chat.dim/MingKeMing)](https://mvnrepository.com/artifact/chat.dim/MingKeMing)\n\n[![Watchers](https://img.shields.io/github/watchers/dimchat/mkm-java)](https://github.com/dimchat/mkm-java/watchers)\n[![Forks](https://img.shields.io/github/forks/dimchat/mkm-java)](https://github.com/dimchat/mkm-java/forks)\n[![Stars](https://img.shields.io/github/stars/dimchat/mkm-java)](https://github.com/dimchat/mkm-java/stargazers)\n[![Followers](https://img.shields.io/github/followers/dimchat)](https://github.com/orgs/dimchat/followers)\n\nThis [document](https://github.com/moky/DIMP/blob/master/MingKeMing-Identity.md) introduces a common **Account Module** for decentralized user identity authentication.\n\n## Features\n\n- [Meta](#meta)\n    - [Type](#meta-type)\n    - [Key](#public-key)\n    - [Seed](#seed)\n    - [Fingerprint](#fingerprint)\n- [ID](#id)\n    - [Type](#id-type)\n    - [Name](#id-name)\n    - [Address](#id-address)\n    - [Terminal](#terminal)\n- [Samples](#samples)\n\n## Meta\n\nThe **Meta** was generated by your **private key**, it can be used to build a new ID for entity, or verify the ID/PK pair.\n\nIt consists of 4 fields:\n\n| Field       | Description                              |\n| ----------- | ---------------------------------------- |\n| type        | Algorithm Version                        |\n| key         | Public Key                               |\n| seed        | Entity Name (Optional)                   |\n| fingerprint | Signature to generate address (Optional) |\n\nIf ```seed``` exists, ```fingerprint = privateKey.sign(seed)```\n\n### Meta Type\n\n1. ```MKM``` _(Default)_\n2. ```BTC```\n3. ~~Extended BTC~~\n4. ```ETH```\n5. ~~Extended ETH~~\n6. ...\n\n### Public Key\n\nA **public key** (PK) was binded to an ID by the **Meta Algorithm**.\n\n### Seed\n\nA string as same as **ID.name** for generate the fingerprint.\n\n### Fingerprint\n\nTHe **fingerprint** field was generated by your **private key** and **seed**:\n\n````java\ndata = seed.getBytes(Charset.forName(\"UTF-8\"));\nfingerprint = privateKey.sign(data);\n````\n\n## ID\n\nThe **ID** is used to identify an **entity**(user/group). It consists of 3 fields and 2 extended properties:\n\n| Field       | Description                    |\n| ----------- | ------------------------------ |\n| type        | Entity type                    |\n| name        | Same with meta.seed (Optional) |\n| address     | Unique Identification          |\n| terminal    | Login point (Optional)         |\n\nThe ID format is ```name@address[/terminal]```.\n\n### ID Type\n\n```java\npublic enum EntityType {\n\n    /**\n     *  Main: 0, 1\n     */\n    USER            (0x00), // 0000 0000\n    GROUP           (0x01), // 0000 0001 (User Group)\n\n    /**\n     *  Network: 2, 3\n     */\n    STATION         (0x02), // 0000 0010 (Server Node)\n    ISP             (0x03), // 0000 0011 (Service Provider)\n    //STATION_GROUP (0x03), // 0000 0011\n\n    /**\n     *  Bot: 4, 5\n     */\n    BOT             (0x04), // 0000 0100 (Business Node)\n    ICP             (0x05), // 0000 0101 (Content Provider)\n    //BOT_GROUP     (0x05), // 0000 0101\n\n    /*\n     *  Management: 6, 7, 8\n     */\n    //SUPERVISOR    (0x06), // 0000 0110 (Company CEO)\n    //COMPANY       (0x07), // 0000 0111 (Super Group for ISP/ICP)\n    //CA            (0x08), // 0000 1000 (Certification Authority)\n\n    /*\n     *  Customized: 64, 65\n     */\n    //APP_USER      (0x40), // 0100 0000 (Application Customized User)\n    //APP_GROUP     (0x41), // 0100 0001 (Application Customized Group)\n\n    /**\n     *  Broadcast: 128, 129\n     */\n    ANY             (0x80), // 1000 0000 (anyone@anywhere)\n    EVERY           (0x81); // 1000 0001 (everyone@everywhere)\n\n    // Network ID\n    public final int value;\n\n    EntityType(int network) {\n        value = network;\n    }\n\n    public boolean equals(int other) {\n        return value == other;\n    }\n\n    public static boolean isUser(int network) {\n        return (network \u0026 GROUP.value) == USER.value;\n    }\n\n    public static boolean isGroup(int network) {\n        return (network \u0026 GROUP.value) == GROUP.value;\n    }\n\n    public static boolean isBroadcast(int network) {\n        return (network \u0026 ANY.value) == ANY.value;\n    }\n}\n```\n\n### ID Name\n\nThe **Name** field is a username, or just a random string for group:\n\n1. The length of name must more than 1 byte, less than 32 bytes;\n2. It should be composed by a-z, A-Z, 0-9, or charactors '_', '-', '.';\n3. It cannot contain key charactors('@', '/').\n\nName examples:\n\n```java\nuser_name  = \"Albert.Moky\";\ngroup_name = \"Group-9527\";\n```\n\nIt's equivalent to ```meta.seed```\n\n### ID Address\n\nThe **Address** field was created with the Meta and a **Network ID**:\n\n#### BTC Address\n\n```java\n/**\n *  Address like BitCoin\n *\n *  \u003cblockquote\u003e\u003cpre\u003e\n *  data format: \"network+digest+code\"\n *      network    --  1 byte\n *      digest     -- 20 bytes\n *      check code --  4 bytes\n *\n *  algorithm:\n *      fingerprint = PK.data\n *      digest      = ripemd160(sha256(fingerprint));\n *      code        = sha256(sha256(network + digest)).prefix(4);\n *      address     = base58_encode(network + digest + code);\n *  \u003c/pre\u003e\u003c/blockquote\u003e\n */\npublic final class BTCAddress extends ConstantString implements Address {\n\n    private final byte type;\n\n    public BTCAddress(String string, byte network) {\n        super(string);\n        type = network;\n    }\n\n    @Override\n    public int getNetwork() {\n        return type;\n    }\n\n    /**\n     *  Generate BTC address with fingerprint and network ID\n     *\n     * @param fingerprint - meta.fingerprint or key.data\n     * @param network - address type\n     * @return Address object\n     */\n    public static BTCAddress generate(byte[] fingerprint, byte network) {\n        // 1. digest = ripemd160(sha256(fingerprint))\n        byte[] digest = RIPEMD160.digest(SHA256.digest(fingerprint));\n        // 2. head = network + digest\n        byte[] head = new byte[21];\n        head[0] = network;\n        System.arraycopy(digest, 0, head, 1, 20);\n        // 3. cc = sha256(sha256(head)).prefix(4)\n        byte[] cc = checkCode(head);\n        // 4. data = base58_encode(head + cc)\n        byte[] data = new byte[25];\n        System.arraycopy(head, 0, data, 0, 21);\n        System.arraycopy(cc,0, data, 21, 4);\n        return new BTCAddress(Base58.encode(data), network);\n    }\n\n    /**\n     *  Parse a string for BTC address\n     *\n     * @param address - address string\n     * @return null on error\n     */\n    public static BTCAddress parse(String address) {\n        int len = address.length();\n        if (len \u003c 26 || len \u003e 35) {\n            return null;\n        }\n        // decode\n        byte[] data = Base58.decode(address);\n        if (data == null || data.length != 25) {\n            return null;\n        }\n        // Check Code\n        byte[] prefix = new byte[21];\n        byte[] suffix = new byte[4];\n        System.arraycopy(data, 0, prefix, 0, 21);\n        System.arraycopy(data, 21, suffix, 0, 4);\n        byte[] cc = checkCode(prefix);\n        if (Arrays.equals(cc, suffix)) {\n            return new BTCAddress(address, data[0]);\n        } else {\n            return null;\n        }\n    }\n\n    private static byte[] checkCode(byte[] data) {\n        byte[] sha256d = SHA256.digest(SHA256.digest(data));\n        assert sha256d != null : \"sha256 error\";\n        byte[] cc = new byte[4];\n        System.arraycopy(sha256d, 0, cc, 0, 4);\n        return cc;\n    }\n}\n```\n\n#### ETH Address\n\n```java\n/**\n *  Address like Ethereum\n *\n *  \u003cblockquote\u003e\u003cpre\u003e\n *  data format:\n *      \"0x{address}\"\n *\n *  algorithm:\n *      fingerprint = PK.data;\n *      digest      = keccak256(fingerprint);\n *      address     = hex_encode(digest.suffix(20));\n *  \u003c/pre\u003e\u003c/blockquote\u003e\n */\npublic final class ETHAddress extends ConstantString implements Address {\n\n    public ETHAddress(String string) {\n        super(string);\n    }\n\n    @Override\n    public int getNetwork() {\n        return EntityType.USER.value;\n    }\n\n    // https://eips.ethereum.org/EIPS/eip-55\n    private static String eip55(String hex) {\n        StringBuilder sb = new StringBuilder();\n        byte[] hash = Keccak256.digest(hex.getBytes());\n        char ch;\n        for (int i = 0; i \u003c 40; ++i) {\n            ch = hex.charAt(i);\n            if (ch \u003e '9') {\n                // check for each 4 bits in the hash table\n                // if the first bit is '1',\n                //     change the character to uppercase\n                ch -= (hash[i \u003e\u003e 1] \u003c\u003c (i \u003c\u003c 2 \u0026 4) \u0026 0x80) \u003e\u003e 2;\n            }\n            sb.append(ch);\n        }\n        return sb.toString();\n    }\n\n    private static boolean isETH(String address) {\n        if (address.length() != 42) {\n            return false;\n        }\n        if (address.charAt(0) != '0' || address.charAt(1) != 'x') {\n            return false;\n        }\n        char ch;\n        for (int i = 2; i \u003c 42; ++i) {\n            ch = address.charAt(i);\n            if (ch \u003e= '0' \u0026\u0026 ch \u003c= '9') {\n                continue;\n            }\n            if (ch \u003e= 'A' \u0026\u0026 ch \u003c= 'Z') {\n                continue;\n            }\n            if (ch \u003e= 'a' \u0026\u0026 ch \u003c= 'z') {\n                continue;\n            }\n            // unexpected character\n            return false;\n        }\n        return true;\n    }\n\n    public static String getValidateAddress(String address) {\n        if (!isETH(address)) {\n            // not an ETH address\n            return null;\n        }\n        String lower = address.substring(2).toLowerCase();\n        return \"0x\" + eip55(lower);\n    }\n\n    public static boolean isValidate(String address) {\n        String validate = getValidateAddress(address);\n        return validate != null \u0026\u0026 validate.equals(address);\n    }\n\n    /**\n     *  Generate ETH address with key.data\n     *\n     * @param fingerprint = key.data\n     * @return Address object\n     */\n    public static ETHAddress generate(byte[] fingerprint) {\n        if (fingerprint.length == 65) {\n            // skip first char\n            byte[] data = new byte[64];\n            System.arraycopy(fingerprint, 1, data, 0, 64);\n            fingerprint = data;\n        }\n        assert fingerprint.length == 64 : \"key data length error: \" + fingerprint.length;\n        // 1. digest = keccak256(fingerprint);\n        byte[] digest = Keccak256.digest(fingerprint);\n        // 2. address = hex_encode(digest.suffix(20));\n        byte[] tail = new byte[20];\n        System.arraycopy(digest, digest.length - 20, tail, 0, 20);\n        String address = \"0x\" + eip55(Hex.encode(tail));\n        return new ETHAddress(address);\n    }\n\n    /**\n     *  Parse a string for ETH address\n     *\n     * @param address - address string\n     * @return null on error\n     */\n    public static ETHAddress parse(String address) {\n        if (!isETH(address)) {\n            // not an ETH address\n            return null;\n        }\n        return new ETHAddress(address);\n    }\n}\n```\n\nWhen you get a meta for the entity ID from the network,\nyou must verify it with the consensus algorithm before accepting its **public key**.\n\n### Terminal\n\nA resource identifier as **Login Point**.\n\n## Samples\n\nID examples\n\n```java\n# ID examples\nID1 = \"hulk@4YeVEN3aUnvC1DNUufCq1bs9zoBSJTzVEj\";  // Immortal Hulk\nID2 = \"moki@4WDfe3zZ4T7opFSi3iDAKiuTnUHjxmXekk\";  // Monkey King\n```\n\nMeta Example (JsON) for ```hulk@4YeVEN3aUnvC1DNUufCq1bs9zoBSJTzVEj```\n\n```javascript\n{\n    \"type\"        : \"1\",\n    \"key\"         : {\n        \"algorithm\" : \"RSA\",\n        \"data\"      : \"-----BEGIN PUBLIC KEY-----\\nMIGJAoGBALB+vbUK48UU9rjlgnohQowME+3JtTb2hLPqtatVOW364/EKFq0/PSdnZVE9V2Zq+pbX7dj3nCS4pWnYf40ELH8wuDm0Tc4jQ70v4LgAcdy3JGTnWUGiCsY+0Z8kNzRkm3FJid592FL7ryzfvIzB9bjg8U2JqlyCVAyUYEnKv4lDAgMBAAE=\\n-----END PUBLIC KEY-----\",\n        \"mode\"      : \"ECB\",\n        \"padding\"   : \"PKCS1\",\n        \"digest\"    : \"SHA256\"\n    },\n    \"seed\"        : \"hulk\",\n    \"fingerprint\" : \"jIPGWpWSbR/DQH6ol3t9DSFkYroVHQDvtbJErmFztMUP2DgRrRSNWuoKY5Y26qL38wfXJQXjYiWqNWKQmQe/gK8M8NkU7lRwm+2nh9wSBYV6Q4WXsCboKbnM0+HVn9Vdfp21hMMGrxTX1pBPRbi0567ZjNQC8ffdW2WvQSoec2I=\"\n}\n```\n\n(All data encode with **BASE64** algorithm as default, excepts the **address**)\n\n----\n\nCopyright \u0026copy; 2018-2025 Albert Moky\n[![Followers](https://img.shields.io/github/followers/moky)](https://github.com/moky?tab=followers)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdimchat%2Fmkm-java","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdimchat%2Fmkm-java","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdimchat%2Fmkm-java/lists"}